ASP.NET Core 2.2 : 十六.扒一扒2.2版更新的新路由方案

ASP.NET Core 2.2 : 十六.扒一扒2.2版更新的新路由方案
原文:ASP.NET Core 2.2 : 十六.扒一扒2.2版更新的新路由方案

ASP.NET Core 从2.2版本开始,采用了一个新的名为Endpoint的路由方案,与原来的方案在使用上差别不大,但从内部运行方式上来说,差别还是很大的。上一篇详细介绍了原版路由方案的运行机制,本文仍然通过一幅图来了解一下新版的运行机制,最后再总结一下二者的异同点。(ASP.NET Core 系列目录)

一、概述

       此方案从2.2版本开始,被称作终结点路由(下文以“新版”称呼),它是默认开启的,若想采用原来的方案(<=2.1,下文以原版称呼),可以在AddMvc的时候进行设置

services.AddMvc(option=>option.EnableEndpointRouting = false).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

EnableEndpointRouting 默认为true,也就是启用新的Endpoint方案,设置为false则采用旧版(<=2.1)的路由方案。

        在配置方法上来说,系统仍然采用在Startup中的use.Mvc()中配置,而实际上内部的处理中间件已由原来的RouterMiddleware改为EndpointMiddleware和EndpointRoutingMiddleware两个中间件处理,下面依旧通过一幅图来详细看一下:

 二、流程及解析

为了方便查看,依然对几个“重点对象”做了颜色标识(点击图片可以看大图):

      1. 路由的初始化配置(图的前两个泳道) 

  1. ①  一切依然是从Startup开始,而且和旧版一样,是通过UseMvc方法进行配置,传入routes.MapRoute(...)这样的一个或多个配置, 不做赘述。
  1. 下面着重说一下后面的流程,看一下MvcApplicationBuilderExtensions中的UseMvc方法:
 1 public static IApplicationBuilder UseMvc(
 2     this IApplicationBuilder app,
 3     Action<IRouteBuilder> configureRoutes)
 4 {
 5 //此处各种验证,略。。
 6     var options = app.ApplicationServices.GetRequiredService<IOptions<MvcOptions>>();
 7     if (options.Value.EnableEndpointRouting)
 8     {
 9         var mvcEndpointDataSource = app.ApplicationServices
10             .GetRequiredService<IEnumerable<EndpointDataSource>>()
11             .OfType<MvcEndpointDataSource>()
12             .First();
13         var parameterPolicyFactory = app.ApplicationServices
14             .GetRequiredService<ParameterPolicyFactory>();
15 
16         var endpointRouteBuilder = new EndpointRouteBuilder(app);
17 
18         configureRoutes(endpointRouteBuilder);
19 
20         foreach (var router in endpointRouteBuilder.Routes)
21         {
22             // Only accept Microsoft.AspNetCore.Routing.Route when converting to endpoint
23             // Sub-types could have additional customization that we can't knowingly convert
24             if (router is Route route && router.GetType() == typeof(Route))
25             {
26                 var endpointInfo = new MvcEndpointInfo(
27                     route.Name,
28                     route.RouteTemplate,
29                     route.Defaults,
30                     route.Constraints.ToDictionary(kvp => kvp.Key, kvp => (object)kvp.Value),
31                     route.DataTokens,
32                     parameterPolicyFactory);
33              mvcEndpointDataSource.ConventionalEndpointInfos.Add(endpointInfo);
34             }
35             else
36             {
37                 throw new InvalidOperationException($"Cannot use '{router.GetType().FullName}' with Endpoint Routing.");
38             }
39         }
40         if (!app.Properties.TryGetValue(EndpointRoutingRegisteredKey, out _))
41         {
42             // Matching middleware has not been registered yet
43             // For back-compat register middleware so an endpoint is matched and then immediately used
44             app.UseEndpointRouting();
45         }
46         return app.UseEndpoint();
47     }
48     else
49     {
50        //旧版路由方案
51     }
52 }

            ② 第6行,这里会获取并判断设置的EnableEndpointRouting的值,若为false,则采用旧版路由,详见上一篇文章;该值默认为true,即采用新版路由。
            ③ 对应第9行,MvcEndpointDataSource在新版路由中是个非法非常重要的角色,在启动初始化阶段,它完成了路由表存储和转换,此处先用颜色重点标记一下,大家记住它,在后面的流程中详细介绍。
            ④ 对应第16行,同旧版的RouteBuilder一样,这里会new一个 endpointRouteBuilder,二者都是一个IRouteBuilder,所以也同样调用configureRoutes(endpointRouteBuilder)方法(也就是startup中的配置)获取了一个Route的集合(IList<IRouter>)赋值给endpointRouteBuilder.Routes,这里有个特别该注意的地方if (router is Route route && router.GetType() == typeof(Route)) ,也就是这里只接受route类型,终结点路由系统不支持基于 IRouter的可扩展性,包括从 Route继承。
            ⑤ 对应第20行,这里对刚获取到的endpointRouteBuilder.Routes进行遍历,转换成了一个MvcEndpointInfo的集和,赋值给mvcEndpointDataSource.ConventionalEndpointInfos。
            ⑥ 之后就是向管道塞中间件了,这里的处理中间件由原来的RouterMiddleware改为EndpointMiddleware和EndpointRoutingMiddleware。

       2.请求的处理(图的后两个泳道)

       请求的处理大部分功能在中间件EndpointRoutingMiddleware,他有个重要的属性_endpointDataSource保存了上文中初始化阶段生成的MvcEndpointDataSource,而中间件EndpointMiddleware的功能比较简单,主要是在EndpointRoutingMiddleware筛选出endpoint之后,调用该endpoint的endpoint.RequestDelegate(httpContext)进行请求处理。
            ⑦ InitializeAsync()方法主要是用于调用InitializeCoreAsync()创建一个matcher,而通过这个方法的代码可以看出它只是在第一次请求的时候执行一次。

