asp.net core mvc剖析:路由
在mvc框架中,任何一个动作请求都会被映射到具体控制器中的方法上,那框架是如何完成这样一个过程的,现在我们就来简单分析下流程。
我们紧跟上面的主题,任何一个请求都会交给处理管道进行处理,那mvc处理的流程自然也应该处于这个管道中,在startup.cs文件的Configure方法中,我们会看到这样的代码
app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}", defaults: new { area = "admin" }); });
这部分代码的作用我们都清楚,就是配置路由规则,把用户的请求,路由到控制器方法上,我们来看它里面怎么做到的。首先看下UseMvc方法,直接上代码:
public static IApplicationBuilder UseMvc( this IApplicationBuilder app, Action<IRouteBuilder> configureRoutes) { 。。。。。。 //实例化路由构造器 var routes = new RouteBuilder(app) { //设置默认处理器,就是路由符合条件时使用MvcRouteHandler来处理请求 DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(), }; //配置路由规则 configureRoutes(routes); //这句很重要,上面配置的全局的路由规则,我们同样可以在控制器或者控制器方法上使用RouteAttribute配置路由规则,这些规则会优先采用 routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices)); //routes.Build方法生成IRouter对象,一会我们在看具体细节,然后通过UseRouter注册一个RouterMiddleware中间件 return app.UseRouter(routes.Build()); }
我们来看下Build方法代码:
public IRouter Build() { //创建一个路由规则集合 var routeCollection = new RouteCollection(); //把配置的路由规则加入到集合中,这个Routes就是上面configureRoutes(routes)配置的 foreach (var route in Routes) { routeCollection.Add(route); } return routeCollection; }
configureRoutes中,通过MapRoute方法注册规则,我们看一下:
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>(); //new了一个Route对象,把这个对象加入到routeBuilder.Routes集合中 routeBuilder.Routes.Add(new Route( routeBuilder.DefaultHandler, name, template, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints), new RouteValueDictionary(dataTokens), inlineConstraintResolver)); return routeBuilder; }
路由规则配置好了,当用户请求过来后,又是如何进行匹配的?上面我们提到了RouterMiddleware中间件,用户请求会通过这个中间件进行处理,这个中间件Invoke方法的代码:
public async Task Invoke(HttpContext httpContext) { var context = new RouteContext(httpContext); //这句目前没有搞清楚作用是什么 context.RouteData.Routers.Add(_router); //_router就是我们上面通过Build方法创建的,它就是RouteCollection await _router.RouteAsync(context); //判断是否找到了匹配的规则,这里的Handler只有当规则匹配了,才会赋值,Handler是什么?留一个疑问 if (context.Handler == null) { _logger.RequestDidNotMatchRoutes(); //如果没有任何路由符合要求,直接执行下一个中间件 await _next.Invoke(httpContext); } else { httpContext.Features[typeof(IRoutingFeature)] = new RoutingFeature() { RouteData = context.RouteData, }; //使用Handler处理请求 await context.Handler(context.HttpContext); } }
下面来看RouteCollection的RouteAsync方法实现:
public async virtual Task RouteAsync(RouteContext context) { 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 { //调用Route对象的RouteAsync方法,匹配规则 await route.RouteAsync(context); //规则匹配成功,直接break if (context.Handler != null) { break; } } finally { if (context.Handler == null) { snapshot.Restore(); } } } }
具体匹配方式不再介绍了,就是根据请求的路径跟设置的地址规则进行对比,我们只看匹配成功后,做了什么?
protected override Task OnRouteMatched(RouteContext context) { context.RouteData.Routers.Add(_target); //_target是routeBuilder.DefaultHandler,这个是在上面创建routeBuilder时设置的,是一个MvcRouteHandler return _target.RouteAsync(context); }
在MvcRouteHandler里完成了context.Handler的设置,下面是这个Handler的具体代码:
context.Handler = (c) => { var routeData = c.GetRouteData(); var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor); if (_actionContextAccessor != null) { _actionContextAccessor.ActionContext = actionContext; } //根据请求的动作信息创建一个IActionInvoker对象 var invoker = _actionInvokerFactory.CreateInvoker(actionContext); if (invoker == null) { throw new InvalidOperationException( Resources.FormatActionInvokerFactory_CouldNotCreateInvoker( actionDescriptor.DisplayName)); } //完成动作执行 return invoker.InvokeAsync(); };
上面调用过程如下图:
后面再详细介绍mvc具体执行过程。