.net core mvc route的注冊,激活,調用流程
mvc的入口是route,當前請求的url匹配到合適的route之后,mvc根據route所指定的controller和action激活controller並調用action完成mvc的處理流程。下面我們看看服務器是如何調用route的。
core mvc startup基本代碼。重點在AddMvc和UseMvc
public class Startup
{
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
AddMvc:把各種service加入IOC容器。比如格式化提供程序,action定位器,controllerFactory,controller激活器等等,一應服務全部在這里加入。
UseMvc:最重要的一行代碼:builder.UseMiddleware
public static IMvcBuilder AddMvc(this IServiceCollection services)
{
var builder = services.AddMvcCore();
builder.AddJsonFormatters();
builder.AddCors();
return new MvcBuilder(builder.Services, builder.PartManager);
}
public static IMvcCoreBuilder AddMvcCore(this IServiceCollection services, Action<MvcOptions> setupAction)
{
var builder = services.AddMvcCore();
services.Configure(setupAction);
return builder;
}
internal static void AddMvcCoreServices(IServiceCollection services)
{
services.TryAddSingleton<IActionSelector, ActionSelector>();
services.TryAddSingleton<ActionConstraintCache>();
services.TryAddSingleton<IActionSelectorDecisionTreeProvider, ActionSelectorDecisionTreeProvider>();
// This has a cache, so it needs to be a singleton
services.TryAddSingleton<IControllerFactory, DefaultControllerFactory>();
// Will be cached by the DefaultControllerFactory
services.TryAddTransient<IControllerActivator, DefaultControllerActivator>();
services.TryAddEnumerable(ServiceDescriptor.Transient<IControllerPropertyActivator, DefaultControllerPropertyActivator>());
// Route Handlers
services.TryAddSingleton<MvcRouteHandler>(); // Only one per app
services.TryAddTransient<MvcAttributeRouteHandler>(); // Many per app
}
public static IApplicationBuilder UseMvc(this IApplicationBuilder app, Action<IRouteBuilder> configureRoutes)
{
var routes = new RouteBuilder(app)
{
DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(),
};
configureRoutes(routes);
routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices));
return app.UseRouter(routes.Build());
}
public static IApplicationBuilder UseRouter(this IApplicationBuilder builder, IRouter router)
{
return builder.UseMiddleware<RouterMiddleware>(router);
}
如此,mvc的入口route handler就加入了我們的請求委托鏈中。后續服務器接收到的請求就能交由route匹配,查找action,激活action處理。
router middleware的激活調用
//middleware加入_components請求處理委托鏈
public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
_components.Add(middleware);
return this;
}
public static class UseMiddlewareExtensions
{
private const string InvokeMethodName = "Invoke";
private static readonly MethodInfo GetServiceInfo = typeof(UseMiddlewareExtensions).GetMethod(nameof(GetService), BindingFlags.NonPublic | BindingFlags.Static);
//注冊middleware
public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
{
var applicationServices = app.ApplicationServices;
//將middleware 加入請求處理委托鏈
return app.Use(next =>
{
//解析方法和參數。查找類的Invoke方法作為入口方法。所以middleware只要是個class就行。只要有一個功公共的Invoke方法即可。
var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
var invokeMethods = methods.Where(m => string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)).ToArray();
var methodinfo = invokeMethods[0];
var parameters = methodinfo.GetParameters();
var ctorArgs = new object[args.Length + 1];
ctorArgs[0] = next;
Array.Copy(args, 0, ctorArgs, 1, args.Length);
//創建middleware的實例。並且通過構造函數注入相關的service
var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
//如果方法只有一個參數,默認它就是httpcontext。
if (parameters.Length == 1)
{
return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);
}
//多余一個參數的則構建一個func。並從ioc容器解析參數注入
var factory = Compile<object>(methodinfo, parameters);
return context =>
{
var serviceProvider = context.RequestServices ?? applicationServices;
return factory(instance, context, serviceProvider);
};
});
}
//代碼中的創建實例注入service,創建有多個參數的invoke方法注入service具體代碼就不貼上來了,占地方。
//構造函數就是匹配最適合的構造函數,然后從IServiceProvider get實例,注入。
//多個參數的invoke就更簡單了。直接從IServiceProvider get實例注入。
上述源代碼git地址,aspnet/HttpAbstractions項目
route handler middleware代碼
public class RouterMiddleware
{
private readonly ILogger _logger;
private readonly RequestDelegate _next;
private readonly IRouter _router;
//創建middleware的實例。並且通過構造函數注入相關的service
public RouterMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IRouter router)
{
_next = next;
_router = router;
_logger = loggerFactory.CreateLogger<RouterMiddleware>();
}
//被調用的方法。從這里開始進入mvc route。
public async Task Invoke(HttpContext httpContext)
{
//此處的 IRouter router對象。是我們在Startup中routes.MapRoute...配置的route集合對象:RouteCollection。當然也還有比如attributeroute等等好幾種route。
var context = new RouteContext(httpContext);
context.RouteData.Routers.Add(_router);
await _router.RouteAsync(context);
if (context.Handler == null)
{
//沒有匹配到route的情況
_logger.RequestDidNotMatchRoutes();
await _next.Invoke(httpContext);
}
else
{
httpContext.Features[typeof(IRoutingFeature)] = new RoutingFeature()
{
RouteData = context.RouteData,
};
//匹配到路由處理
await context.Handler(context.HttpContext);
}
}
}
//Microsoft.AspNetCore.Routing.RouteCollection
public async virtual Task RouteAsync(RouteContext context)
{
// Perf: We want to avoid allocating a new RouteData for each route we need to process.
// We can do this by snapshotting the state at the beginning and then restoring it
// for each router we execute.
var snapshot = context.RouteData.PushState(null, values: null, dataTokens: null);
for (var i = 0; i < Count; i++)
{
var route = this[i];
context.RouteData.Routers.Add(route);
try
{
//循環所有routes規則,逐一匹配,匹配到一個自然就break。
await route.RouteAsync(context);
if (context.Handler != null)
break;
}
finally
{
if (context.Handler == null)
snapshot.Restore();
}
}
}
UseMvc中有一行非常重要的代碼。給RouteBuilder的DefaultHandler賦值一個handler。記住這行代碼,我們繼續往下看。
public static IApplicationBuilder UseMvc(this IApplicationBuilder app, Action<IRouteBuilder> configureRoutes)
{
var routes = new RouteBuilder(app)
{
DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(),
};
}
//我們在Startup中routes.MapRoute的所有調用最終調用方法都是這個。new Route( routeBuilder.DefaultHandler,....)
//全部都指定了_target為routeBuilder.DefaultHandler
public static IRouteBuilder MapRoute(this IRouteBuilder routeBuilder, string name, string template, object defaults, object constraints, object dataTokens)
{
if (routeBuilder.DefaultHandler == null)
throw new RouteCreationException(Resources.FormatDefaultHandler_MustBeSet(nameof(IRouteBuilder)));
var inlineConstraintResolver = routeBuilder.ServiceProvider.GetRequiredService<IInlineConstraintResolver>();
routeBuilder.Routes.Add(new Route(
routeBuilder.DefaultHandler,
name, template, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints), new RouteValueDictionary(dataTokens), inlineConstraintResolver));
return routeBuilder;
}
到這里,我們的邏輯有點繞了,讓我們理理清楚:
1.請求進到RouterMiddleware.Invoke()方法
2.調用RouteCollection.RouteAsync()方法,RouteCollection.RouteAsync方法中循環注冊的每一個route對象。
並調用route對象的RouteAsync()方法(route對象的RouteAsync方法在它的父類中Microsoft.AspNetCore.Routing.RouteBase)。
這里說的route對象即時Startup中routes.MapRoute生成的route對象(Microsoft.AspNetCore.Routing.Route)。Route繼承RouteBase,RouteBase實現IRouter接口
3.RouteBase.RouteAsync()判斷當前請求是否符合當前route規則,如果匹配的話,則調用抽象方法OnRouteMatched
4.RouteBase的抽象方法OnRouteMatched,又回到Route對象的OnRouteMatched方法中。調用_target.RouteAsync();_target對象即上面代碼中的routeBuilder.DefaultHandler。
5.來到Microsoft.AspNetCore.Mvc.Internal.MvcRouteHandler.RouteAsync()方法中。最重要的一行代碼: context.Handler =....
6.調用堆棧最終返回到1中(RouterMiddleware.Invoke())。判斷context.Handler == null。為null沒找到route匹配的action。不為null則await context.Handler(context.HttpContext)
7.context.Handler即為5中賦值的func。即下面的代碼,定位action,調用action。
//Microsoft.AspNetCore.Mvc.Internal.MvcRouteHandler.RouteAsync
public Task RouteAsync(RouteContext context)
{
var candidates = _actionSelector.SelectCandidates(context);
if (candidates == null || candidates.Count == 0)
{
_logger.NoActionsMatched();
return TaskCache.CompletedTask;
}
var actionDescriptor = _actionSelector.SelectBestCandidate(context, candidates);
if (actionDescriptor == null)
{
_logger.NoActionsMatched();
return TaskCache.CompletedTask;
}
context.Handler = (c) =>
{
var routeData = c.GetRouteData();
var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor);
if (_actionContextAccessor != null)
_actionContextAccessor.ActionContext = actionContext;
var invoker = _actionInvokerFactory.CreateInvoker(actionContext);
if (invoker == null)
throw new InvalidOperationException( Resources.FormatActionInvokerFactory_CouldNotCreateInvoker(actionDescriptor.DisplayName));
return invoker.InvokeAsync();
};
return TaskCache.CompletedTask;
}
至此,route的處理流程大約交代清楚了。包括route的注冊,route的激活,route的選擇等。