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.pswp.cn/news/450128.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/450128.shtml
英文地址,請注明出處:http://en.pswp.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…