private Task<Matcher> InitializeAsync()
{
var initializationTask = _initializationTask;
if (initializationTask != null)
{
return initializationTask;
}return InitializeCoreAsync();
}

            ⑧ MvcEndpointDataSource一个重要的方法UpdateEndpoints(),作用是读取所有action,并将这个action列表与它的ConventionalEndpointInfos列表(见⑤)进行匹配,最终生成一个新的列表。如下图,我们默认情况下只配置了一个"{controller=Home}/{action=Index}/{id?}"这样的路由,默认的HomeController有三个action,添加了一个名为FlyLoloController的controller并添加了一个带属性路由的action,最终生成了7个Endpoint,这有点像路由与action的“乘积”。当然,这里只是用默认程序举了个简单的例子,实际项目中可能会有更多的路由模板注册、会有更多的Controller和Action以及属性路由等。

具体代码如下:

 1         private void UpdateEndpoints()
 2         {
 3             lock (_lock)
 4             {
 5                 var endpoints = new List<Endpoint>();
 6                 StringBuilder patternStringBuilder = null;
 7 
 8                 foreach (var action in _actions.ActionDescriptors.Items)
 9                 {
10                     if (action.AttributeRouteInfo == null)
11                     {
12                         // In traditional conventional routing setup, the routes defined by a user have a static order
13                         // defined by how they are added into the list. We would like to maintain the same order when building
14                         // up the endpoints too.
15                         //
16                         // Start with an order of '1' for conventional routes as attribute routes have a default order of '0'.
17                         // This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world.
18                         var conventionalRouteOrder = 1;
19 
20                         // Check each of the conventional patterns to see if the action would be reachable
21                         // If the action and pattern are compatible then create an endpoint with the
22                         // area/controller/action parameter parts replaced with literals
23                         //
24                         // e.g. {controller}/{action} with HomeController.Index and HomeController.Login
25                         // would result in endpoints:
26                         // - Home/Index
27                         // - Home/Login
28                         foreach (var endpointInfo in ConventionalEndpointInfos)
29                         {
30                             // An 'endpointInfo' is applicable if:
31                             // 1. it has a parameter (or default value) for 'required' non-null route value
32                             // 2. it does not have a parameter (or default value) for 'required' null route value
33                             var isApplicable = true;
34                             foreach (var routeKey in action.RouteValues.Keys)
35                             {
36                                 if (!MatchRouteValue(action, endpointInfo, routeKey))
37                                 {
38                                     isApplicable = false;
39                                     break;
40                                 }
41                             }
42 
43                             if (!isApplicable)
44                             {
45                                 continue;
46                             }
47 
48                             conventionalRouteOrder = CreateEndpoints(
49                                 endpoints,
50                                 ref patternStringBuilder,
51                                 action,
52                                 conventionalRouteOrder,
53                                 endpointInfo.ParsedPattern,
54                                 endpointInfo.MergedDefaults,
55                                 endpointInfo.Defaults,
56                                 endpointInfo.Name,
57                                 endpointInfo.DataTokens,
58                                 endpointInfo.ParameterPolicies,
59                                 suppressLinkGeneration: false,
60                                 suppressPathMatching: false);
61                         }
62                     }
63                     else
64                     {
65                         var attributeRoutePattern = RoutePatternFactory.Parse(action.AttributeRouteInfo.Template);
66 
67                         CreateEndpoints(
68                             endpoints,
69                             ref patternStringBuilder,
70                             action,
71                             action.AttributeRouteInfo.Order,
72                             attributeRoutePattern,
73                             attributeRoutePattern.Defaults,
74                             nonInlineDefaults: null,
75                             action.AttributeRouteInfo.Name,
76                             dataTokens: null,
77                             allParameterPolicies: null,
78                             action.AttributeRouteInfo.SuppressLinkGeneration,
79                             action.AttributeRouteInfo.SuppressPathMatching);
80                     }
81                 }
82 
83                 // See comments in DefaultActionDescriptorCollectionProvider. These steps are done
84                 // in a specific order to ensure callers always see a consistent state.
85 
86                 // Step 1 - capture old token
87                 var oldCancellationTokenSource = _cancellationTokenSource;
88 
89                 // Step 2 - update endpoints
90                 _endpoints = endpoints;
91 
92                 // Step 3 - create new change token
93                 _cancellationTokenSource = new CancellationTokenSource();
94                 _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token);
95 
96                 // Step 4 - trigger old token
97                 oldCancellationTokenSource?.Cancel();
98             }
99         }
View Code

