ASP.NET Core的路由[2]:路由系統的核心對象——Router

来源:http://www.cnblogs.com/artech/archive/2016/12/21/asp-net-core-routing-02.html
-Advertisement-
Play Games

ASP.NET Core應用中的路由機制實現在RouterMiddleware中間件中,它的目的在於通過路由解析為請求找到一個匹配的處理器,同時將請求攜帶的數據以路由參數的形式解析出來供後續請求處理流程使用。但是具體的路由解析功能其實並沒有直接實現在RouterMiddleware中間件中,而是由一... ...


ASP.NET Core應用中的路由機制實現在RouterMiddleware中間件中,它的目的在於通過路由解析為請求找到一個匹配的處理器,同時將請求攜帶的數據以路由參數的形式解析出來供後續請求處理流程使用。但是具體的路由解析功能其實並沒有直接實現在RouterMiddleware中間件中,而是由一個Router對象來完成的。[本文已經同步到《ASP.NET Core框架揭秘》之中]

目錄
一、IRouter介面
二、RouteContext
三、RouteData
四、Route
五、RouteHandler
總結

一、IRouter介面

Router是我們對所有實現了IRouter介面的所有類型以及對應對象的統稱,如下麵所示的RouterMiddleware類型定義可以看出,當我們創建這個中間件對象的時候,我們需要指定這個Router。

   1: public class RouterMiddleware
   2: {
   3:     public RouterMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IRouter router);
   4:     public Task Invoke(HttpContext httpContext);
   5: }

除了檢驗請求是否與自身設置的路由規則相匹配,併在成功匹配的情況下解析出路由參數並指定請求處理器之外,Router的路由解析還為另一個領用場景服務,那就是根據自身的路由規則和提供的參數生成一個URL。我們把這兩個方面稱為路由的兩個“方向”,它們分別對應著RouteDirection枚舉的兩個選項。針對這兩個方向的路由解析分別實現在IRouter的如下兩個方法(RouteAsync和GetVirtualPath),目前我們主要關註針對前者的RouteAsync方法。

   1: public interface IRouter
   2: {
   3:     Task RouteAsync(RouteContext context);
   4:     VirtualPathData GetVirtualPath(VirtualPathContext context);
   5: }
   6:  
   7: public enum RouteDirection
   8: {
   9:     IncomingRequest,
  10:     UrlGeneration
  11: }

如上面的代碼片段所示,針對請求實施路由解析的RouteAsync方法的輸入參數是一個類型為RouteContext的上下文對象。這個RouteContext實際上是對一個HttpContext對象的封裝,Router可以利用它得到所有與當前請求相關的信息。如果Router完成路由解析並判斷當前請求與自身的路由規則一致,那麼它會將解析出來的路由參數轉換成一個RouteData並存放到RouteContext對象代表的上下文之中,另一個一併被放入上下文的是代表當前請求處理器的RequestDelegate對象。下圖基本上展示了RouteAsync方法試試路由解析的原理。

6

二、RouteContext

接下來我們來瞭解一下整個路由解析涉及到了幾個核心類型,首先來看看為整個路由解析提供執行上下文的這個RouteContext類型。如上圖所示,一個RouteContext上下文包含三個核心對象,一個是代表當前請求上下文的HttpContext對象,對應的屬性是HttpContext。它實際上是作為路由解析的輸入,併在RouteContext創建的時候以構造函數參數的形式提供。另外兩個則是作為路由解析的輸出,一個是代表存放路由參數的RouteData對象,另一個則是作為請求處理器的RequestDelegate對象,對應的屬性分別是RouteData和Handler。

   1: public class RouteContext
   2: {
   3:     public HttpContext         HttpContext { get; }
   4:     public RouteData           RouteData { get; set; }
   5:     public RequestDelegate     Handler { get; set; }
   6:  
   7:     public RouteContext(HttpContext httpContext);
   8: }

三、RouteData

我們先來看看用於存放路由參數的RouteData類型。從數據來源的角度來講,路由參數具有兩種類型,一種是通過請求路徑攜帶的參數,另一種則是Router對象自身攜帶的參數,這兩種路由參數分別對應著RouteData的Values和DataTonkens屬性。至於另一個屬性Routers,則保存著實施路由解析並提供路由參數的所有Router對象。

   1: public class RouteData
   2: {
   3:     public RouteValueDictionary     Values { get; }
   4:     public RouteValueDictionary     DataTokens { get; }
   5:     public IList<IRouter>           Routers { get; }
   6: }
   7:  
   8: public class RouteValueDictionary : IDictionary<string, object>, IReadOnlyDictionary<string, object>
   9: {
  10:     public RouteValueDictionary(object values);   
  11:
  12: }

