我们知道,Startup.cs 中的 Configure(IApplicationBuilder app) 中,我们使用 app.UseMvc()在 UseMVC() 代码执行的过程中,它可以接收一个 Action 形式的委托,我们使用这个委托可以进行自定义路由的配置,默认情况下,我们一般会如下进行配置:
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 configureRoutes)
{
// ......
var routes = new RouteBuilder(app)
{
DefaultHandler = app.ApplicationServices.GetRequiredService(),
};
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(),
services,
actions =>
{
var handler = services.GetRequiredService();
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 candidates)
MvcRouteHandler:
在这个流程中,显示调用了 IActionSelect 接口中的 SelectCandidates() 用来找到所有符合条件的候选 Action,然后调用了 SelectBestCandidate 找出最佳的一个。
程序走到这里,这里会有两个重点的地方,或者叫有疑问的地方?
1、 程序集中定义的 Action 是怎么找到的?
要想找到程序定义的所有 Action,那么首先需要找到 Controller,在上一篇文章中我们已经知道了有一个 MVC 程序用来管理 AssemblyPart 的东西叫 ApplicationPartManager ,它的里面存储了所有 MVC 框架在启动的时候加载的所有程序集,那么我们可以从这个程序集中找到需要的 Controller。下面这个流程图显示了查找Controller 的流程:
GetControllerTypes 返回的是一个 IEnumerable 的集合,有了 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();
Filters = new List();
Properties = new Dictionary();
}
public ApiExplorerModel ApiExplorer { get; set; }
public IList Controllers { get; private set; }
public IList Filters { get; private set; }
public IDictionary Properties { get; }
}
ApplicationModel 里面关于 Controller 的包装是一个 IList,看一下 ControllerModel 的定义:
public class ControllerModel : ICommonModel, IFilterModel, IApiExplorerModel
{
//......
public IList Actions { get; }
public ApiExplorerModel ApiExplorer { get; set; }
public ApplicationModel Application { get; set; }
public IReadOnlyList