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)路由。

细心的同学可能注意到了, 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.htmlreturn 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 的,并且讲解了其中的过程。

相关文章

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

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

原文地址:http://www.cnblogs.com/savorboard/p/aspnetcore-mvc-routing-action.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/326221.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

win10偶尔打不开开始菜单(按win键和点击开始菜单都没反应)

像我这种桌面上一个图标都没有的。习惯把所有的应用程序放在开始菜单里面&#xff0c;但是……最近发现点击开始菜单或者按win键的时候召唤不出来开始菜单&#xff0c;怎么都出不来&#xff0c;怎么办&#xff1f;&#xff1f;&#xff1f;难道只有重启电脑来解决吗&#xff1f…

Mybatis 的Log4j日志输出问题 - 以及有关日志的所有问题

转载自 Mybatis 的Log4j日志输出问题 - 以及有关日志的所有问题 使用Mybatis的时候&#xff0c;有些时候能输出&#xff08;主要是指sql&#xff0c;参数&#xff0c;结果&#xff09;日志。有些时候就不能。 无法输出日志的时候&#xff0c;无论怎么配置log4j&#xff0c;…

2019蓝桥杯省赛---java---C---9(等差数列)

题目描述 代码实现 package TEST;import java.util.Arrays; import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scanner new Scanner(System.in);int nscanner.nextInt();int[] arrnew int[n];for (int i 0; i < n; i) {arr…

Win10 Bash\/WSL调试Linux环境下的.NET Core应用程序

一、简介 使用过Mac OS的程序员都知道,在Mac Book Pro上写程序是一件比较爽的事儿,作为dotneter&#xff0c;我们都比较羡慕Mac系统的环境,比如命令行,当然设备也是挺漂亮的。 在新的Win10系统中微软给我们提供了一个基于Ubuntu的Linux子系统&#xff08;Bash/WSL&#xff09…

公众号新上线微信小游戏(疯狂猜图)

为了活跃公众号&#xff0c;于2018.09.29推出一款小游戏《疯狂猜图》&#xff0c;可以赢大奖哦&#xff0c;那么小游戏怎么玩呢&#xff1f;关注公众号的用户只需回复“小游戏”即可弹出游戏链接&#xff0c;点击进入就可以啦~~目前已经有126人参与&#xff0c;期待您的参与&am…

android输入时背景颜色,Button根据EditText输入状态改变背景颜色

需求Button随EditText输入状态改变颜色有3个不同颜色状态&#xff0c;EditText未输入时&#xff0c;Button处于不可点击状态EditText输入时&#xff0c;Button处于高亮状态EditText输入且用户按下按钮&#xff0c;Button --> Pressed状态效果如下&#xff1a;演示图片EditTe…

小和问题

题目描述 思路分析 代码实现 package class02;import java.util.Arrays; import java.util.concurrent.locks.ReentrantLock;/*** 创建人 wdl* 创建时间 2021/4/13* 描述*/ public class Demo02SmallSum {public static int mergeSort(int[] arr){if(arrnull|| arr.length<…

移动用户免费领取15G流量(秒到)

爱刷抖音、头条、火山小视频、西瓜视频的福利来啦&#xff0c;移动用户15G流量免费领取&#xff01;&#xff01;&#xff01;是的&#xff0c;免费领取&#xff01;&#xff01;&#xff01; 我们来看看领取方式&#xff1a; 1.去应用中心下载“今日头条APP” 2.然后打开头…

深入浅出数据库索引原理

前段时间&#xff0c;公司一个新上线的网站出现页面响应速度缓慢的问题&#xff0c; 一位负责这个项目的但并不是搞技术的妹子找到我&#xff0c;让我想办法提升网站的访问速度 &#xff0c;因为已经有很多用户来投诉了。我第一反应觉的是数据库上的问题&#xff0c;假装思索了…

2017蓝桥杯省赛---java---A---2(9数算式)

题目描述 思路分析 全排列check 代码实现 package TEST;import java.util.HashSet; import java.util.Set;class Main{static int[] a { 1, 2, 3, 4, 5, 6, 7, 8, 9 };static int ans;public static void main(String[] args) {f(0);System.out.println(ans / 2);}// 全排列…

Xamarin的Kimono以及Google的Guetzli和Draco

Xamarin开源了用于编辑SkiaSharp对象的工具&#xff0c;而Google则推出了减少2D JPEG和3D图形大小的方案。 Xamarin是微软的子公司&#xff0c;开源了Kimono设计器&#xff0c;它是一个用来图形化编辑SkiaSharp对象的工具&#xff0c;这种对象随后可以转换为目标平台的编码。S…

2017蓝桥杯省赛---java---A---7(正则问题)

题目描述 考虑一种简单的正则表达式&#xff1a; 只由 x ( ) | 组成的正则表达式。 小明想求出这个正则表达式能接受的最长字符串的长度。 例如 ((xx|xxx)x|(x|xx))xx 能接受的最长字符串是&#xff1a; xxxxxx&#xff0c;长度是6。输入 ---- 一个由x()|组成的正则表达式。输…

Mybatis简介与原理

转载自 Mybatis简介与原理 什么是Mybatis MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code&#xff0c;并且改名为MyBatis 。iBATIS一词来源于“internet”和“abatis”的组合&#xff0c;是一个基于Java的持久层框…

Visual Studio 2017通过SSH支持Git

在大多数开发人员的工具包中&#xff0c;Git的重要性在不断提升&#xff0c;说它是一项必备技能也不为过&#xff0c;所以对于任意一款现代化的IDE来说&#xff0c;能够使用Git多样化的功能都是其重要的组成部分。在Visual Studio 2017中&#xff0c;微软已经在其UI界面中扩展了…

2018蓝桥杯省赛---java---A---1(分数)

题目描述 思路分析 方案一 等比数列&#xff0c;再进行约分 方案二 求和 约分&#xff1a;辗转相除法求最大公约数 package TEST;class Main{static int GCD(int x,int y){//求最大公约数if(y0){return x;}return GCD(y,x%y);//是x%y&#xff0c;不是x/y}public static vo…

SpringMVC+Spring4+Mybatis3集成,开发简单Web项目+源码下载

转载自 SpringMVCSpring4Mybatis3集成&#xff0c;开发简单Web项目源码下载 基本准备工作 1、安装JDK1.6以上版本&#xff0c;安装与配置 2、下载mybatis-3.2.0版&#xff1a;https://repo1.maven.org/maven2/org/mybatis/mybatis/ 3、下载mybatis-spring-1.2.1版&#x…

微软宣布12月15日关闭开源软件托管平台CodePlex

网易科技讯4月1日消息&#xff0c;据Venturebeat报道&#xff0c;微软今天宣布&#xff0c;将关闭开源软件托管平台CodePlex。微软2006年推出这项服务&#xff0c;并决定在今年12月15日将其关闭。 微软公司副总裁布莱恩哈里&#xff08;Brian Harry&#xff09;在博文中写道&am…

2018蓝桥杯省赛---java---A--2-(星期一)

题目描述 思路分析 方案一 翻电脑日历得2000年12月31日是周日 方案二 package TEST;class Main{public static void main(String[] args) {int sum0;for (int i 1901; i < 2000; i) {//开始的那天是星期二if((i%4000)||(i%100!0&&i%40)){sum366;}else {sum36…

Mybatis与Hibernate的详细对比

转载自 Mybatis与Hibernate的详细对比 前言 这篇博文我们重点分析一下Mybatis与Hibernate的区别&#xff0c;当然在前面的博文中我们已经深入的研究了Mybatis和Hibernate的原理。 Mybatis 【持久化框架】Mybatis简介与原理【持久化框架】SpringMVCSpring4Mybatis3集成&…

android重置系统,安卓手机越用越卡,恢复出厂设置真有用?别瞎搞,看完就明白了!...

安卓手机越用越卡&#xff0c;恢复出厂设置真有用&#xff1f;别瞎搞&#xff0c;看完就明白了&#xff01;现在手机的价格逐渐的开始上升&#xff0c;一部好一点的手机价格还是比较贵的&#xff0c;所以很多人想要节省更多的换机支出&#xff0c;都会想要购买到一款可以使用的…