從上面的代碼片可以看出,RouteData的Values和DataTokens屬性的類型都是RouteValueDictionary,它實際上就是一個字典對象而已,其Key和Value分別代表路由參數的名稱和值,而作為Key的字元串是不區分大小寫的。值得一提的是RouteValueDictionary具有一個特殊的構造函數,作為唯一參數的是一個object類型的對象。如果我們指定的參數是一個RouteValueDictionary對象或者是一個元素類型為KeyValuePair<string, object>>的集合,指定的數據將會作為原始數據源。這個特性體現在如下所示的調試斷言中。

   1: var values1 = new RouteValueDictionary() ;
   2: values1.Add("foo", 1);
   3: values1.Add("bar", 2);
   4: values1.Add("baz", 3);
   5:  
   6: var values2 = new RouteValueDictionary(values1);
   7: Debug.Assert(int.Parse(values2["foo"].ToString()) == 1);
   8: Debug.Assert(int.Parse(values2["bar"].ToString()) == 2);
   9: Debug.Assert(int.Parse(values2["baz"].ToString()) == 3);
  10:  
  11: values2 = new RouteValueDictionary(new Dictionary<string, object>
  12: {
  13:     ["foo"] = 1,
  14:     ["bar"] = 2,
  15:     ["baz"] = 3,
  16: });
  17: Debug.Assert(int.Parse(values2["foo"].ToString()) == 1);
  18: Debug.Assert(int.Parse(values2["bar"].ToString()) == 2);
  19: Debug.Assert(int.Parse(values2["baz"].ToString()) == 3);

RouteValueDictionary的這個構造函數的特殊之處其實並不止於此。除了將一個自身具有字典結構的對象作為原始數據源作為參數之外,我們還可以將一個普通的對象作為參數,在此情況下這個構造函數會解析定義在對象自身類型的所有屬性定義,並將屬性名稱和值作為路由參數的名稱和值。如下麵的代碼片段所示,我們創建一個匿名類型的對象並根據它來創建一個RouteValueDictionary,這種方式在MVC應用使用得比較多。

   1: RouteValueDictionary values = new RouteValueDictionary(new 
   2: {
   3:     Foo = 1,
   4:     Bar = 2,
   5:     Baz = 3
   6: });
   7:  
   8: Debug.Assert(int.Parse(values["foo"].ToString()) == 1);
   9: Debug.Assert(int.Parse(values["bar"].ToString()) == 2);
  10: Debug.Assert(int.Parse(values["baz"].ToString()) == 3);

由於RouteData被直接置於RouteContext這上下文中,所以任何可以訪問到這個上下文的對象都可以隨意地修改其中的路由參數,為了全局對象造成的“數據污染”問題,一種類型與“快照”的策略被應用到RouteData上。具體來說,我們為某個RouteData當前的狀態創建一個快照,在後續的某個時刻我們利用這個快照讓這個RouteData對象回覆到當初的狀態。

針對RouteData的這個快照通過具有如下定義的結構RouteDataSnapshot表示。當我們創建這個一個對象的時候,需要指定目標RouteData對象和當前的狀態(Values、DataTokens和Routers)。當我們調用其Restore方法的時候,目標RouteData將會恢復到快照創建時的狀態。我們可以直接調用RouteData的PushState為它自己創建一個快照。

   1: public struct RouteDataSnapshot
   2: {
   3:     public RouteDataSnapshot(RouteData routeData, RouteValueDictionary dataTokens, IList<IRouter> routers, RouteValueDictionary values);
   4:     public void Restore();
   5: }
   6:  
   7: public class RouteData
   8: {
   9:     public RouteDataSnapshot PushState(IRouter router, RouteValueDictionary values, RouteValueDictionary dataTokens);
  10: }

如下麵的代碼片段所示,我們創建了一個RouteData對象並調用其PushState方法為它創建了一個快照,調用該方法指定的三個參數均為null。雖然我們在後續步驟中修改了這個RouteData的狀態,但是一旦我們調用了這個RouteDataSnapshot對象的Restore方法,這個RouteData將重新恢復到最初的狀態。

   1: RouteData routeData = new RouteData();
   2: RouteDataSnapshot snapshot = routeData.PushState(null, null, null);
   3:  
   4: routeData.Values.Add("foo", 1);
   5: routeData.DataTokens.Add("bar", 2);
   6: routeData.Routers.Add(new RouteHandler(null));
   7:  
   8: snapshot.Restore();
   9: Debug.Assert(!routeData.Values.Any());
  10: Debug.Assert(!routeData.DataTokens.Any());
  11: Debug.Assert(!routeData.Routers.Any());