本质就是计算出一个个可能被请求的请求终结点,也就是Endpoint。由此可见,如上一篇文章那样想自定义一个handler来处理特殊模板的方式(如 routes.MapRoute("flylolo/{code}/{name}", MyRouteHandler.Handler);)将被忽略掉,因其无法生成 Endpoint,且此种方式完全可以自定义一个中间件来实现,没必要混在路由中。

            ⑨ 就是用上面生成的Matcher,携带Endpoint列表与请求URL做匹配,并将匹配到的Endpoint赋值给feature.Endpoint。
            ⑩ 获取feature.Endpoint,若存在则调用其RequestDelegate处理请求httpContext。

 三、新版与旧版的异同点总结

简要从应用系统启动和请求处理两个阶段对比说一下两个版本的区别:

1.启动阶段:

这个阶段大部分都差不多,都是通过Startup的app.UseMvc()方法配置一个路由表,一个Route的集合Routes(IList<IRouter>),然后将其简单转换一下

<=2.1:  将Routes转换为RouteCollection

2.2+ :   将Routes转换为List<MvcEndpointInfo>

二者区别不大,虽然名字不同,但本质上还是差不多,都仍可理解为Route的集合的包装。

2.请求处理阶段:

<=2.1:   1. 将请求的URL与RouteCollection中记录的路由模板进行匹配。

           2. 找到匹配的Route之后,再根据这个请求的URL判断是否存在对应的Controlled和Action。

           3. 若以上均通过,则调用Route的Handler对HttpContext进行处理。

