Asp.Net Web API 2第六課——Web API路由和動作選擇


Asp.Net Web API 導航

      Asp.Net Web API第一課——入門http://www.cnblogs.com/aehyok/p/3432158.html

      Asp.Net Web API第二課——CRUD操作http://www.cnblogs.com/aehyok/p/3434578.html

      Asp.Net Web API第三課——.NET客戶端調用Web API http://www.cnblogs.com/aehyok/p/3439698.html

      Asp.Net Web API第四課——HttpClient消息處理器 http://www.cnblogs.com/aehyok/p/3442277.html

  Asp.Net Web API第五課——Web API路由 http://www.cnblogs.com/aehyok/p/3442051.html

前言

  本文描述ASP.NET Web API如何把一個HTTP請求路由到控制器的一個特定的Action上。關於路由的總體概述可以參見上一篇教程 http://www.cnblogs.com/aehyok/p/3442051.html。這篇文章主要來學習路由過程的細節。如果你創建了一個Web API項目,發現有一些請求沒有按照你期望的方式被路由,希望這篇文章將對你有所幫助。

      本文主要分為三個階段:

  1.匹配URI到一個Route Template。

  2.選擇一個Controller。

  3.選擇一個Action。

  你可以用自己的自定義行為來替換這一過程中的某些部分。在本文中,我將來描述默認的行為。在文章結尾,我會注明可以在什么地方自定義行為。

 

  

Route Templates

 路由模版看上去類似於一個URI路徑,但它可以具有占位符,並用花括號來指示:

"api/{controller}/public/{category}/{id}"

當創建一個路由的時候,你可以為某些或所有占位符提供默認值:

defaults: new { category = "all" }

你也可以提供約束,它限制URI片段如何與占位符匹配:

constraints: new { id = @"\d+" }   // Only matches if "id" is one or more digits.

上面語句是通過正則表達式來限制片段的取值,上面的注釋說明 id片段只匹配一個或多個數字,因此URI中的id片段必須是數字才能與這個路由進行匹配。

 

這個框架試圖把URI路徑中的片段與這個模板進行匹配。模板中的文字必須嚴格匹配。一個占位符可以匹配任何值,除非你指定了約束。這個框架不會匹配URI另外的部分,例如主機名或者一個查詢字符串。這個框架會選擇路由表中第一個匹配的路由。

這里有兩個特殊的占位符:“{controller}”和“{action}”。

  • “{controller}”提供控制器名。
  • “{action}”提供動作名。在Web API中,通常的約定是忽略“{action}”的。

Defaults(默認值)

如果你提供默認值,那么這個路由將匹配缺少這些片段的URI。例如:

routes.MapHttpRoute(
    name: "DefaultApi", 
    routeTemplate: "api/{controller}/{category}", 
    defaults: new { category = "all" } 
);

這個URI“http://localhost/api/products”與這個路由是匹配的。“{category}”片段被賦成了默認值“all”。

Route Dictionary(路由字典)

  如果這個框架發現了一個匹配的URI,它會創建包含每個占位符值的一個字典。這個鍵值是不帶花括號的的占位符名稱。這個值取自於URI路徑或者是默認值中的。這個字段被存在IHttpRouteData對象中。在匹配路由階段,這個特殊的"{controller}" and "{action}"占位符的處理和其他占位符是一樣的。它們用另外的值被簡單的存儲在字典中。

  在默認值中可以使用特殊的RouteParameter.Optional值。如果一個占位符被賦予了這個值,那么這個值將不會被添加到路由字典中,例如:

routes.MapHttpRoute( 
    name: "DefaultApi", 
    routeTemplate: "api/{controller}/{category}/{id}", 
    defaults: new { category = "all", id = RouteParameter.Optional } 
);

對於URI路徑“api/products”,路由字典將含有:controller:"products"、category:"all"。

然而,對於“api/products/toys/123”,路由字典將含有:controller:"products"、category:"toys"、id:"123"。

 

這個默認值也可以包含未出現在路由模板中的值。若這條路由匹配,則該值會被存儲在路由字典中。例如:

routes.MapHttpRoute( 
    name: "Root", 
    routeTemplate: "api/root/{id}", 
    defaults: new { controller = "customers", id = RouteParameter.Optional } 
);

如果URI路徑是“api/root/8”,字典將含有兩個值:controller:“customers”,id:"8"。

Selecting a Controller

控制器選擇是由IHttpControllerSelector.SelectController方法來處理的。這個方法以HttpRequestMessage實例為參數,並返回HttpControllerDescriptor

其默認實現是由DefaultHttpControllerSelector類提供的。這個類使用了一種很直接的算法:

  1.查找路由字典的“controller”鍵。

  2.取得這個鍵的值,並附加字符串“Controller”,以得到控制器的類型名。

  3.用這個類型名查找Web API控制器。

  例如,如果路由字典中的鍵-值對為“controller”=“products”,那么控制器類型便為“ProductsController”。如果沒有匹配類型,或有多個匹配,這個框架會給客戶端返回一條錯誤。

對於步驟3,DefaultHttpControllerSelector使用IHttpControllerTypeResolver接口以獲得Web API控制器類型的列表。 IHttpControllerTypeResolver的默認實現會返回所有符合以下條件的public類:

a:實現IHttpController的類。

b:是非抽象類。

c:名稱以“Controller”結尾的類。

Action Selection

  選擇了控制器之后,這個框架會通過調用IHttpActionSelector.SelectAction方法來選擇動作。這個方法以HttpControllerContext為參數,並返回HttpActionDescriptor

這個默認實現是由ApiControllerActionSelector類提供的。為了選擇一個動作,會查找以下方面:

  1.HTTP請求的方法。

  2.這個路由模板中的“action”占位符。

  3.控制器中動作的參數。

在查找選擇算法之前,我們需要理解控制器動作的一些事情。

  控制器中的哪些方法被看成為是“動作”?當選擇一個動作時,這個框架只考察控制器的public實例方法。而且,它會排除特殊名稱的方法(構造器、事件、操作符、重載等等),以及集成自ApiController的類方法。

 

HTTP Methods

這個框架只會選擇與請求的HTTP方法匹配的動作,確定如下:

  1.你可以用注解屬性AcceptVerbs、HttpDelete、HttpGet、HttpHead、HttpOptions、HttpPatch、HttpPost、或HttpPut來指定HTTP方法。

  2.否則,如果控制器方法名稱以“Get”、“Post”、“Put”、“Delete”、“Head”、“Options”、或“Patch”開頭,那么根據這個約定,該Action將支持相應的HTTP方法。

  3.如果以上都不是,那么這個方法將支持Post。

Parameter Bindings.

  參數綁定是指Web API如何創建參數值。以下是參數綁定的默認規則:1.簡單類型取自URI。2.復雜類型取自請求正文。

簡單類型包括所有“.NET框架簡單類型”,另外還有,DateTime、Decimal、Guid、String和TimeSpan。對於每一個動作,最多只有一個參數可以讀取請求正文。

它也可以重寫這種默認的綁定規則。See WebAPI Parameter binding under the hood

在這種背景下,動作選擇算法如下:

1.創建該控制器中與HTTP請求方法匹配的所有動作的列表。

2.如果路由字典有“action”條目,移除與該條目值不匹配的動作。

3.試圖將動作參數與該URI匹配,如下:

  a:針對每個動作,獲得簡單類型的參數列表,這是綁定得到URI參數的地方。該列表不包括可選參數。

  b:從這個列表中,試着在路由字典或是在URI查詢字符串中,找到每個參數的匹配。匹配是與大小寫無關的,且與參數順序無關。

  c:選擇這樣的一個action,在列表中的每個參數在URI中有一個匹配。

  d:如果滿足這些條件的動作不止一個,選用參數匹配最多的一個。

4.忽略用[NonAction]注解屬性標注的動作。

 