四、Route

除了IRouter這個最為基礎的介面之外,路由系統中還定義了額外一些介面和抽象類,其中就包含如下這個INamedRouter介面。這個介面代表一個“具名的”Router,說白了就是這個Router具有一個通過屬性Name表示的名字。

   1: public interface INamedRouter : IRouter
   2: {
   3:     string Name { get; }
   4: }

所有具體的Route基本上都最終繼承自如下這個抽象基類RouteBase,前面演示實例體現的基於“路由模板”的路由解析策略就體現在這個類型中。如下麵的代碼片段所示,RouterBase實現了INamedRouter介面,所以它具有一個名稱作為標識。它的ParsedTemplate屬性返回的RouteTemplate對象表示這個路由模板,它的Defaults和Constraints則是針對以內聯方式設置的預設值和約束的解析結果。針對內聯約束的解析是利用一個InlineConstraintResolver對象來完成的,RouteBase的ConstraintResolver屬性返回就是這麼一個對象。RouteData的DataTokens來源於Router對象,對應的屬性就是DataTokens。

   1: public abstract class RouteBase : INamedRouter
   2: {
   3:     public virtual string                           Name { get; protected set; }
   4:     public virtual RouteTemplate                    ParsedTemplate { get; protected set; }
   5:     protected virtual IInlineConstraintResolver     ConstraintResolver { get; set; }
   6:     
   7:     public virtual RouteValueDictionary                    DataTokens { get; protected set; }
   8:     public virtual RouteValueDictionary                    Defaults { get; protected set; }   
   9:     public virtual IDictionary<string, IRouteConstraint>   Constraints { get; protected set; } 
  10:  
	   

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

-Advertisement-
Play Games
更多相關文章
  • 1.環境準備 手動添加資料庫依賴: 在package.json的dependencies中新增, “mysql” : “latest”, 使用命令安裝mysql並添加依賴: 2.官方例子: 運行node ...
  • 一:在Dos里切換盤符 a:在電腦左下角右擊顯示圖片;(我用的是win10系統,其他系統類似) b:點擊運行,輸入cmd; c:點擊確定: d:輸入盤符:(如f:) 或F: 只寫字母,不寫分號是不行的!如下圖: cd f:(F:)是不行的如下圖: ...
  • 我在 "Linux字元設備驅動框架" 一文中已經簡單的介紹了字元設備驅動的基本的編程框架,這裡我們來探討一下Linux內核(以4.8.5內核為例)是怎麼管理字元設備的,即當我們獲得了設備號,分配了 cdev 結構,註冊了驅動的操作方法集,最後進行 cdev_add() 的時候,究竟是將哪些內容告訴了 ...
  • snull是《Linux Device Drivers》中的一個網路驅動的例子。這裡引用這個例子學習Linux網路驅動。 因為snull的源碼,網上已經更新到適合最新內核,而我自己用的還是2.6.22.6比較舊的內核。而網上好像找不到舊版的snull。因此結合《Linux Device Driver ...
  • <! 知識的力量是無限的(當然肯定還有更簡單的方法) !> 當我考慮將省市區三級聯動數據從mysql轉入mongodb時遇到了網上無直接插入mongodb的示例(基本均是mysql插入示例)。於是想到利用json文件直接導入mongodb會比較easy(SQLyog如何導出json?) 在SQLyo ...
  • Linux內核大量使用面向對象的設計思想,通過追蹤源碼,我們甚至可以使用面向對象語言常用的UML類圖來分析Linux設備管理的"類"之間的關係。這裡以4.8.5內核為例從kobject,kset,kobj_type的分析入手,進而一探內核對於設備的管理方式 container_of巨集 這個巨集幾乎是l ...
  • 首先設定loop數量(ubuntu系統) 更改以下內容: 然後更新grub 重啟電腦 之後就可以看到loop設備數量變為64個 設定loop數量有此系統不一樣,archlinux可參考我的另一隨筆http://www.cnblogs.com/LTSperl/p/6195493.html。 然後就是今 ...
  • OAuth(開放授權)是一個開放標準,允許用戶讓第三方應用訪問該用戶在某一網站上存儲的私密的資源(如照片,視頻,聯繫人列表),而無需將用戶名和密碼提供給第三方應用。 OAuth 允許用戶提供一個令牌,而不是用戶名和密碼來訪問他們存放在特定服務提供者的數據.每一個令牌授權一個特定的網站(例如,視頻編輯 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...