2.2+ :   1. 第一次处理请求时,首先根据启动阶段所配置的路由集合List<MvcEndpointInfo>和_actions.ActionDescriptors.Items(所有的action的信息)做匹配,生成一个列表,这个列表存储了所有可能被匹配的URL模板,如下图,这个列表同样是List<MvcEndpointInfo>,记录了所有可能的URL模式,实际上是列出了一个个可以被访问的详细地址,已经算是最终地址了,即终结点,或许就是为什么叫Endpoint路由的原因。

            2.请求的Url和这个生成的表做匹配,找到对应的MvcEndpointInfo。

            3. 调用被匹配的MvcEndpointInfo的RequestDelegate方法对请求进行处理。

二者区别就是对于_actions.ActionDescriptors.Items(所有的action的信息)的匹配上,原版是先根据路由模板匹配后,再根据ActionDescriptors判断是否存在对应的Controller和action,而新版是先利用了action信息与路由模板匹配,然后再用请求的URL进行匹配,由于这样的工作只在第一次请求的时候执行,所以虽然没有做执行效率上的测试,但感觉应该是比之前快的。

posted on 2019-01-15 14:34 NET未来之路 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/lonelyxmas/p/10271762.html

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

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

相关文章

ES6学习笔记

ES6学习笔记 在学习ES6的过程中做的一些记录&#xff0c;用于未来的快速回忆。let&const 作用域的概念 ES6新增块级作用域的概念&#xff08;一个大括号括起来的部分就是一个块作用域&#xff09;let与const用于在块级作用域中声明变量&#xff0c;该变量仅在当前块级作用域…

用jenkins创建节点

原料&#xff1a;(1)jre下载链接&#xff1a;https://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html (2)jdk:下载链接&#xff1a;https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 一、创建SLAVE节点…

统计git仓库一些commit数据

基于git统计某个人的代码提交行数 git log --author"xxx" --prettytformat: --since1.hour.ago --numstat | awk { add $1 ; subs $2 ; loc $1 - $2 } END { printf "added lines: %s removed lines : %s total lines: %s\n",add,subs,loc } - 统计仓…

JAXB: XML绑定的Java体系结构

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 用于XML绑定的Java体系结构&#xff08;JAXB&#xff09;是一种软件框架&#xff0c;它允许Java开发人员将Java 类映射到XML表示。JAXB提…

解决 Script Error 的另类思路

2019独角兽企业重金招聘Python工程师标准>>> 本文由小芭乐发表 前端的同学如果用 window.onerror 事件做过监控&#xff0c;应该知道&#xff0c;跨域的脚本会给出 "Script Error." 提示&#xff0c;拿不到具体的错误信息和堆栈信息。 这里读者可以跟我一…

大平台的局限

这篇文章算是二稿。初稿使的是惯用的卖弄笔法&#xff0c;写到尽兴时去查了查资料&#xff0c;哦草&#xff0c;错了好多。闷闷不乐。后来就不敢再鬼扯&#xff0c;老老实实干巴巴地讲观点。 做产品的人都喜欢大平台&#xff0c;好像男人都喜欢大胸脯女郎&#xff0c;但是胸脯大…

Lisenter笔记

EventListener与EventObject要完成在线用户列表的监听器&#xff0c;需要使用如下几个接口&#xff1a;ServletContextListener接口&#xff1a;在上下文初始化时设置一个空的集合到application之中&#xff1b;HttpSessionAttributeListener接口&#xff1a;用户增加session属…

Android应用开发—重载fragment构造函数导致的lint errors

背景&#xff1a;在一次release打包中发现lint报以下错误&#xff1a; Error: Avoid non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead [ValidFragment] 根据后面的log提示是由于重载了fragment的构造函数&…

