ASP.NET Core MVC 源码学习:详解 Action 的匹配
前言
在 上一篇 文章中,我们已经学习了 ASP.NET Core MVC 的启动流程,那么 MVC 在启动了之后,当请求到达过来的时候,它是怎么样处理的呢? 又是怎么样把我们的请求准确的传达到我们的 Action 上呢? 那么,在这边文章中,我们一起跟踪源码看一下,框架都做了些什么东西。
Getting Started我们知道,Startup.cs 中的 Configure(IApplicationBuilder app) 中,我们使用 app.UseMvc()
在 UseMVC() 代码执行的过程中,它可以接收一个 Action<IRouteBuilder> 形式的委托,我们使用这个委托可以进行自定义路由的配置,默认情况下,我们一般会如下进行配置:
app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); });或者是你使用默认的 app.UseMvcWithDefaultRoute(),这个扩展方法在内部已经帮你做了上述代码的内容。
那我们今天就从这个 Route 的配置开始看起吧。
RouteContext 如何初始化?在 IRouteBuilder 通过配置 IRouteBuilder,IRouteBuilder 在 Build() 之后会得到 Router 会得到 IRouter。
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()); }上面的代码有两个地方需要注意的。
第一个地方是 DefaultHandler,可以看到默认配置下,MVC 程序从 DI 中获取 MvcRouteHandler 路由处理程序来作为路由的默认处理程序。
第二个地方是 AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices)
,那么这个地方是干嘛的呢?
CreateAttributeMegaRoute 它返回了一个 IRouter ,主要是用来处理带 RouteAttribute 标记的 Action,我们来看一下这个方法:
public static IRouter CreateAttributeMegaRoute(IServiceProvider services) { return new AttributeRoute( services.GetRequiredService<IActionDescriptorCollectionProvider>(), services, actions => { var handler = services.GetRequiredService<MvcAttributeRouteHandler>(); handler.Actions = actions; return handler; }); }在方法内部,new 了一个 AttributeRoute 返回了回去,大家可以看到有一个参数 actions,它使用的是 MvcAttributeRouteHandler 这个处理程序,说明在实际调用过程中使用的是 MvcAttributeRouteHandler 进行的路由处理。
OK,我们总结一下关于 MVC 自己的几个路由处理程序,还是用一个图比较容易看的清楚,幸运的是,MVC 一共就这3个路由处理程序,我们已经全部接触到了。
MVC 框架针对于 IRouter 接口的实现有以下三个:
提前告诉你,最左边绿色的那个 AttributeRoute 其实只是一个包装,在内部也是通过 MvcAttributeRouteHandler 或者 MvcRouteHandler 进行的处理。那么,现在关于路由的处理程序只剩下了两个,他们分别是:
默认处理程序: MvcRouteHandler,用来处理约定的 Action。
注解处理程序: MvcAttributeRouteHandler ,用来处理注解(Attribute)路由。
细心的同学可能注意到了, MvcAttributeRouteHandler 比 MvcRouteHandler 多了一个 Actions : ActionDescriptor[]属性。
我们再看一下这两个处理程序的 RouteAsync 方法,这个方法是路由组件的入口方法,我们通过一个对比工具来看一下两者之间的差距。
图片看不清楚可以新标签打开
可以看到,这两个 RouteAsync 主要有两处差距,第一处就是 SelectBestCandidate 这个函数第二个参数
ActionDescriptor SelectBestCandidate(RouteContext context, IReadOnlyList<ActionDescriptor> candidates) MvcRouteHandler:在这个流程中,显示调用了 IActionSelect 接口中的 SelectCandidates() 用来找到所有符合条件的候选 Action,然后调用了 SelectBestCandidate 找出最佳的一个。
程序走到这里,这里会有两个重点的地方,或者叫有疑问的地方?
1、 程序集中定义的 Action 是怎么找到的?
要想找到程序定义的所有 Action,那么首先需要找到 Controller,在上一篇文章中我们已经知道了有一个 MVC 程序用来管理 AssemblyPart 的东西叫 ApplicationPartManager ,它的里面存储了所有 MVC 框架在启动的时候加载的所有程序集,那么我们可以从这个程序集中找到需要的 Controller。下面这个流程图显示了查找Controller 的流程:
GetControllerTypes 返回的是一个 IEnumerable<TypeInfo> 的集合,有了 Controller 之后,MVC 框架使用了一个对象来包装 Controller,因为在后续的流程中,除了需要 Controller 之外还需要其他的一些东西,比如 Filter, ApiExplorer 等。
ApplicationModel
ApplicationModel 就是MVC框架用来包装 Controller,Filter , ApiExplorer 等的一个Model 对象,我们来看一下它的定义:
public class ApplicationModel : IPropertyModel, IFilterModel, IApiExplorerModel { public ApplicationModel() { ApiExplorer = new ApiExplorerModel(); Controllers = new List<ControllerModel>(); Filters = new List<IFilterMetadata>(); Properties = new Dictionary<object, object>(); } public ApiExplorerModel ApiExplorer { get; set; } public IList<ControllerModel> Controllers { get; private set; } public IList<IFilterMetadata> Filters { get; private set; } public IDictionary<object, object> Properties { get; } }