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

来源:https://www.cnblogs.com/FlyLolo/archive/2019/01/15/ASPNETCore2_16.html
-Advertisement-
Play Games

ASP.NET Core 從2.2版本開始,採用了一個新的名為Endpoint的路由方案,與原來的方案在使用上差別不大,但從內部運行方式上來說,差別還是很大的。上一篇詳細介紹了原版路由方案的運行機制,本文仍然通過一幅圖來瞭解一下新版的運行機制,最後再總結一下二者的異同點。(ASP.NET Core ...


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. 路由的初始化配置(圖的前兩個泳道) 

    ①  一切依然是從Startup開始,而且和舊版一樣,是通過UseMvc方法進行配置,傳入routes.MapRoute(...)這樣的一個或多個配置, 不做贅述。
    下麵著重說一下後面的流程,看一下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進行匹配,由於這樣的工作只在第一次請求的時候執行,所以雖然沒有做執行效率上的測試,但感覺應該是比之前快的。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 將能夠處理同一類請求的對象形成一條鏈,所提交的請求沿著鏈傳遞,鏈上的對象逐個判斷是否有能力處理該請求 如果能則處理,否則傳遞給下一個對象。 例如:公司行政審批流程,鬥地主游戲,田徑項目中接力運動等。都是責任鏈模式 的運用。 責任鏈可能是一條直線,一個環鏈或者是一個樹結構的一部分。 責任鏈涉及角色: ...
  • 一、下載maven Apache Maven下載地址:http://maven.apache.org/download.cgi 二、maven的安裝 將下載好的安裝文件解壓到d盤根目錄下即可(當然,這裡放別的目錄也行,但是最好不要出現中文.): 三、配置maven的環境變數 第一步:新建一個MAVE ...
  • 1.Window-Preferences-Server-Runtime Environments 2.點擊Add,選擇相應的Tomcat版本,我的是7.0的所以我選擇這個。並勾選Create a new local server,點擊next 3.點擊Browse,選擇Tomcat的本地安裝路徑,並 ...
  • jdk8 Stream map 取對象中某項的值 map 取對象中某項的值 問題 ... List<ScreenWiperResponse> screenWiperList= screenWiperResponseList.stream().map(CarAdapterForScreenWiperR ...
  • Django 系列博客(十) 前言 本篇博客介紹在 Django 中如何對資料庫進行增刪查改,主要為對單表進行操作。 ORM簡介 查詢數據層次圖解:如果操作 mysql,ORM 是在 pymysql 之上又進行了一層封裝。 MVC 或者 MTV 框架中包括一個重要的部分,就是 ORM,它實現了數據模 ...
  • 1. 線程的其他方法 2. 線程隊列 線程隊列中三種隊列形式,所使用的方法相同,都有put(),get(),put_nowait(),get_nowait(),qsize(),full(),empty() 等這些方法.就只傳一組先進先出的代碼 import queue 先進先出隊列:queue.Qu ...
  • php連接資料庫,以及日期處理函數 $conn=mysql_connect("10.0.10.0:0000","root","123456") or die("資料庫連接失敗").mysql_error(); mysql_select_db("database"); $querry1 = "SELE ...
  • 線程的其他方法: from threading import Thread,current_thread: currrent_thread().getName() 獲取線程的名稱 current_thread().ident 獲取到線程的id current_thread() 當前線程的信息 imp ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...