迅雷影音怎样 1.5倍速度播放

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 看视频 觉得播放速度太慢&#xff0c;想让1.5速度播放可以这样设置&#xff1a; 点击快进按钮&#xff0c;点一次变为1.1倍&#xff0c…

【Java】Mybatis mapper动态代理方式

前言 我们在使用Mybatis的时候&#xff0c;获取需要执行的SQL语句的时候&#xff0c;都是通过调用xml文件来获取&#xff0c;例如&#xff1a;User user (User) sqlSession.selectOne("cn.ddnd.www.Entity.User.getUser", "xue8qq.com");。这种方式是通过…

git pull时冲突的几种解决方式

仅结合本人使用场景&#xff0c;方法可能不是最优的 1. 忽略本地修改&#xff0c;强制拉取远程到本地 主要是项目中的文档目录&#xff0c;看的时候可能多了些标注&#xff0c;现在远程文档更新&#xff0c;本地的版本已无用&#xff0c;可以强拉 git fetch --allgit reset --h…

Android应用开发—eventBus发布事件和事件处理的时序关系

占坑&#xff0c;简单说明下eventBus发布事件和事件处理的时序关系。 什么时候使用sticky&#xff1a; 当你希望你的事件不被马上处理的时候&#xff0c;举个栗子&#xff0c;比如说&#xff0c;在一个详情页点赞之后&#xff0c;产生一个VoteEvent&#xff0c;VoteEvent并不立…

grep命令 解说

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 grep&#xff08;global search regular expression(RE) and print out the line&#xff0c;全面搜索正则表达式并把行打印出来&#x…

创业第一桶金怎么来

文章摘要&#xff1a;资金是创业要具备的一个必要条件&#xff0c;那么对于创业者来说&#xff0c;第一桶金如何取得&#xff1f;资金是创业要具备的一个必要条件&#xff0c;那么对于创业者来说&#xff0c;第一桶金如何取得&#xff1f;   一、一门手艺   都说拥有万贯…

4001.基于双向链表的双向冒泡排序法

基于双向链表的双向冒泡排序法 发布时间: 2018年11月26日 10:09 时间限制: 1000ms 内存限制: 128M 习题集源码中出现了 temp->next->prior p; 本人推断这里缺少预先的对temp->nextNULL这种情况的判定&#xff0c;所以需加入一个判断语句解决。 此为非循环的双向链…

页面向上滚动

#页面或者div向上无缝滚动 1.css: body {margin: 0;padding: 0;overflow: hidden;}.container {position: relative;top: 0;}.container div {width: 500px;height: 500px;border: 1px solid chartreuse;font-size: 100px;line-height: 500px;font-weight: bold;color: black;t…

叨逼叨

此处记录点零散的小idea&#xff0c;为了避免把csdn当微博&#xff0c;开一篇&#xff0c;都记在这里吧。 感觉服务注册机制&#xff0c;貌似也是一种依赖注入。&#xff08;虽然我还没完全搞懂依赖注入&#xff09;&#xff0c;理由呢&#xff1a;你需要一个模块的功能&#x…

Linux:echo命令详解

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 echo命令 用于字符串的输出 格式 echo string使用echo实现更复杂的输出格式控制 1.显示普通字符串: echo "It is a test"这里…

看年轻人如何赚第一桶金

上世纪90年代&#xff0c;成为百万富翁&#xff0c;对很多人只是个梦想。不过如今&#xff0c;随着经济飞速发展&#xff0c;拥有百万资产已经不再是神话&#xff0c;放眼望去&#xff0c;我们身边的百万富翁比比皆是&#xff0c;甚至很多初入社会、白手起家的年轻人&#xff0…

跨越解决方案之nginx

这里是修真院前端小课堂&#xff0c;每篇分享文从 【背景介绍】【知识剖析】【常见问题】【解决方案】【编码实战】【扩展思考】【更多讨论】【参考文献】 八个方面深度解析前端知识/技能&#xff0c;本篇分享的是&#xff1a; 【跨越解决方案之nginx】 1.背景介绍 跨域&#x…