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; }
}
ApplicationModel
里面关于 Controller 的包装是一个 IList<ControllerModel>
,看一下 ControllerModel
的定义:
public class ControllerModel : ICommonModel, IFilterModel, IApiExplorerModel
{
//......
public IList<ActionModel> Actions { get; }
public ApiExplorerModel ApiExplorer { get; set; }
public ApplicationModel Application { get; set; }
public IReadOnlyList<object> Attributes { get; }
MemberInfo ICommonModel.MemberInfo => ControllerType;
string ICommonModel.Name => ControllerName;
public string ControllerName { get; set; }
public TypeInfo ControllerType { get; }
public IList<PropertyModel> ControllerProperties { get; }
public IList<IFilterMetadata> Filters { get; }
public IDictionary<string, string> RouteValues { get; }
public IDictionary<object, object> Properties { get; }
public IList<SelectorModel> Selectors { get; }
}
在 ASP.NET Core MVC 框架中,ApplicationModel
有下面几个提供者,他们用于初始化整个 ApplicationModel 的各个部分,我们还是分别看一下吧。
AuthorizationApplicationModelProvider
: 处理认证相关业务逻辑,在它的Executing方法中会将 AuthorizeFilter
,AllowAnonymousFilter
等过滤器添加到 ApplicationModelProviderContext 里面的 ApplicationModel 里。
DefaultApplicationModelProvider
:初始化 ControllerModel
, 添加 Controller 相关的各种信息,添加用户自定义 Filter,遍历 ControllerTypes : 创建 ControllerModel
--> 初始化Properties
--> 初始化Parameters
。
CorsApplicationModelProvider
:跨域资源相关逻辑,添加CorsAuthorizationFilterFactory
,DisableCorsAuthorizationFilter
,CorsAuthorizationFilterFactory
,DisableCorsAuthorizationFilter
等过滤器。
TempDataApplicationModelProvider
: 添加 SaveTempDataPropertyFilterFactory
过滤器,存储Controller中的TempData信息,注意 TempDataAttribute 修饰的属性只能是基元类型或字符串。
构建ApplicationModel
MVC 框架通过 ControllerActionDescriptorProvider
中的 BuildModel()
这个方法进行 ApplicationModel 的构建:
internal protected ApplicationModel BuildModel()
{
var controllerTypes = GetControllerTypes();
var context = new ApplicationModelProviderContext(controllerTypes);
for (var i = 0; i < _applicationModelProviders.Length; i++)
{
_applicationModelProviders[i].OnProvidersExecuting(context);
}
for (var i = _applicationModelProviders.Length - 1; i >= 0; i--)
{
_applicationModelProviders[i].OnProvidersExecuted(context);
}
return context.Result;
}
现在,我们已经有一个完整的 ApplicationModel 对象了。
有了 ApplicationModel 对象之后,会再进行一次约定的应用。比如以下Action重写路由的情况或者配置多个路由的情况。
2、ActionDescriptorCollection是怎么创建的?
ControllerActionDescriptor构建
ControllerActionDescriptor
的构建是基于ApplicationModel
对象的,下面我就画了一个流程图用来展示构建 ControllerActionDescriptor 的整个过程,就不过多描述了。
截止到目前,我们会得到一个 IEnumerable<ControllerActionDescriptor>
集合对象。
在有了 ControllerActionDescriptor
之后,ActionDescriptorCollectionProvider
会提供一个属性,
public ActionDescriptorCollection ActionDescriptors
{
get
{
if (_collection == null)
{
UpdateCollection();
}
return _collection;
}
}
在这个属性中使用了 UpdateCollection 这个方法来更新 ActionDescriptorCollection
。
private void UpdateCollection()
{
var context = new ActionDescriptorProviderContext();
for (var i = 0; i < _actionDescriptorProviders.Length; i++)
{
_actionDescriptorProviders[i].OnProvidersExecuting(context);
}
for (var i = _actionDescriptorProviders.Length - 1; i >= 0; i--)
{
_actionDescriptorProviders[i].OnProvidersExecuted(context);
}
_collection = new ActionDescriptorCollection(
new ReadOnlyCollection<ActionDescriptor>(context.Results),
Interlocked.Increment(ref _version));
}
OK , 现在我们有了 ActionDescriptorCollection
, 之后的流程就比较简单了,但是会涉及到几个算法。
接下来,轮到 ActionSelectorDecisionTreeProvider
上场了,它主要是把 ActionDescriptorCollection
,组装成为一个 IActionSelectionDecisionTree
对象以便于后续的查找匹配工作, IActionSelectionDecisionTree
的数据结构是一个多叉树,组装过程是使用了一个深度优先的递归算法。
我们回到起点,继续看这张图:
现在 SelectCandidates 你应该能够看懂了:
public IReadOnlyList<ActionDescriptor> SelectCandidates(RouteContext context)
{
//IActionSelectionDecisionTree 对象
var tree = _decisionTreeProvider.DecisionTree;
//使用的是一个多叉树查找算法,关于算法可以看我这篇博文:
//http://www.cnblogs.com/savorboard/p/6582399.html
return tree.Select(context.RouteData.Values);
}
接下来就是 SelectBestCandidates
这个流程:
1、遍历 Action 列表,评估 Action 的相关约束,返回匹配的 ActionDescriptor 列表。
2、从匹配的 ActionDescriptor 列表中返回最佳的 Action 列表,注意这里这个方法 SelectBestActions
,它是一个虚方法,默认是没有实现的会直接返回上一步的结果,也就是说用户可以通过重写这个方法来自定义一些Action匹配规则。
3、如果SelectBestActions 返回的是一个ActionDescriptor,则直接返回,当路由系统匹配到多个 Action 的时候,那么 MVC 需要从这些 Action 候选者中选中最佳的哪一个,当两个动作通过路由匹配时,MVC必须消除歧义以选择“最佳”候选者,否则抛出 AmbiguousActionException
异常
最终 SelectBestCandidates
会返回一个 ActionDescriptor ,即需要执行的 Action。
后续流程的执行,我又画了一个图来表示,希望能够更加清晰一些:
终于讲解结束了,心好累,如果你认为本篇文章对你有帮助的话,顺手点个【推荐】吧。
MvcAttributeRouteHandler:
下面是MvcAttributeRouteHandler
的 RouteAsync
。
可以看到,在 MvcAttributeRouteHandler
中,少了 SelectCandidates()
这个流程,取而代之的是用 Actions 的属性参数。 这个Actions 就比较简单了,就是MVC框架启动的时候配置的IRouter Action。
然后就是 SelectBestCandidates
这个流程了,参考上文的流程吧,都一样。
总结
本文详细描述了 MVC 在 Request 到达的时候是怎么样通过自定义的路由处理程序来选择一个Action 的,并且讲解了其中的过程。
如果你对 .NET Core 感兴趣可以关注我,我会定期在博客分享关于 .NET Core 的学习心得,如果你认为本篇文章对你有帮助的话,谢谢你的【推荐】。
本文地址:http://www.cnblogs.com/savorboard/p/aspnetcore-mvc-routing-action.html
作者博客:Savorboard
欢迎转载,请在明显位置给出出处及链接
ASP.NET Core MVC 源码学习:详解 Action 的匹配的更多相关文章
- ASP.NET Core MVC 源码学习:MVC 启动流程详解
前言 在 上一篇 文章中,我们学习了 ASP.NET Core MVC 的路由模块,那么在本篇文章中,主要是对 ASP.NET Core MVC 启动流程的一个学习. ASP.NET Core 是新一 ...
- ASP.NET Core MVC 源码学习:详解 Action 的激活
前言 在 上一篇 文章中,我们已经学习了 ASP.NET Core MVC 的启动流程,那么 MVC 在启动了之后,当请求到达过来的时候,它是怎么样处理的呢? 又是怎么样把我们的请求准确的传达到我们的 ...
- ASP.NET Core MVC 源码学习:Routing 路由
前言 最近打算抽时间看一下 ASP.NET Core MVC 的源码,特此把自己学习到的内容记录下来,也算是做个笔记吧. 路由作为 MVC 的基本部分,所以在学习 MVC 的其他源码之前还是先学习一下 ...
- MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)
前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎.这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之 ...
- [转]MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)
本文转自:http://www.cnblogs.com/landeanfen/p/5989092.html 阅读目录 一.MVC原理解析 1.MVC原理 二.HttpHandler 1.HttpHan ...
- MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)
前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...
- ASP.NET Core 框架源码地址
ASP.NET Core 框架源码地址 https://github.com/dotnet/corefx 这个是.net core的 开源项目地址 https://github.com/aspnet ...
- MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)
前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...
- MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)
前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...
随机推荐
- [BZOJ2252]矩阵距离(BFS)
题意 输入矩阵m行n列(m<=500,n<=500),只含0.1,输出离每个元素距离最近的1的距离,其中距离定义为D(aij,akl)=abs(i-k)+abs(j-l). 示例: 输入: ...
- Shell中sed----学习
sed原理及使用 目录 前言 一.简介 二.处理流程 三.命令选项options 四.pattern 1. 模式空间 2. 模式空间的转换 3. 地址匹配 五.procedure 1. 替换命令: s ...
- wpf 寻找TreeView的子元素,并对其进行操作
//itemsControl 开始为指定的TreeView控件 item为TreeView子元素 private void PareItems(ItemsControl itemsControl, ...
- 使用Jmeter连接数据库检查数据库记录的方法
一.首先下载Jdbc驱动(jdbc驱动分两种,classes与ojdbc文件,该用哪个需要检查电脑安装的jdk版本(查看方法cmd命令java –version) 关于各种版本的说明:http://e ...
- KNN Python实现
KNN Python实现 ''' k近邻(kNN)算法的工作机制比较简单,根据某种距离测度找出距离给定待测样本距离最小的k个训练样本,根据k个训练样本进行预测. 分类问题:k个点中出现频率最高的类别作 ...
- P2158 [SDOI2008] 仪仗队(欧拉函数模板)
题目描述 作为体育委员,C君负责这次运动会仪仗队的训练.仪仗队是由学生组成的N * N的方阵,为了保证队伍在行进中整齐划一,C君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是否整齐(如下图 ...
- php实现下载模板与上传模板解析
<? //下载模板的请求 if(isset($_GET['action']) && $_GET['action'] =='down_group_excel'){ $code = ...
- docker常用操作备忘
一.docker安装 参考资料:阿里云镜像加速1. 安装/升级Docker客户端 curl -fsSL https://get.docker.com | bash -s docker --mirror ...
- beamer插入图片的一些技巧
1. 点一下,让另一张隐藏的图出现
- ZOJ 2507 Let's play a game
题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1507 MisereNim博弈.代码如下: //=========== ...