前言

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

欢迎转载,请在明显位置给出出处及链接

最新文章

  1. URLConnection类介绍
  2. 前端学PHP之文件操作(认真读读)
  3. Service中事务不能回滚的解决方式(转)
  4. 设置TOMCAT的JVM虚拟机内存大小
  5. [读书笔记]算法(Sedgewick著)&#183;第一章(1)
  6. RabbitMQ PHP操作类,守护进程及相关测试数据
  7. 配置Delphi工具菜单 转
  8. MonkeyRunner于Windows在下面Eclipse开发环境的搭建步骤(并解决在线Jython配置错误的问题)
  9. 解决IIS7运行ASP提示错误:An error occurred on the server when processing the URL. Please contact the system administrator
  10. 集线器(HUB),交换机,和路由器的区别
  11. Android使用ViewPager实现导航菜单
  12. java equals()方法
  13. minicom在虚拟机(linux)安装配置过程
  14. 声音变调算法PitchShift(模拟汤姆猫) 附完整C++算法实现代码
  15. Delphi基础-数据类型
  16. PHP 常用设计模式 (转载)
  17. Educational Codeforces Round 55 (Rated for Div. 2) B. Vova and Trophies
  18. SharePoint每日小贴士Web部件
  19. Mac Node.js 配置
  20. android 开发 我的高德地图代码例子

热门文章

  1. 原生js实现轮播图
  2. [MongoDB] - 数据的增删改操作
  3. C语言陷阱:浮点运算
  4. 几分钟看完 flow.ci 全部功能
  5. JAVA包名、类名、变量名命名规则
  6. Docker存储驱动之Btrfs简介
  7. 两款【linux字符界面下】显示【菜单】,【选项】的powershell脚本模块介绍
  8. Ubuntu下安装Redis并实现远程访问
  9. java基础之路(一)
  10. 知识管理(KM) - 数据流