雖然ASP.NET Web API框架采用與ASP.NET MVC框架類似的管道式設(shè)計(jì),但是ASP.NET Web API管道的核心部分(定義在程序集System.Web.Http.dll中)已經(jīng)移除了對(duì)System.Web.dll程序集的依賴,實(shí)現(xiàn)在ASP.NET Web API框架中的URL路由系統(tǒng)亦是如此。也就是說,ASP.NET Web API核心框架的URL路由系統(tǒng)與ASP.NET本身的路由系統(tǒng)是相對(duì)獨(dú)立的。但是當(dāng)我們采用基于Web Host的方式(定義在程序集System.Web.Http.WebHost.dll)將ASP.NET Web API承載于一個(gè)ASP.NET Web應(yīng)用的時(shí)候,真正實(shí)現(xiàn)URL路由的依然是ASP.NET本身的路由系統(tǒng),Web Host實(shí)際上在這種情況下起到了一個(gè)“適配”的作用,是兩個(gè)相對(duì)獨(dú)立的路由系統(tǒng)的“適配器”。我們先來討論一下實(shí)現(xiàn)在ASP.NET Web API框架中這個(gè)獨(dú)立的路由系統(tǒng)是如何設(shè)計(jì)的。[本文已經(jīng)同步到《How ASP.NET Web API Works?》]
一、HttpRequestMessage與HttpResponseMessageASP.NET Web API框架通過具有如下定義的類型HttpRequestMessage表示某個(gè)HTTP請(qǐng)求的封裝。HttpRequestMessage的屬性Method和RequestUri分別表示請(qǐng)求采用的HTTP方法和請(qǐng)求地址,它們可以在相應(yīng)的構(gòu)造函數(shù)中直接被初始化,而默認(rèn)采用的HTTP方法為HTTP-GET。 1: public class HttpRequestMessage : IDisposable 2: {
3: public HttpRequestMessage(); 4: public HttpRequestMessage(HttpMethod method, string requestUri); 5: public HttpRequestMessage(HttpMethod method, Uri requestUri); 6:
7: public HttpMethod Method { get; set; } 8: public Uri RequestUri { get; set; } 9: public HttpRequestHeaders Headers { get; } 10: public IDictionary<string, object> Properties { get; } 11: public Version Version { get; set; } 12: public HttpContent Content { get; set; } 13:
14: public void Dispose(); 15: }
只讀屬性Headers表示的System.Net.Http.Headers.HttpRequestHeaders對(duì)象具有一個(gè)類似于字典的數(shù)據(jù)結(jié)構(gòu),用于存放HTTP請(qǐng)求報(bào)頭。通過利用字典類型的只讀屬性Properties,我們可以將任意屬性附加到一個(gè)HttpRequestMessage對(duì)象上。類型為System.Version的Version屬性表示請(qǐng)求的HTTP版本,默認(rèn)采用的HTTP版本為HTTP 1.1(HttpVersion.Version11)。 HttpRequestMessage具有一個(gè)Content屬性封裝了HTTP消息主體相關(guān)的信息,其類型為HttpContent。如下面的代碼片斷所示,HttpContent是一個(gè)抽象類,它定義了CopyToAsync和ReadAsByteArrayAsync兩組方法進(jìn)行主體內(nèi)容的讀寫操作。HttpContent的Headers屬性返回一個(gè)System.Net.Http.Headers.HttpContentHeaders對(duì)象代表HTTP消息主體內(nèi)容相關(guān)的報(bào)頭列表,比如表示主題內(nèi)容編碼和長度的“Content-Encoding”和“Content-Length”等。 1: public abstract class HttpContent : IDisposable 2: {
3: //其他成員 4: public Task<byte[]> ReadAsByteArrayAsync(); 5: public Task<Stream> ReadAsStreamAsync(); 6: public Task<string> ReadAsStringAsync(); 7:
8: public Task CopyToAsync(Stream stream); 9: public Task CopyToAsync(Stream stream, TransportContext context); 10:
11: public HttpContentHeaders Headers { get; } 12: }
HTTP響應(yīng)的基本信息本封裝到具有如下定義的HttpResponseMessage類型中。它的RequestMessage表示與之匹配的請(qǐng)求。屬性StatusCode和表示響應(yīng)狀態(tài)碼以及輔助表示響應(yīng)狀態(tài)的文字。布爾類型的屬性IsSuccessStatusCode用于判斷是否屬性一個(gè)成功的響應(yīng),所謂“成功的響應(yīng)”指的是狀態(tài)碼在范圍[200,299]以內(nèi)的響應(yīng)。類型為HttpResponseHeaders的屬性Headers表示回復(fù)消息的HTTP報(bào)頭列表,而Version代表HTTP消息的版本,默認(rèn)采用的HTTP版本依然是HTTP 1.1(HttpVersion.Version11)。響應(yīng)消息主體內(nèi)容的讀取和寫入,以及相關(guān)內(nèi)容報(bào)頭的獲取可以通過屬性Content表示的HttpContent來完成。 1: public class HttpResponseMessage : IDisposable 2: {
3: //其他成員 4: public HttpRequestMessage RequestMessage { get; set; } 5:
6: public HttpStatusCode StatusCode { get; set; } 7: public string ReasonPhrase { get; set; } 8: public bool IsSuccessStatusCode { get; } 9: public HttpResponseHeaders Headers { get; } 10: public Version Version { get; set; } 11: public HttpContent Content { get; set; } 12: }
二、HttpRouteData當(dāng)我們調(diào)用某個(gè)Route的GetRouteData的時(shí)候,如果指定的HTTP上下文具有一個(gè)與自身URL模板相匹配,同時(shí)滿足定義的所有約束條件的情況下會(huì)返回一個(gè)RouteData對(duì)象。ASP.NET的路由系統(tǒng)通過RouteData對(duì)象來封裝解析出來的路由數(shù)據(jù),其核心自然是通過Values和DataTokens屬性封裝的路由變量。 ASP.NET Web API用于封裝路由數(shù)據(jù)的對(duì)象被稱為HttpRouteData,其類型實(shí)現(xiàn)了具有如下定義的接口IHttpRouteData。IHttpRouteData接口的定義可比RouteData要簡單很多,它只有兩個(gè)只讀的屬性。Route屬性表示生成該HttpRouteData的Route,而字典類型的屬性Values表示解析出來的路由變量,變量名和變量值對(duì)應(yīng)著該字典對(duì)象的Key和Value。 1: public interface IHttpRouteData 2: {
3: IHttpRoute Route { get; }
4: IDictionary<string, object> Values { get; } 5: }
在ASP.NET Web API路由系統(tǒng)中唯一實(shí)現(xiàn)了IHttpRouteData接口的公有類型為HttpRouteData,具體的定義如下所示。HttpRouteData實(shí)現(xiàn)的兩個(gè)只讀屬性直接在構(gòu)造函數(shù)中初始化,用于初始化Values屬性的參數(shù)values的類型為HttpRouteValueDictionary,通過如下的代碼片斷可以看到它直接繼承了Dictionary<string, object>,也就是說HttpRouteData對(duì)象具體返回的是一個(gè)類型為HttpRouteValueDictionary的對(duì)象。如果調(diào)用另一個(gè)構(gòu)造函數(shù)(只包含一個(gè)唯一的參數(shù)route),其Values屬性會(huì)初始化成一個(gè)不包含任何元素的空HttpRouteValueDictionary對(duì)象。 1: public class HttpRouteData : IHttpRouteData 2: {
3: public HttpRouteData(IHttpRoute route); 4: public HttpRouteData(IHttpRoute route, HttpRouteValueDictionary values); 5:
6: public IHttpRoute Route { get; } 7: public IDictionary<string, object> Values { get; } 8: }
9:
10: public class HttpRouteValueDictionary : Dictionary<string, object> 11: {
12: public HttpRouteValueDictionary(); 13: public HttpRouteValueDictionary(IDictionary<string, object> dictionary); 14: public HttpRouteValueDictionary(object values); 15: }
三、HttpVirtualPathData在ASP.NET 路由系統(tǒng)中,當(dāng)我們調(diào)用Route的GetVirtualPath方法根據(jù)定義在路由本身的URL模板和指定的路由變量生成一個(gè)完整的URL的時(shí)候,在URL模板與提供的路由變量相匹配的情況下會(huì)返回一個(gè)VirtualPathData對(duì)象,我們可以通過其VirtualPath屬性得到生成的URL。 在ASP.NET Web API路由系統(tǒng)中與VirtualPathData對(duì)應(yīng)的對(duì)象被稱為HttpVirtualPathData,它實(shí)現(xiàn)了具有如下定義的接口IHttpVirtualPathData。對(duì)于定義在IHttpVirtualPathData接口中的兩個(gè)屬性,只讀屬性自然返回的是生成該HttpVirtualPathData對(duì)象的Route,另一個(gè)屬性VirtualPath(改屬性是可讀可寫的)返回生成的URL字符串。 1: public interface IHttpVirtualPathData 2: {
3: IHttpRoute Route { get; }
4: string VirtualPath { get; set; } 5: }
在ASP.NET Web API的應(yīng)用編程接口中定義了如下一個(gè)類型HttpVirtualPathData,它是實(shí)現(xiàn)了接口IHttpVirtualPathData的唯一公有類型。 1: public class HttpVirtualPathData : IHttpVirtualPathData 2: {
3: public HttpVirtualPathData(IHttpRoute route, string virtualPath); 4:
5: public IHttpRoute Route { get; } 6: public string VirtualPath { get; set; } 7: }
四、HttpRouteConstraint一個(gè)Route能夠與HTTP請(qǐng)求相匹配,必須同時(shí)滿足兩個(gè)條件:其一,請(qǐng)求的URL必須與Route自身的URL的模式相匹配;其二,當(dāng)前請(qǐng)求必須通過定義在當(dāng)前Route上的所有約束。ASP.NET Web API路由系統(tǒng)通過HttpRouteContraint表示路由約束,具體類型實(shí)現(xiàn)了具有如下定義的接口IHttpRouteConstraint。 1: public interface IHttpRouteConstraint 2: {
3: bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection); 4: }
HTTP請(qǐng)求是否滿足HttpRouteContraint的約束通過調(diào)用定義在IHttpRouteConstraint的唯一的方法Match來決定,在這里被驗(yàn)證的請(qǐng)求(參數(shù)request)通過HttpRequestMessage對(duì)象來表示。 參數(shù)route代表當(dāng)前HttpRouteContraint所在的Route。 基于HttpRouteContraint的約束是針對(duì)某個(gè)路由變量的,參數(shù)parameterName實(shí)際上代表的就是變量的名稱。由于大部分路由變量會(huì)映射為定義在HttpController中某個(gè)Action方法的參數(shù),所以這里的參數(shù)名為parameterName。當(dāng)ASP.NET Web API框架實(shí)施約束檢驗(yàn)的時(shí)候,已經(jīng)通過URL模板匹配得到了所有的路由變量值,參數(shù)values表示的字典對(duì)象存放了這些路由變量,其Key和Value分別代表路由變量的名稱和值。 通過對(duì)ASP.NET 路由系統(tǒng)的介紹我們知道URL路由具有兩個(gè)“方向”上的應(yīng)用,分別是匹配“入?!闭?qǐng)求并得到相應(yīng)的路由數(shù)據(jù),以及根據(jù)定義的路由規(guī)則和提供的路由變量生成“出?!盪RL。ASP.NET路由系統(tǒng)通過枚舉RouteDirection表示這兩種“路由方向”,而ASP.NET Web API路由系統(tǒng)中的“路由方向”則通過具有如下定義的HttpRouteDirection枚舉來表示。Match方法的參數(shù)routeDirection就是這么一個(gè)枚舉對(duì)象。 1: public enum HttpRouteDirection 2: {
3: UriResolution,
4: UriGeneration
5: }
我們知道HTTP方法在面向資源的REST架構(gòu)中具有重要的地位和作用,它體現(xiàn)了針對(duì)目標(biāo)資源的操作類型,很多情況下我們?cè)谶M(jìn)行路由注冊(cè)過程中指定的URL模板都是針對(duì)具體某一種或幾種HTTP方法的。ASP.NET路由系統(tǒng)定義了一個(gè)HttpMethodConstraint類型是實(shí)現(xiàn)針對(duì)HTTP方法的約束,ASP.NET Web API的路由系統(tǒng)中則定義了如下一個(gè)同名類型實(shí)現(xiàn)類似的功能。 1: public class HttpMethodConstraint : IHttpRouteConstraint 2: {
3: public HttpMethodConstraint(params HttpMethod[] allowedMethods); 4:
5: protected virtual bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection); 6: bool IHttpRouteConstraint.Match(HttpRequestMessage request, IHttpRoute route, 7: string parameterName, IDictionary<string, object> values, 8: HttpRouteDirection routeDirection);
9:
10: public Collection<HttpMethod> AllowedMethods { get; } 11: }
HttpMethodConstraint的只讀屬性AllowedMethods返回一個(gè)元素類型為HttpMethod的集合,它代表了允許的HTTP方法列表。Match方法從表示請(qǐng)求的HttpRequestMessage對(duì)象中獲得當(dāng)前的HTTP方法,根據(jù)它是否在允許的列表之內(nèi)從而做出是否滿足約束的最終判斷。 除了HttpMethodConstraint,在ASP.NET Web API路由系統(tǒng)的應(yīng)用編程接口中還定義了一系列的約束類型,比如用于驗(yàn)證數(shù)據(jù)類型的IntRouteConstraint、FloatRouteConstraint和BoolRouteConstraint等,用于驗(yàn)證字符串長度的LengthRouteConstraint、MinLengthRouteConstraint和MaxLengthRouteConstraint等。這一系列的HttpMethodConstraint類型其實(shí)是為基于特性(Attribute)的路由而設(shè)計(jì)的,但是由于它們實(shí)現(xiàn)了IHttpRouteConstraint接口,所以在這里它們依然是可用的。 五、HttpRouteASP.NET路由系統(tǒng)中的Route的類型均為RouteBase的子類。從前面針對(duì)HttpRouteData和HttpVirtualPathDatad的介紹,我們知道ASP.NET Web API路由系統(tǒng)的RouteHttpRoute的類型實(shí)現(xiàn)了接口IHttpRoute,其定義如下。IHttpRoute的只讀屬性RouteTemplate表示定義的URL模板。兩個(gè)字典類型的只讀屬性Constraints和Defaults表示為路由變量定義的約束和默認(rèn)值,字典的Key和Value分別表示變量名稱和約束/默認(rèn)值。另一個(gè)同樣通過字典類型表示的只讀屬性DataTokens,我們應(yīng)該不會(huì)感到陌生,至于通過制度屬性Handler返回的HttpMessageHandler對(duì)象是組成ASP.NET Web API消息處理管道的核心,我們會(huì)在后續(xù)的文章中對(duì)它進(jìn)行詳細(xì)介紹。 1: public interface IHttpRoute 2: {
3: string RouteTemplate { get; } 4: IDictionary<string, object> Constraints { get; } 5: IDictionary<string, object> Defaults { get; } 6: IDictionary<string, object> DataTokens { get; } 7: HttpMessageHandler Handler { get; }
8:
9: IHttpRouteData GetRouteData(string virtualPathRoot, HttpRequestMessage request); 10: IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, IDictionary<string, object> values); 11: }
HttpRoute的作用體現(xiàn)在兩點(diǎn):對(duì)請(qǐng)求的URL進(jìn)行解析并生成封裝路由數(shù)據(jù)的HttpRouteData對(duì)象,以及將提供的路由變量綁定到URL模板以生成一個(gè)完整的URL,這兩個(gè)功能分別通過調(diào)用IHttpRoute的方法GetRouteData和GetVirtualPath來實(shí)現(xiàn)。GetRouteData方法的參數(shù)virtualPathRoot表示虛擬根路徑,一般來說當(dāng)通過HttpRequestMessage獲取的真正請(qǐng)求路徑后需要剔除這個(gè)根路徑部分得到一個(gè)相對(duì)路徑,基于URL模板的匹配應(yīng)該針對(duì)這個(gè)相對(duì)路徑來進(jìn)行。 ASP.NET Web API路由系統(tǒng)中直接實(shí)現(xiàn)了接口IHttpRoute的唯一類型是具有如下定義的HttpRoute。HttpRoute實(shí)現(xiàn)的5個(gè)只讀屬性都可以直接通過調(diào)用相應(yīng)的構(gòu)造函數(shù)進(jìn)行初始化,對(duì)于3個(gè)字典類型的屬性(Constraints、DataTokens和Defaults),如果不曾在構(gòu)造函數(shù)中通過對(duì)應(yīng)的參數(shù)來指定(或者指定的對(duì)象為Null),它們會(huì)被初始化為一個(gè)空的HttpRouteValueDictionary對(duì)象。 1: public class HttpRoute : IHttpRoute 2: {
3: public HttpRoute(); 4: public HttpRoute(string routeTemplate); 5: public HttpRoute(string routeTemplate, HttpRouteValueDictionary defaults); 6: public HttpRoute(string routeTemplate, HttpRouteValueDictionary defaults, HttpRouteValueDictionary constraints); 7: public HttpRoute(string routeTemplate, HttpRouteValueDictionary defaults, HttpRouteValueDictionary constraints, HttpRouteValueDictionary dataTokens); 8: public HttpRoute(string routeTemplate, HttpRouteValueDictionary defaults, HttpRouteValueDictionary constraints, HttpRouteValueDictionary dataTokens, HttpMessageHandler handler); 9:
10: public virtual IHttpRouteData GetRouteData(string virtualPathRoot, HttpRequestMessage request); 11: public virtual IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, IDictionary<string, object> values); 12:
13: public IDictionary<string, object> Constraints { get; } 14: public IDictionary<string, object> DataTokens { get; } 15: public IDictionary<string, object> Defaults { get; } 16: public HttpMessageHandler Handler { get; } 17: public string RouteTemplate { get; } 18: }
現(xiàn)在我們來簡單討論一下實(shí)現(xiàn)在GetRouteData方法中的路由匹配規(guī)則以及最終路由數(shù)據(jù)如何生成。HttpRoute首先根據(jù)表示請(qǐng)求的HttpRequestMessage對(duì)象的RequestUri屬性得到請(qǐng)求地址,一般來說這是一個(gè)包含網(wǎng)絡(luò)協(xié)議前綴(http://或者h(yuǎn)ttps://)和主機(jī)名稱的完整URL。然后HttpRoute會(huì)從該URL中提取路徑部分,并加上“/”前綴。比如說請(qǐng)求地址為“http://www./webapi/products/001”,最終得到的相對(duì)URL為“/webapi/products/001”。 如果GetRouteData方法中通過virtualPathRoot指定了一個(gè)根路徑,如果這個(gè)路徑不是上面得到的相對(duì)URL的前綴(比如“/webservice”),那么匹配失敗并直接返回Null。HttpRoute會(huì)從這個(gè)相對(duì)URL中將這個(gè)根路徑部分剔除掉,最終得到的URL與自身定義的URL模板進(jìn)行模式匹配。比如說,如果指定的根路徑為“/webapi”,那么最終與URL模板進(jìn)行匹配的相對(duì)URL為“products/001”。如果請(qǐng)求URL不符合URL模板的模式,HttpRoute會(huì)直接返回Null。 基于URL模板的模式匹配成功之后,解析出來的路由變量會(huì)保存到一個(gè)字典對(duì)象中。HttpRoute接下來需要檢驗(yàn)通過URL模板驗(yàn)證的請(qǐng)求是否滿足自身定義的所有約束。從上面給出的關(guān)于接口IHttpRoute的定義我們知道表示針對(duì)路由變量約束的列表的屬性Constraints不是IDictionary<string, IHttpRouteConstraint>,而是IDictionary<string, object>。字典對(duì)象的Key代表路由變量的名稱,其Value可以是一個(gè)真正的HttpRouteContraint對(duì)象,也可以是針對(duì)某種類型HttpRouteContraint的字符串表達(dá)式。 如果保存在Constraints屬性中的一個(gè)真正的HttpRouteContraint,HttpRoute會(huì)直接調(diào)用它的Match方法對(duì)請(qǐng)求進(jìn)行相應(yīng)的約束檢驗(yàn),作為參數(shù)parameterName和values傳入的分別是對(duì)應(yīng)的Key和通過URL模板匹配解析出來的路由變量。如果保存在Constraints中的是針對(duì)某種HttpRouteContraint類型的字符串表達(dá)式,HttpRoute會(huì)據(jù)此創(chuàng)建對(duì)應(yīng)的HttpRouteContraint對(duì)象對(duì)請(qǐng)求予以驗(yàn)證。由于這些HttpRouteContraint主要是針對(duì)“特性路由”而設(shè)計(jì)的,對(duì)于每個(gè)HttpRouteContraint的表達(dá)式各自具有怎樣的格式,我們會(huì)在本書第3章“基于標(biāo)注特性的路由”中進(jìn)行詳細(xì)介紹。 如果指定的表示請(qǐng)求的HttpRequestMessage通過了所有HttpRouteContraint的檢驗(yàn),HttpRoute會(huì)根據(jù)解析出來的以字典形式表示的路由變量生成一個(gè)HttpRouteData并作為GetRouteData的返回值,該HttpRouteData對(duì)象的Route屬性就是對(duì)它自身的引用。 我們可以通過一個(gè)簡單的實(shí)例來演示HttpRoute對(duì)請(qǐng)求的路由匹配與檢驗(yàn)規(guī)則。我們?cè)谝粋€(gè)空的ASP.NET MVC應(yīng)用中定義了如下一個(gè)HomeController。在默認(rèn)的Action方法中我們創(chuàng)建了一個(gè)HttpRoute對(duì)象,它的URL模板為“movies/{genre}/{title}/{id}”(針對(duì)某一個(gè)電影,定義其中的三個(gè)變量分別表示電影的類型、片名和ID),而HTTP方法被設(shè)置為HTTP-POST。我們?yōu)榇薍ttpRoute添加了一個(gè)HttpMethodConstraint類型的約束,并將允許的HTTP方法限定為HTTP-POST。 1: public class HomeController : Controller 2: {
3: public ActionResult Index() 4: {
5: string routeTemplate = "movies/{genre}/{title}/{id}"; 6: IHttpRoute route = new HttpRoute(routeTemplate); 7: route.Constraints.Add("httpMethod", new HttpMethodConstraint(HttpMethod.Post)); 8:
9: HttpRequestMessage request1 = new HttpRequestMessage(HttpMethod.Get, "http://www./products/movies/romance/titanic/r001"); 10: HttpRequestMessage request2 = new HttpRequestMessage(HttpMethod.Post, "http://www./products/movies/romance/titanic/r001"); 11:
12: string virtualPathRoot1 = "/"; 13: string virtualPathRoot2 = "/products/"; 14:
15: IHttpRouteData routeData1 = route.GetRouteData(virtualPathRoot1, request1);
16: IHttpRouteData routeData2 = route.GetRouteData(virtualPathRoot1, request2);
17: IHttpRouteData routeData3 = route.GetRouteData(virtualPathRoot2, request1);
18: IHttpRouteData routeData4 = route.GetRouteData(virtualPathRoot2, request2);
19:
20: return View(new bool[] { routeData1 != null, routeData2 != null, routeData3 != null, routeData4 != null }); 21: }
22: }
我們創(chuàng)建了兩個(gè)HttpRequestMessage對(duì)象作為被檢驗(yàn)的HTTP請(qǐng)求,它們具有相同的請(qǐng)求地址(“http://www./products/movies/romance/titanic/r001”)不同的HTTP方法(HTTP-GET和HTTP-POST)。為了驗(yàn)證指定不同的虛擬根路徑對(duì)HttpRoute路由解析的影響,我們分別定義了兩個(gè)不同的根路徑(“/”和“/products/”)。針對(duì)兩個(gè)不同的請(qǐng)求和根路徑的組合,我們4次調(diào)用了HttpRoute的GetRouteData方法,通過判斷返回的HttpRouteData是否為Null來判斷對(duì)應(yīng)的請(qǐng)求針對(duì)給定的根路徑是否與定義在HttpRoute中的路由規(guī)則相匹配。 Action方法Index最終將默認(rèn)的View呈現(xiàn)出來,指定的Model是一個(gè)布爾類型元素的數(shù)組,每個(gè)一個(gè)布爾值代表對(duì)應(yīng)的請(qǐng)求與根路徑組合是否通過了HttpRoute的檢驗(yàn)。如下所示的就是對(duì)應(yīng)View的定義,這是一個(gè)Model類型為bool[]的強(qiáng)類型View,我們將代表檢驗(yàn)結(jié)果的布爾值以表格的形式呈現(xiàn)出來。 1: @model bool[]
2: <html> 3: <head> 4: <title>路由解析</title> 5: </head> 6: <body> 7: <table> 8: <tr> 9: <th></th> 10: <th>HTTP-GET</th> 11: <th>HTTP-POST</th> 12: </tr> 13: <tr> 14: <td>/</td> 15: <td>@Model[0]</td> 16: <td>@Model[1]</td> 17: </tr> 18: <tr> 19: <td>/products/</td> 20: <td>@Model[2]</td> 21: <td>@Model[3]</td> 22: </tr> 23: </table> 24: </body> 25: </html> 直接運(yùn)行該程序后會(huì)在瀏覽器中呈現(xiàn)出如右圖所示的輸出結(jié)果,針對(duì)兩個(gè)基于不同HTTP方法的請(qǐng)求和兩個(gè)不同虛擬根路徑的組合,只有最后一組能夠完全符合定義在HttpRoute中的路由規(guī)則,由此可以看出上面我們介紹的URL模板、約束以及指定的虛擬根路徑對(duì)HttpRoute路由解析的影響。 HttpRoute的GetRouteData方法解決了針對(duì)“入棧”請(qǐng)求的檢驗(yàn),接下來我們來討論HttpRoute在另一個(gè)“路由方向”上的應(yīng)用,即根據(jù)定義的路由規(guī)則和給定的路由變量生成一個(gè)完整的URL。針對(duì)生成URL的路由解析實(shí)現(xiàn)在GetVirtualPath方法中,我們現(xiàn)在來詳細(xì)介紹用于封裝生成URL的HttpVirtualPathData是如何生成出來的。 1: public class HttpRoute : IHttpRoute 2: {
3: //其他成員 4: public virtual IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, IDictionary<string, object> values); 5: }
如上面的代碼片斷所示,HttpRoute的GetVirtualPath方法具有兩個(gè)參數(shù),分別是表示請(qǐng)求的HttpRequestMessage對(duì)象和用于替換掉定義在URL模板中路由變量占位符的“值”。HttpRoute能夠根據(jù)模板生成一個(gè)完整的URL取決于是否能夠提供定義在URL模板中所有路由變量占位符的值,而這個(gè)路由變量值具有如下三個(gè)來源。
上述的這個(gè)列表順序也體現(xiàn)了HttpRoute對(duì)象在提取路由變量值過程中的選擇優(yōu)先級(jí)。換句話說,如果同名變量值同時(shí)存在于上述的三個(gè)或者兩個(gè)數(shù)據(jù)源,排在前面的會(huì)被優(yōu)先選擇。 至于如何將封裝路由數(shù)據(jù)的HttpRoute對(duì)象附加到某個(gè)HttpRequestMessage對(duì)象上,實(shí)際上就是將對(duì)象添加到HttpRequestMessage的Properties屬性表示的字典對(duì)象中,ASP.NET Web API的路由系統(tǒng)為它限定了一個(gè)固定的Key值為“MS_HttpRouteData”,我們可以通過如下所示的定義在靜態(tài)類型HttpPropertyKeys中的只讀字段HttpRouteDataKey得到這個(gè)值。除此之外,我們還可以調(diào)用針對(duì)HttpRequestMessage類型的兩個(gè)擴(kuò)展方法GetRouteData/SetRouteData來提取和設(shè)置HttpRouteData。 1: public static class HttpPropertyKeys 2: {
3: //其他成員 4: public static readonly string HttpRouteDataKey; 5: }
6:
7: public static class HttpRequestMessageExtensions 8: {
9: //其他成員 10: public static IHttpRouteData GetRouteData(this HttpRequestMessage request); 11: public static void SetRouteData(this HttpRequestMessage request, IHttpRouteData routeData); 12: }
如果HttpRoute在上述三個(gè)來源中不能完全獲取用于替換定義在URL模板中的所有路由變量占位符,它會(huì)直接返回Null。即使能夠完全獲得這些變量值,它還有一個(gè)很“隱晦”的條件:要求參數(shù)values表示的字典對(duì)象中必須包含一個(gè)Key值為“httproute”的元素,否則會(huì)認(rèn)為提供的對(duì)象并非一個(gè)有效的能夠提供“路由變量值”的字典。至于這個(gè)特殊的Key值,我們可以通過定義在類型HttpRoute中如下一個(gè)靜態(tài)只讀字段HttpRouteKey來獲得。 1: public class HttpRoute : IHttpRoute 2: {
3: //其他成員 4: public static readonly string HttpRouteKey = "httproute"; 5: }
當(dāng)HttpRoute根據(jù)優(yōu)先級(jí)從上述三個(gè)數(shù)據(jù)源中獲取到以字典對(duì)象表示的所有路由變量值之后,還需要檢驗(yàn)它們是否能夠滿足自身定義的所有約束,如果不滿足任何一個(gè)約束,HttpRoute依然會(huì)直接返回Null。當(dāng)?shù)玫降穆酚勺兞恐档玫搅怂屑s束的檢驗(yàn),這些值會(huì)綁定到URL模板生成一個(gè)完整的URL,最終被封裝成類型為HttpVirtualPathData的對(duì)象返回。 為了使讀者能夠?qū)Χx在HttpRoute的GetVirtualPath方法中的路由解析邏輯具有更加深刻的印象,我們來做一個(gè)簡單的實(shí)例演示。我們?cè)谝粋€(gè)空的ASP.NET MVC應(yīng)用中定義了如下一個(gè)HomeController,在默認(rèn)的Action方法Index中將5次調(diào)用HttpRoute對(duì)象的GetVirtualPath方法返回的HttpVirtualPathData對(duì)象呈現(xiàn)在默認(rèn)的View中。 1: public class HomeController : Controller 2: {
3: public ActionResult Index() 4: {
5: string routeTemplate = "weather/{areacode}/{days}"; 6: IHttpRoute route = new HttpRoute(routeTemplate); 7: route.Constraints.Add("httpMethod", 8: new HttpMethodConstraint(HttpMethod.Post)); 9: route.Defaults.Add("days", 2); 10:
11: List<IHttpVirtualPathData> virutualPathList =
12: new List<IHttpVirtualPathData>(); 13: HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "/"); 14:
15: //1. 不能提供路由變量areacode的值 16: Dictionary<string, object> values = new Dictionary<string, object>(); 17: virutualPathList.Add(route.GetVirtualPath(request, values));
18:
19: //2. values無Key為"httproute"的元素 20: values.Add("areaCode", "028"); 21: virutualPathList.Add(route.GetVirtualPath(request, values));
22:
23: //3. 所有的路由變量值通過values提供 24: values.Add("httproute", true); 25: values.Add("days", 3); 26: IHttpRouteData routeData = new HttpRouteData(route); 27: routeData.Values.Add("areacode", "0512"); 28: routeData.Values.Add("days", 4); 29: request.SetRouteData(routeData);
30: virutualPathList.Add(route.GetVirtualPath(request, values));
31:
32: //4. 所有的路由變量值通過request提供 33: values.Clear();
34: values.Add("httproute", true); 35: virutualPathList.Add(route.GetVirtualPath(request, values));
36:
37: //5. 采用定義在HttpRoute上的默認(rèn)值(days = 2) 38: routeData.Values.Remove("days"); 39: virutualPathList.Add(route.GetVirtualPath(request, values));
40:
41: return View(virutualPathList.ToArray()); 42: }
43: }
如上面的代碼片斷所示,我們針對(duì)URL模板“weather/{areacode}/{days}”創(chuàng)建了一個(gè)HttpRoute對(duì)象,其中路由變量days具有默認(rèn)值2。除此之外,我們?yōu)閯?chuàng)建的HttpRoute添加了一個(gè)HttpMethodConstraint類型的約束將允許的HTTP方法限定為HTTP-POST。我們隨后創(chuàng)建了基于HTTP-GET的HttpRequestMessage對(duì)象,其請(qǐng)求地址為“/”。 第一次調(diào)用GetVirtualPath方法傳入的參數(shù)分別是上面創(chuàng)建的HttpRequestMessage和一個(gè)空的字典對(duì)象values,很顯然在此情況下HttpRoute不能為路由變量areaCode獲取相應(yīng)的替換值。對(duì)于第二次調(diào)用,傳入的字典對(duì)象為路由變量areaCode指定了相應(yīng)的值。 在第三次調(diào)用中,變量values表示的字典對(duì)象不僅僅同時(shí)包含了路由變量areaCode和days的值,還添加了一個(gè)Key和Value分別為“httproute”和True的元素。對(duì)于提供的HttpRequestMessage對(duì)象,我們通過調(diào)用擴(kuò)展方法SetRouteData為它設(shè)置了一個(gè)HttpRouteData對(duì)象,該對(duì)象的Values屬性表示的字典中同樣具有areaCode和days這兩個(gè)路由變量的值。 我們?cè)诘谒拇握{(diào)用GetVirtualPath方法之前將values變量保存的路由變量areaCode和days的值清除,但保留了Key為“httproute”的元素。對(duì)于最后一次GetVirtualPath方法調(diào)用,我們清楚了附加在HttpRequestMessage上HttpRouteData對(duì)象針對(duì)路由變量days的值。 如下所示的是Action方法Index對(duì)應(yīng)View的定義,這是一個(gè)Model類型為IHttpVirtualPathData數(shù)組的強(qiáng)類型View。在該View中,我們將每個(gè)HttpVirtualPathData對(duì)象的VirtualPath屬性表示的URL以表格的形式呈現(xiàn)出來。如果HttpVirtualPathData為Null,直接顯示“N/A”字樣。 1: @model System.Web.Http.Routing.IHttpVirtualPathData[]
2: <html> 3: <head> 4: <title>路由解析</title> 5: </head> 6: <body> 7: <table> 8: @for (int i = 0; i < Model.Length; i++) 9: {
10: <tr> 11: <td>@(i+1)</td> 12: <td>@(Model[i] == null ? "N/A" : Model[i].VirtualPath)</td> 13: </tr> 14: }
15: </table> 16: </body> 17: </html>
其實(shí)這個(gè)實(shí)例還說明了另一個(gè)問題:HttpRoute的GetVirtualPath方法只會(huì)進(jìn)行針對(duì)定義在URL模板中路由變量的約束檢驗(yàn)。對(duì)于這個(gè)演示實(shí)例來說,我們創(chuàng)建的HttpRoute具有一個(gè)基于HTTP-POST的HttpMethodConstraint類型的約束(對(duì)應(yīng)的名稱為“httpMethod”),但是調(diào)用GetVirtualPath方法傳入的確是一個(gè)針對(duì)HTTP-GET的HttpRequestMessage對(duì)象,依然是可以生成相應(yīng)HttpVirtualPathData的。這也很好理解,因?yàn)镠ttpRoute的GetVirtualPath方法的目的在于生成一個(gè)合法的URL,定義在URL模板中的路由變量對(duì)應(yīng)的約束才有意義。 六、HttpRouteCollection故名思義HttpRouteCollection就是一個(gè)元素類型為IHttpRoute的集合,如下面的代碼片斷所示,它實(shí)現(xiàn)了接口ICollection<IHttpRoute>。ASP.NET Web API路由系統(tǒng)中的路由表實(shí)際上就是一個(gè)HttpRouteCollection對(duì)象。HttpRouteCollection具有一個(gè)只讀屬性VirtualPathRoot表示進(jìn)行路由解析時(shí)默認(rèn)使用的虛擬跟路徑,該屬性可以直接在調(diào)用構(gòu)造函數(shù)是通過參數(shù)指定,其默認(rèn)值為“/”。 1: public class HttpRouteCollection : ICollection<IHttpRoute>, IDisposable 2: {
3: //其他成員 4: public HttpRouteCollection(); 5: public HttpRouteCollection(string virtualPathRoot); 6:
7: public IHttpRoute CreateRoute(string routeTemplate, object defaults, object constraints); 8: public IHttpRoute CreateRoute(string routeTemplate, IDictionary<string, object> defaults, IDictionary<string, object> constraints, IDictionary<string, object> dataTokens); 9: public virtual IHttpRoute CreateRoute(string routeTemplate, IDictionary<string, object> defaults, IDictionary<string, object> constraints, IDictionary<string, object> dataTokens, HttpMessageHandler handler); 10:
11: public virtual IHttpRouteData GetRouteData(HttpRequestMessage request); 12: public virtual IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, string name, IDictionary<string, object> values); 13:
14: public virtual string VirtualPathRoot { get; } 15: }
除了實(shí)現(xiàn)定義在接口ICollection<IHttpRoute>中的眾多方法之外,HttpRouteCollection還定義了兩個(gè)CreateRoute方法重載幫助我們根據(jù)指定的URL模板、路由變量默認(rèn)值、約束和DateToken列表以及HttpMessageHandler對(duì)象創(chuàng)建HttpRoute對(duì)象。 HttpRouteCollection同樣定義了GetRouteData和GetVirtualPath方法,它們的邏輯與ASP.NET路由系統(tǒng)中的RouteCollection類型中的同名方法一致:按照先后順利調(diào)用每個(gè)HttpRoute對(duì)象的同名方法直到返回一個(gè)具體的HttpRouteData或者HttpVirtualPathData對(duì)象。 HttpRouteCollection的GetRouteData方法中并沒有表示虛擬根路徑的參數(shù),那么當(dāng)它在調(diào)用具體HttpRoute對(duì)象的同名方法的時(shí)候如何指定這個(gè)參數(shù)呢?具體的邏輯是這樣的:它先判斷虛擬根路徑是否已經(jīng)被添加到表示請(qǐng)求的HttpRequestMessage的屬性字典(Properties屬性)中,對(duì)應(yīng)的Key為“MS_VirtualPathRoot”,如果這樣的屬性存在并且是一個(gè)字符串,那么這將直接被用作調(diào)用HttpRoute的GetRouteData方法的參數(shù)。否則直接使用通過屬性VirtualPathRoot表示的默認(rèn)根路徑。 HttpRequestMessage屬性字典中表示虛擬根路徑的Key可以直接通過類型HttpPropertyKeys的靜態(tài)只讀字段VirtualPathRoot獲取。我們可以直接調(diào)用HttpRequestMessage如下兩個(gè)擴(kuò)展方法GetVirtualPathRoot和SetVirtualPathRoot獲取或者設(shè)置虛擬根路徑。 1: public static class HttpPropertyKeys 2: {
3: //其他成員 4: public static readonly string VirtualPathRoot; 5: }
6:
7: public static class HttpRequestMessageExtensions 8: {
9: //其他成員 10: public static string GetVirtualPathRoot(this HttpRequestMessage request); 11: public static void SetVirtualPathRoot(this HttpRequestMessage request, string virtualPathRoot); 12: }
關(guān)于HttpRouteCollection,值得一提的是它對(duì)于添加的HttpRoute對(duì)象的保存方式。如下面的代碼片斷所示,HttpRouteCollection具有_collection和_dictionary兩個(gè)只讀字段,類型分別是List<IHttpRoute>和IDictionary<string, IHttpRoute>,前者單純地保存添加的HttpRoute對(duì)象,后者給每個(gè)添加的HttpRoute對(duì)象匹配一個(gè)具有唯一性的名稱。 1: public class HttpRouteCollection : ICollection<IHttpRoute>, IDisposable 2: {
3: // 其他成員 4: private readonly List<IHttpRoute> _collection; 5: private readonly IDictionary<string, IHttpRoute> _dictionary; 6:
7: public virtual void Insert(int index, string name, IHttpRoute value); 8: public virtual void Add(string name, IHttpRoute route); 9: void ICollection<IHttpRoute>.Add(IHttpRoute route); 10:
11: public virtual bool Remove(string name); 12: bool ICollection<IHttpRoute>.Remove(IHttpRoute route); 13:
14: public virtual IHttpRoute this[int index] { get; } 15: public virtual IHttpRoute this[string name] { get; } 16: }
HttpRouteCollection采用“接口顯式實(shí)現(xiàn)”的方式實(shí)現(xiàn)了定義在ICollection<T>中的Add和Remove方法,所以這兩個(gè)方法我們基本上不用使用。取而代之的是額外的Add和Remove方法,通過調(diào)用Add方法可以為添加的HttpRoute對(duì)象指定一個(gè)“注冊(cè)名稱”,而根據(jù)這個(gè)注冊(cè)名稱可以調(diào)用Remove方法將對(duì)應(yīng)的HttpRoute移除。 調(diào)用Add方法添加的HttpRoute會(huì)同時(shí)被添加到通過字段_collection和_dictionary表示的集合和字典之中。不論是調(diào)用HttpRouteCollection的GetRouteData方法還是GetVirtualPath方法,它總是按照HttpRoute在集合_collection中的順序進(jìn)行便利,第一個(gè)匹配的HttpRoute會(huì)被選用,所以HttpRoute在集合中的順序顯得尤為重要。由于通過Add方法添加的HttpRoute對(duì)象總是被添加到集合的最后,所以另一個(gè)Insert方法被定義在HttpRouteCollection中使我們可以同時(shí)決定被添加HttpRoute的名稱和次序。除了上述這些方法外,我們還可以通過索引的方式得到存在于HttpRouteCollection對(duì)象中的HttpRoute對(duì)象。 七、注冊(cè)路由映射與ASP.NET路由系統(tǒng)下的路由映射類似,ASP.NET Web API下的路由映射就是為針對(duì)應(yīng)用的路由表添加相應(yīng)HttpRoute對(duì)象的過程。整個(gè)ASP.NET Web API框架是一個(gè)請(qǐng)求處理的管道,我們可以在程序啟動(dòng)的時(shí)候?qū)ζ溥M(jìn)行相應(yīng)的配置是整個(gè)管道按照我們希望的方式來工作,我們所做的擴(kuò)張也是通過相應(yīng)的配置應(yīng)用到管道之上。 我們對(duì)ASP.NET Web API的請(qǐng)求處理管道所做的所有配置基本上都是通過一個(gè)類型為HttpConfiguration的對(duì)象來完成,而路由注冊(cè)自然也不例外。如下面的代碼片斷所示,HttpConfiguration具有一個(gè)類型為HttpRouteCollection的只讀屬性Routes,我們進(jìn)行路由映射注冊(cè)的HttpRoute正是被添加于此。 1: public class HttpConfiguration : IDisposable 2: {
3: //其他成員 4: public HttpRouteCollection Routes { get; } 5: public string VirtualPathRoot { get; } 6: public ConcurrentDictionary<object, object> Properties { get; } 7: }
HttpConfiguration的另一個(gè)與路由相關(guān)的屬性VirtualPathRoot表示默認(rèn)使用的虛擬根路徑,它直接返回通過Routes屬性表示的HttpRouteCollection對(duì)象的同名屬性。我們可以通過字典類型的只讀屬性Properties將相應(yīng)的對(duì)象附加到HttpConfiguration,這與我們使用HttpRequestMessage的Properties屬性的方式一致。在具體的運(yùn)行環(huán)境中,我們使用HttpConfiguration都是針對(duì)整個(gè)應(yīng)用的全局對(duì)象,所以我們添加到Properties屬性中的對(duì)象也是全局,我們?cè)谡麄€(gè)應(yīng)用的任何地方都可以提取它們。 我們可以直接根據(jù)指定的URL模板,以及針對(duì)路由變量的默認(rèn)值和約束來創(chuàng)建相應(yīng)的HttpRoute,并最終將其添加到通過HttpConfiguration的Routes對(duì)象表示的路由表中從而到達(dá)注冊(cè)路由映射的目的。除此之外,我們還可以直接調(diào)用HttpRouteCollection如下一系列重載的擴(kuò)展方法MapHttpRoute實(shí)現(xiàn)相同的目的。實(shí)際上這些擴(kuò)展方法最終還是調(diào)用HttpRouteCollection的Add方法將創(chuàng)建的HttpRoute添加到路由表中的。 1: public static class HttpRouteCollectionExtensions 2: {
3: //其他成員 4: public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, string routeTemplate); 5: public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, string routeTemplate, object defaults); 6: public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, string routeTemplate, object defaults, object constraints); 7: public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, string routeTemplate, object defaults, object constraints, HttpMessageHandler handler); 8: }
對(duì)于上面定義的這些MapHttpRoute方法重載,最終根據(jù)指定的URL模板、默認(rèn)值、約束、DataToken以及HttpMessageHandler對(duì)具體HttpRoute的創(chuàng)建是通過調(diào)用HttpRouteCollection具有如下定義的CreateRoute方法實(shí)現(xiàn)的。這是一個(gè)虛方法,所以如何我們希望調(diào)用這些擴(kuò)展方法注冊(cè)自定義的HttpRoute,可以自定義一個(gè)HttpRouteCollection類型并重寫這個(gè)CreateRoute方法即可。 1: public class HttpRouteCollection : ICollection<IHttpRoute>, IDisposable 2: {
3: //其他成員 4: public virtual IHttpRoute CreateRoute(string routeTemplate, IDictionary<string, object> defaults, IDictionary<string, object> constraints, 5: IDictionary<string, object> dataTokens, HttpMessageHandler handler); 6: }
至于如果獲取用于配置ASP.NET Web API管道的HttpConfiguration對(duì)象,這依賴于我們對(duì)Web API的寄宿方式,這并沒有定義在ASP.NET Web API的核心框架之中。 八、缺省路由變量我們?cè)谶M(jìn)行路由注冊(cè)的時(shí)候可以為某個(gè)路由變量設(shè)置一個(gè)默認(rèn)值,這個(gè)默認(rèn)值可以是一個(gè)具體的變量值,也可以是通過RouteParameter具有如下定義的靜態(tài)只讀字段Optional返回的一個(gè)RouteParameter對(duì)象,我們具有這種默認(rèn)值的路由變量成為缺省路由變量。 1: public sealed class RouteParameter 2: {
3: public static readonly RouteParameter Optional; 4: }
實(shí)際上當(dāng)我們利用Visual Studio的ASP.NET Web API向?qū)陆ㄒ粋€(gè)Web應(yīng)用的時(shí)候,在生成的用于注冊(cè)路由的RouteConfig.cs中會(huì)默認(rèn)注冊(cè)如下一個(gè)HttpRoute,其路由變量id就是一個(gè)具有默認(rèn)值為RouteParameter.Optional的缺省路由變量。 1: public class RouteConfig 2: {
3: public static void RegisterRoutes(RouteCollection routes) 4: {
5: //其他操作 6: routes.MapHttpRoute(
7: name : "DefaultApi", 8: routeTemplate : "api/{controller}/{id}", 9: defaults : new { id =RouteParameter.Optional } 10: );
11: }
12: }
雖然同是具有默認(rèn)值的路由變量,但是缺省路由變量具有不同之處:如果請(qǐng)求URL中沒有提供對(duì)應(yīng)變量的值,普通具有默認(rèn)值的路由變量依然會(huì)出現(xiàn)在最終HttpRouteData的Values屬性中,但是缺省路由變量則不會(huì)。 作者:Artech
出處:http://artech.cnblogs.com/ 本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。 評(píng)論列表
|
|