一個Web應用本質上體現為一組終結點的集合。終結點則體現為一個暴露在網絡中可供外界采用HTTP協議調用的服務,路由的作用就是建立一個請求URL模式與對應終結點之間的映射關系。借助這個映射關系,客戶端可以采用模式匹配的URL來調用對應的終結點。除了利用下圖所示的映射關系對請求進行路由解析,然后選擇並執行與之匹配的終結點,路由系統還可以注冊路由的URL模式和指定的路由參數值生成一個完整的URL。我們將這兩方面的工作稱為兩個路由方向(Routing Direction),前者為入棧路由(Inbound Routing),后者為出棧路由(Outbound Routing)。[更多關於ASP.NET Core的文章請點這里]
對於路由系統來說,作為路由目標的終結點總是關聯一個具體的URL路徑模式,我們將其稱為路由模式(Route Pattern)。表示路由模式的RoutePattern是通過解析路由注冊時提供的路由模板生成的,路由模式的基本組成元素通過抽象類型RoutePatternPart表示。
一、RoutePatternPart
RoutePatternPart在路由模板中主要有兩種類型:一種是靜態文本,另一種是路由參數。例如,包含兩段的路由模板“foo/{bar}”,第一段為靜態文本,第二段為路由參數。由於花括號在路由模板中被用來定義路由參數,如果靜態文本中包含“{”和“}”字符,就需要采用“{{”和“}}”進行轉義。
其實除了上述這兩種基本類型,RoutePatternPart還有第三種類型。例如,如果采用字符串“files/{name}.{ext?}”來表示針對某個文件的路由模板,文件名({name})和擴展名(ext?)體現為路由參數,而它們之間的“.”就是RoutePattern的第三種展現形式,被稱為分隔符。路由系統對於分隔符具有特殊的匹配邏輯:如果分隔符后面跟的是一個可以默認的路由參數,請求地址在沒有提供該參數值的情況下,分隔符是可以默認的。對於“files/{name}.{ext?}”這個路由模板來說,擴展名是可以默認的,如果請求地址沒有提供擴展名,請求路徑只需要提供文件名(如/files/foobar)即可。RoutePatternPart的3種類型通過RoutePatternPartKind枚舉表示。
public enum RoutePatternPartKind { Literal, Parameter, Separator }
如下所示的代碼片段是RoutePatternPart的定義,可以看出這是一個抽象類。除了定義表示類型的PartKind只讀屬性,RoutePatternPart還有3個布爾類型的屬性(IsLiteral、IsParameter和IsSeparator),它們表示當前是否屬於對應的類型。
public abstract class RoutePatternPart { public RoutePatternPartKind PartKind { get; } public bool IsLiteral { get; } public bool IsParameter { get; } public bool IsSeparator { get; } }
針對RoutePatternPartKind枚舉體現的3種類型,路由系統提供3個針對RoutePatternPart的派生類,如下所示的代碼片段是針對靜態文本和分隔符的RoutePatternLiteralPart與RoutePattern
SeparatorPart類型的定義,它們具有表示具體內容(靜態文本內容和分隔符)的Content屬性。
public sealed class RoutePatternLiteralPart : RoutePatternPart { public string Content { get; } } public sealed class RoutePatternSeparatorPart : RoutePatternPart { public string Content { get; } }
由於路由參數在路由模板中有多種定義形式,所以對應的RoutePatternParameterPart類型的成員會多一些。RoutePatternParameterPart的Name屬性和ParameterKind屬性表示路由參數的名稱與類型。路由參數類型包括標准形式(如{foobar})、默認形式(如{foobar?}或者{foobar?=123})及通配符形式(如{*foobar}或者{**foobar})。路由參數的這3種定義形式通過RoutePatternParameterKind枚舉表示。
public sealed class RoutePatternParameterPart : RoutePatternPart { public string Name { get; } public RoutePatternParameterKind ParameterKind { get; } public bool IsOptional { get; } public object Default { get; } public bool IsCatchAll { get; } public bool EncodeSlashes { get; } public IReadOnlyList<RoutePatternParameterPolicyReference> ParameterPolicies { get; } } public enum RoutePatternParameterKind { Standard, Optional, CatchAll }
對於默認形式或者通配符形式對應的路由參數,對應RoutePatternParameterPart對象的IsOptional屬性和IsCatchAll屬性會返回True。如果為參數定義了默認值,該值體現在Default屬性上。對於兩種通配符形式定義的路由參數,針對請求URL的解析來說並沒有什么不同,它們之間的差異體現在路由系統根據它生成對應URL的時候。具體來說,對於提供的包含分隔符“/”的參數值(如foo/bar),如果對應的路由參數采用{*variable}的方式,URL格式化過程中會對分隔符進行編碼(foo%2bar),倘若路由參數采用{**variable}的形式定義,提供的字符串將不做任何改變。RoutePatternParameterPart的EncodeSlashes屬性表示是否需要對路徑分隔符“/”進行編碼。
我們在定義路由參數時可以指定約束條件,路由系統將約束視為一種參數策略(Parameter Policy)。路由參數策略通過一個標記接口(不具有任何成員的接口)IParameterPolicy表示路由參數策略,如下所示的RoutePatternParameterPolicyReference是對IParameterPolicy對象的進一步封裝,它定義的Content屬性表示策略的原始(字符串)表現形式。應用在路由參數上的策略定義體現在RoutePatternParameterPart的ParameterPolicies屬性上。
public sealed class RoutePatternParameterPolicyReference { public string Content { get; } public IParameterPolicy ParameterPolicy { get; } } public interface IParameterPolicy { }
二、RoutePattern
在了解了作為路由模式的基本組成元素RoutePatternPart之后,下面介紹表示路由模式的RoutePattern如何定義。表示路由模式的RoutePattern對象是通過解析路由模板生成的,以字符串形式表示的路由模板體現為它的RawText屬性。
public sealed class RoutePattern { public string RawText { get; } public IReadOnlyList<RoutePatternPathSegment> PathSegments { get; } public IReadOnlyList<RoutePatternParameterPart> Parameters { get; } public IReadOnlyDictionary<string, object> Defaults { get; } public IReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>> ParameterPolicies { get; } public decimal InboundPrecedence { get; } public decimal OutboundPrecedence { get; } public IReadOnlyDictionary<string, object> RequiredValues { get; } public RoutePatternParameterPart GetParameter(string name); }
URL的路徑采用字符“/”作為分隔符,我們將分隔符內的內容稱為段,路由模式下針對路徑段的表示體現在如下所示的RoutePatternPathSegment類型上。RoutePatternPathSegment類型的Parts屬性返回一個RoutePatternPart對象的集合,表示構成該路徑段的基本元素。如果RoutePatternPathSegment的Parts集合只包含一個元素(一般為靜態文本或者路由參數),那么它被視為一個簡短的路徑段,其IsSimple屬性會返回True。
public sealed class RoutePatternPathSegment { public IReadOnlyList<RoutePatternPart>Parts { get; } public bool IsSimple { get; } }
路由參數是路由模式的一個重要組成部分,RoutePattern的Parameters屬性返回的RoutePatternParameterPart列表是對所有路由參數的描述。路由參數的默認值會存放在Defaults屬性表示的字典中,該字典對象的Key為路由參數的名稱。RoutePattern的ParameterPolicies屬性同樣返回一個字典對象,針對每個路由參數的參數策略被存放到該字典中。借助RoutePattern類型的GetParameter方法,我們可以通過指定路由參數的名稱得到對應的RoutePatternParameterPart對象。
應用具有一個全局的路由表,其中包含若干注冊的通過RoutePattern表示的路由模式,無論是入棧方向上針對請求URL的路由解析,還是出棧方向上生成完整的URL,都需要從這個路由表中選擇一個匹配的模式。如果注冊的路由很多,就可能出現多個路由在模式上都與當前上下文匹配的情況,在這種狀況下就需要為注冊的路由模式指定不同的匹配的權重或者優先選擇一個匹配度最高的路由模式,RoutePattern類型的InboundPrecedence屬性和OutboundPrecedence屬性分別代表當前路由模式針對兩個路由方向上的匹配優先級,數值越大表示匹配度越高。
RoutePattern類型的RequiredValues屬性與出棧URL的生成相關。“weather/{city=010}/{days=4}”是本章開篇實例演示中定義的一個路由模板,如果根據指定的路由參數值(city=010,days=4)生成一個完整的URL,由於提供的路由參數值為默認值,所以生成的如下所示的3個URL路徑都是合法的。具體生成哪一種由RequiredValues屬性來決定,該屬性返回的字典中存放了生成URL時必須指定的路由參數默認值。
- weather。
- weather/010。
- weather/010/4。
三、RoutePatternFactory
靜態類型RoutePatternFactory提供的一系列靜態方法可以幫助我們根據路由模板字符串創建表示路由模式的RoutePattern對象。如下所示的3個靜態Parse方法重載幫助我們根據指定的路由模板和其他相關數據,包括路由參數的默認值和參數策略,以及必需的路由參數值(對應RoutePattern的RequiredValues屬性),生成了一個表示路由模式的RoutePattern對象。
public static class RoutePatternFactory { public static RoutePattern Parse(string pattern); public static RoutePattern Parse(string pattern, object defaults, object parameterPolicies); public static RoutePattern Parse(string pattern, object defaults, object parameterPolicies, object requiredValues); ... }
下面通過一個簡單的實例演示如何利用RoutePatternFactory對象解析指定的路由模板,並生成一個表示路由模式的RoutePattern對象。我們在一個ASP.NET Core應用程序中定義了如下所示的Format方法,該方法將指定的RoutePattern對象格式化成一個字符串。
public class Program { private static string Format(RoutePattern pattern) { var builder = new StringBuilder(); builder.AppendLine($"RawText:{pattern.RawText}"); builder.AppendLine($"InboundPrecedence:{pattern.InboundPrecedence}"); builder.AppendLine($"OutboundPrecedence:{pattern.OutboundPrecedence}"); var segments = pattern.PathSegments; builder.AppendLine("Segments"); foreach (var segment in segments) { foreach (var part in segment.Parts) { builder.AppendLine($"\t{ToString(part)}"); } } builder.AppendLine("Defaults"); foreach (var @default in pattern.Defaults) { builder.AppendLine($"\t{@default.Key} = {@default.Value}"); } builder.AppendLine("ParameterPolicies "); foreach (var policy in pattern.ParameterPolicies) { builder.AppendLine($"\t{policy.Key} = {string.Join(',', policy.Value.Select(it => it.Content))}"); } builder.AppendLine("RequiredValues"); foreach (var required in pattern.RequiredValues) { builder.AppendLine($"\t{required.Key} = {required.Value}"); } return builder.ToString(); static string ToString(RoutePatternPart part) { if (part is RoutePatternLiteralPart literal) { return $"Literal: {literal.Content}"; } if (part is RoutePatternSeparatorPart separator) { return $"Separator: {separator.Content}"; } else { var parameter = (RoutePatternParameterPart)part; return $"Parameter: Name = {parameter.Name}; Default = {parameter.Default}; IsOptional = {parameter.IsOptional}; IsCatchAll = {parameter.IsCatchAll}; ParameterKind = {parameter.ParameterKind}"; } } } }
在如下所示的應用承載程序中,我們調用RoutePatternFactory 類型的靜態方法Parse解析指定的路由模板“weather/{city:regex(^0\d{{2,3}}$)=010}/{days:int:range(1,4)=4}/{detailed?}”,並生成一個RoutePattern對象,該方法調用中還指定了requiredValues參數的值。我們調用IApplicationBuilder對象的Run方法注冊了唯一的中間件,它會調用上面定義的Format方法將生成的RoutePattern對象格式化成字符串,並作為最終的響應內容。
public class Program { public static void Main() { var template = @"weather/{city:regex(^0\d{{2,3}}$)=010}/{days:int:range(1,4)=4}/{detailed?}"; var pattern = RoutePatternFactory.Parse( pattern: template, defaults: null, parameterPolicies: null, requiredValues: new { city = "010", days = 4 }); Host.CreateDefaultBuilder() .ConfigureWebHostDefaults(builder => builder.Configure(app => app.Run(context => context.Response.WriteAsync(Format(pattern))))) .Build() .Run(); } }
如果利用瀏覽器訪問啟動后的應用程序,得到的輸出結果如下圖所示,該結果結構化地展示了路由模式的原始文本、出入棧路由匹配權重、每個段的組成、路由參數的默認值和參數策略,以及生成URL必須提供的默認參數值。
除了提供Parse方法解析指定的路由模板並生成表示路由模式的RoutePattern對象,RoutePatternFactory還提供了用於解析其他與路由模式相關對象的靜態方法,這些對象包括表示路徑段的RoutePatternPathSegment對象、針對路由參數的RoutePatternParameterPart對象、針對參數策略的RoutePatternParameterPolicyReference對象等。由於篇幅有限,此處不再一一列舉。
ASP.NET Core路由中間件[1]: 終結點與URL的映射
ASP.NET Core路由中間件[2]: 路由模式
ASP.NET Core路由中間件[3]: 終結點
ASP.NET Core路由中間件[4]: EndpointRoutingMiddleware和EndpointMiddleware
ASP.NET Core路由中間件[5]: 路由約束