前言

上一篇 文章中,我们已经学习了 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)路由。

细心的同学可能注意到了, MvcAttributeRouteHandlerMvcRouteHandler 多了一个 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框架用来包装 ControllerFilter , 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:

下面是MvcAttributeRouteHandlerRouteAsync

可以看到,在 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 的激活的更多相关文章

  1. ASP.NET Core MVC 源码学习:MVC 启动流程详解

    前言 在 上一篇 文章中,我们学习了 ASP.NET Core MVC 的路由模块,那么在本篇文章中,主要是对 ASP.NET Core MVC 启动流程的一个学习. ASP.NET Core 是新一 ...

  2. ASP.NET Core MVC 源码学习:详解 Action 的匹配

    前言 在 上一篇 文章中,我们已经学习了 ASP.NET Core MVC 的启动流程,那么 MVC 在启动了之后,当请求到达过来的时候,它是怎么样处理的呢? 又是怎么样把我们的请求准确的传达到我们的 ...

  3. ASP.NET Core MVC 源码学习:Routing 路由

    前言 最近打算抽时间看一下 ASP.NET Core MVC 的源码,特此把自己学习到的内容记录下来,也算是做个笔记吧. 路由作为 MVC 的基本部分,所以在学习 MVC 的其他源码之前还是先学习一下 ...

  4. MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)

    前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎.这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之 ...

  5. [转]MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)

    本文转自:http://www.cnblogs.com/landeanfen/p/5989092.html 阅读目录 一.MVC原理解析 1.MVC原理 二.HttpHandler 1.HttpHan ...

  6. MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)

    前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...

  7. ASP.NET Core 框架源码地址

    ASP.NET Core 框架源码地址 https://github.com/dotnet/corefx 这个是.net core的 开源项目地址 https://github.com/aspnet  ...

  8. MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)

    前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...

  9. MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)

    前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...

随机推荐

  1. 一口一口吃掉Volley(一)

    欢迎访问我的个人博客转发请注明出处:http://www.wensibo.top/2017/02/16/一口一口吃掉Volley(一)/ 本次编写的Volley教程现在看来其实已经跟不上时代了,但是技 ...

  2. java程序测试之字符流

    package filestream; import java.io.FileReader; import java.io.FileWriter; import java.io.FileNotFoun ...

  3. CentOS安装Git服务器

    1.安装Git $ yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel perl-devel $ yum ...

  4. Top 10 Books For Advanced Level Java Developers

    Java is one of the most popular programming language nowadays. There are plenty of books for beginne ...

  5. 测试工作中ADB命令实战

    作者:TT,<测试架构师>微信公众号作者 大家能点击进来,说明还是对ADB有所了解或听说过的,可能也会比较熟练的掌握了这些命令,下面描述如有不对的地方,欢迎指正和交流学习,请多指教! 一. ...

  6. 纪中集训 Day 8 & Last Day

    好吧回到家一直玩到现在才来写冏= = 然后今天终于在最后一场比赛中AK了= = 虽然有两人AK,另一个是初二牛ORZ 其实都是水题+模板题 第一题是DP,第二题是模拟,第三题是可持久化TREAP(其实 ...

  7. js五种设计模式说明与示例

    第一种模式:js工厂模式    var lev=function(){        return "啊打";      };      function Parent(){    ...

  8. Python基础之数据类型

    Python基础之数据类型 变量赋值 Python中的变量不需要声明,变量的赋值操作既是变量声明和定义的过程. 每个变量在内存中创建,都包括变量的标识,名称和数据这些信息. 每个变量在使用前都必须赋值 ...

  9. rsyslog+mariadb+loganalyzer实现日志服务器搭建

    rsyslog+mariadb+loganalyzer实现日志服务器搭建 一.概述 Linux的日志记录了用户在系统上一切操作,包括系统自身运作产生的日志,这些日志是应使用者了解服务器的情况最好的资料 ...

  10. Javascript定时器中的this指向

    使用js中的定时器(setInterval,setTimeout),很容易会遇到this指向的问题. 直接上例子: var name = 'my name is window'; var obj = ...