第3步可能會讓人困擾。其基本思想是,可以從URI、或請求體、或一個自定義綁定來獲取參數值。對於來自URI的參數,我們希望確保URI在其路徑(通過路由字典)或查詢字符串中實際包含了一個用於此參數的值。

例如,考慮以下動作:

public void Get(int id)

其id參數綁定到URI。因此,這個動作只能匹配在路由字典或查詢字符串中包含了“id”值的URI。

可選參數是一個例外,因為它們是可選的。對於可選參數,如果綁定不能通過URI獲取它的值,是沒關系的。

復雜類型是另一種原因的例外。一個復雜類型只能通過自定義綁定來綁定到URI。但是在這種情況下,這個框架不能提前知道是否這個參數被綁定到一個特殊的URI。為了查明情況,這個框架需要調用這個綁定。選擇算法的目的是在調用綁定之前根據靜態描述來選擇一個動作。因此,復雜類型是屬於匹配算法之外的。

動作選擇之后,會調用所有參數綁定。

Summary:

1.動作必須匹配請求的HTTP方法。

2.動作名必須匹配路由字典中的“action”條目,如果有。

3.對於動作的各個參數,如果參數取自URI,那么該參數名必須在路由字典或URI查詢字符串中能夠被找到。(可選參數和復雜類型除外)。

4.試圖匹配最多數目的參數。最佳匹配可能是一個無參數的方法。

Extended Example

看如下路由:

routes.MapHttpRoute( 
    name: "ApiRoot", 
    routeTemplate: "api/root/{id}", 
    defaults: new { controller = "products", id = RouteParameter.Optional } 
); 

routes.MapHttpRoute( 
    name: "DefaultApi", 
    routeTemplate: "api/{controller}/{id}", 
    defaults: new { id = RouteParameter.Optional } 
);

再看如下Contoller下的內容:

public class ProductsController : ApiController 
{ 
    public IEnumerable<Product> GetAll() {} 
    public Product GetById(int id, double version = 1.0) {} 
    [HttpGet] 
    public void FindProductsByName(string name) {} 
    public void Post(Product value) {} 
    public void Put(int id, Product value) {} 
}

 

 

HTTP請求:

http://localhost:34701/api/products/1?version=1.5&details=1

路由匹配:

該URI與名為“DefaultApi”路由匹配。路由字典包含以下條目:controller:"products",id:"1"。該路由字典並未包含查詢字符串參數“version”和“details”,但這些將在動作選擇期間考慮。

控制器選擇:

根據路由字典中的“controller”條目,控制器類型是ProductsController。

動作選擇:

這個HTTP請求是一個GET請求。支持Get的控制器動作是GetALL、GetById、FindProductsByName。這個路由字典不包含”action“條目,因此不需要匹配動作名稱。

下一步,會試圖匹配這些動作的參數名,只考查GET動作。

注意,不會考慮GetById的version參數,因為它是一個可選參數。

GetAll方法非常匹配。GetById方法也匹配,因為路由字典包含了“id”。FindProductsByName方法不匹配。

GetById方法是贏家,因為它匹配了一個參數,而GetAll無參數。該方法將以以下參數值被調用:id=1,version=1.5

注意,雖然version未被用於選擇算法,但該參數值會取自URI查詢字符串。

Extension Points

 Web API為路由過程的某些部分提供了擴展點。

要為以上任一接口提供自己的實現,可使用HttpConfiguration對象的Services集合:

var config = GlobalConfiguration.Configuration;
config.Services.Replace(typeof(IHttpControllerSelector), new MyControllerSelector(config));

總結

 這一篇好難懂,無奈之下多查了一下,就感覺肯定早有人翻譯過了,還真是就在博客園就有,我之前咋就沒發現呢http://www.cnblogs.com/r01cn/archive/2012/12/04/2801158.html,感覺大神翻譯的真心贊,好好看就看懂了,為了提高自己閱讀英語的水平,看一句英文,看一句翻譯,然后自己再逐字敲一遍。這一篇下來真心累了。不過自己對Asp.Net MVC的路由機制也有了新的認識,不錯。

參考原文鏈接http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-and-action-selection

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM