Routing Tables路由表 在Asp.Net Web API中,一個控制器就是一個處理HTTP請求的類,控制器的public方法就被叫做action方法或簡單的Action。當Web API接收到一個請求的時候,它將這個請求路由到一個Action。 註意:Web API的路由與Asp.Net ...
Routing Tables路由表
在Asp.Net Web API中,一個控制器就是一個處理HTTP請求的類,控制器的public方法就被叫做action方法或簡單的Action。當Web API接收到一個請求的時候,它將這個請求路由到一個Action。
註意:Web API的路由與Asp.Net MVC的路由是非常相似的。主要區別就是Web API使用的是HTTP方法,而不是URI路徑來選擇Action
為了確定哪個Action被調用,這個框架使用了一個註冊表。Visual Studio的Web API的項目模板會創建一個預設路由:
1 config.Routes.MapHttpRoute( 2 name: "DefaultApi", 3 routeTemplate: "api/{controller}/{id}", 4 defaults: new { id = RouteParameter.Optional } 5 );
這個路由是在WebApiConfig文件中定義的,該文件位於App_Start目錄
當Web API框架接收到一個HTTP請求時,它會試圖根據路由表中的一個路由模板來匹配其URI。如果無路由匹配,客戶端會接收到一個404(未找到)錯誤。例如,以下URI與這個預設路由的匹配
- /api/product
- /api/product/1
- /api/product?category=category
然而,以下URI就不匹配,因為它缺少“api”欄位
- /product/1
註意:在路由中使用“api”的原因是為了避免與ASP.NET MVC的路由衝突。通過這種方式,可以用“/product”進入一個控制器,而“/api/product”進入一個Web API控制器。當然,如果你不喜歡這種約定,也可以修改這個預設路由表。
一旦一個匹配的路由被髮現,Web API便會選擇相應的Controller和Action。
1.為了找到Controller,Web API會把“控制器”加到{Controller}變數的值
2.為了找到Action,Web API會查找HTTP方法,然後尋找一個名稱以HTTP方法名開頭的方法。例如:對於Get請求,Web API會查找一個以“Get..”開頭的Action,這種約定只應用於GET,POST,PUT,DELETE方法,通過在Controller上使用attribute,你可以啟動其它的HTTP方法
3.路由模板中其它的占位變數;例如{id},將會被映射成Action的參數。
Routing Variations路由變化
HTTP方法
替代使用HTTP方法的命名約定,你可以明確的為一個Action指定HTTP方法,通過以HttpGet,HttpPost,HttpPut或者HttpDelete屬性來對Action方法進行修身
在下列示例中,FindProduct方法被映射到GET請求
1 [HttpGet] 2 public Product FindProduct(int id) 3 { 4 return repository.Get(id); 5 }
使用上面代碼時需要先註釋上面寫的GetProduct(int id);
因為如果不註釋 Web API會匹配到請求匹配的多個操作錯誤
Web API允許一個Action對應多個HTTP方法;
1 [AcceptVerbs("GET","POST","HEAD")] 2 public Product FindProduct(int id) 3 { 4 return repository.Get(id); 5 } 6 [AcceptVerbs("MKCOL")] 7 public void MakeCollection() 8 { 9 10 }
第一個方法:指示該Action接收HTTP的GET,POST和HEAD方法。
第二個方法:WebDAV方法,(基於Web的分散式著作與版本控制的HTTP方法,是一個擴展的HTTP方法,MKCOL時隸屬於WebDAV的一個方法,它在URI指定的位置創建集合)
通過Action名稱路由
在預設的路由模板中,這個Web API使用HTTP方法去選擇Action。然而,你也可以在URI中創建包含Action名的路由
1 config.Routes.MapHttpRoute( 2 name: "DefaultApi", 3 routeTemplate: "api/{controller}/{Action}/{id}", 4 defaults: new { id = RouteParameter.Optional } 5 );
在這個路由模板中,{action}參數命名了控制器的Action方法。採用這種風格,需要使用註解屬性來指明所允許的HTTP方法。例如,假設你的控制器已有以下方法:
1 [HttpGet] 2 public string Details(int id);
在這中情況下,一個GET請求“api/Product/Details/1”將會映射到這個Detail方法。這種風格的路由類似於Asp.Net MVC,而且可能與RPC式的API接近。
你也可以通過使用ActionName註解屬性來覆蓋動作名。在以下例子中,有兩個Action映射到"api/product/thumbnail/id"。一個支持GET,一個支持POST
1 [HttpGet] 2 [ActionName("Thumbnail")] 3 public HttpResponseMessage GetThumbnailImage(int id); 4 [HttpPost] 5 [ActionName("Thumbnail")] 6 public void AddThumbnailImage(int id);
NonActions
為了防止一個方法被當作Action所請求,可以使用NonAction註解屬性。它對框架發送信號:這個方法不是以一個Action,即使它可能與路由規則匹配
1 [NonAction] 2 public void IsNoAction();
Route Templates
路由模板看起來類似一個URI路徑,但它可以具有占位符,並用{}來指示:
"api/{controller}/public/{category}/{id}"
當創建一個路由的時候,你可以為某些或所有占位符提供預設值
defaults: new { category = "all" }
你可以提供約束,它限制URI片段如何與占位符匹配
constraints: new { id = @"\d+" } // 只有在“id”是一個或多個數字時才匹配
上面語句是通過正則表達式來限製片段的取值,上面的註釋說明id片段只匹配一個或多個數字,因此URI中id片段必須是一個數字才能與這個路由進行匹配。
這個框架試圖把URI路徑中的片段與這個模板進行匹配。模板中文字必須嚴格匹配。一個占位符可以匹配任何值,除非你指定了約束。這個框架不會URI另外的部分,例如主機名或者一個查詢字元串。這個框架會選擇路由表中第一個匹配的路由。
這個有兩個特殊的占位符:“{Controller}”和“{Action}”。
{Controller}提供控制器名
{Action} 提供動作名。在Web API中,通常的約定是忽略{Action}的。
Defaults(預設值)
如果你提供預設值,那麼這個路由匹配缺少這些片段的URI。例如
1 routes.MapHttpRoute( 2 name: "DefaultApi", 3 routeTemplate: "api/{controller}/{category}", 4 defaults: new { category = "all" } 5 );
這個URI“http://localhost/api/products”與這個路由是匹配的。“{category}”片段將賦成了預設值“all”。
Route Dictionary(路由欄位)
如果這個框架發現了一個匹配的URI,它會創建包含每個占位符值的字典。這個鍵值是不帶{}的占位符名稱。這個值取自於URI路徑或是預設值。這個欄位被存在IHttpRouteData對象中。在匹配路由階段,這個特殊的{Controller}和{Action}占位符的處理和其它占位符是一樣的,它們用另外的值被簡單的存儲在字典中。
在預設值中可以使用特殊的RouteParameter.Optional值。如果一個占位符被賦予了這個值,那麼這個值將不會被添加到字典中,例如
1 routes.MapHttpRoute( 2 name: "DefaultApi", 3 routeTemplate: "api/{controller}/{category}/{id}", 4 defaults: new { category = "all", id = RouteParameter.Optional } 5 );
對於URI路徑“api/product”,路由字典將含有:controller:"product",category:"all"
然而,對於”api/product/toys/123“,路由字典將含有:controller:"product",category:"toys"
這個預設值也可以包含未出現的路由模板中的值。若這條路由匹配,則該值會被存儲在路由字典中。例如
1 routes.MapHttpRoute( 2 name: "Root", 3 routeTemplate: "api/root/{id}", 4 defaults: new { controller = "product", id = RouteParameter.Optional } 5 );
如果URI路徑是”api/root/7“,字典中將含有兩個值:controller:"product",id:"8"。
Selecting a Controller
控制器選擇是由IHttpControllerSelector.SelectController方法來處理的。這個方法以HttpRequestMessage實例為參數。並返回HttpControllerDescriptor。
其預設實現是由DefaultHttpControllerSelector類提供的。這個類使用了一種很直接的演算法:
1.查找路由字典的”controller“鍵。
2.取得這個鍵的值,並附加字元串”Controller“,以得到控制器的類型名。
3.用這個類型名查找Web API控制器
例如,如果路由字典的鍵-值對為”controller“=”product“,那麼控制器類型便為”ProductController“。如果沒有匹配,或多個匹配,Web API框架會給客戶端返回一個錯誤。
對於步驟3,DefaultHttpControllerSelector使用IHttpControllerTypeResolver介面以獲得Web API控制類型的列表。IHttpControllerTypeResolver的預設實現會返回所有符合以下條件的public類:
- 實現IHttpController的類
- 是非抽象類
- 名稱以”Contoller“結尾的類
Action Selection
選擇了控制器後,Web API框架會通過調用IHttpActionSelector.SelectAction方法來選擇Action。這個方法以HttpControllerContext為參數,並返回HttpActionDescriptor。
這個預設實現是由ApiControllerActionSelector類提供的。為了選擇一個Action,會查找以下方面:
- HTTP請求的方法
- 這個路由模板的action占位符
- 控制器中Action的參數
在查找選擇演算法之前,我們需要理解控制器Action的一些事情
控制器的哪些方法被看成為Action?當選擇一個Action時,這個框架只考察控制器的public實例方法。而且,它會排除特殊名稱的方法(構造器,事件,操作符,重載符等),以及集成自ApiController的類方法
HTTP Methods
Web API框架只會選擇與請求的HTTP方法匹配的Action,確定如下
- 你可以用註解屬性AcceptVerbs,HttpDelete,HttpGet,HttpPost,HttpOptions,HttpPatch,HttpPost或者HttpPut來指定HTTP方法
- 如果控制器方法名稱以Get,Post,Put,Delete,Head,Options或Patch開頭,那麼根據這個約定,該Action將支持相應的HTTP方法。
- 如果以上都不是,那麼這個方法將只支持Post請求。
Parameter Bindings
參數綁定是指Web API如何創建參數值。以下是參數綁定的預設規則:
1.簡單類型取自URI
2.複雜類型取自請求正文
簡單類型包括所有".NET框架簡單類型",另外還有,DateTime,Decimal,Guid,String和TimeSpan。對於每一個Action,最多只有一個參數可以讀取請求正文。
在這種背景下,Action選擇演算法如下
- 創建該控制器中與HTTP請求方法匹配的所有Action的列表
- 如果路由字典有Action條目,移除與該條目值不匹配的Action
- 試圖將Action參數與該URI匹配,如下
a:針對每個Action,獲得簡單類型的參數列表,這是綁定得到URI參數的地方。該列表不包括可選參數
b:從這個列表中,試著在路由字典或是在URI查詢字元串中,找到每個參數的匹配。匹配是與大小寫無關的,且與參數順序無關
c:選擇這樣的一個Action,在列表中的每個參數在URI中有一個匹配
d:如果滿足這些條件的Action不止一個,選用參數匹配最多的一個。
4.忽略用[NonAction]註解屬性標註的Action。
第3步可能會rang人困擾。其基本思想是,可以從URI,或請求體,或一個自定義綁定來獲取參數值。對於來自URI的參數,我們希望確保URI在其路徑(通過路由字典)或查詢字元串中實際包含一個用於此參數的值。
例如,考慮以下Action
public void Get(int id)
其id綁定到URI。因此,這個Action只能匹配在路由字典或查詢字元串包含了id值的URI
可選參數是一個例外,因為它們是可選的。對於可選參數,如果綁定不能通過URI獲取它的值,是沒關係的。
複雜類型是另一個原因的例外。一個複雜類型只能通過自定義綁定來綁定到URI。但是在這種情況下,Web API框架不能提前知道是否這個參數被綁定到一個特殊的URI。為了查明情況,這個框架需要調用這個綁定。選擇演算法的目的是在調用綁定之前根據靜態描述來選擇一個Action。因此,負責類型是屬於匹配演算法之外的。
Action選擇之後,會調用所有參數綁定。
Summary:
- Action必須匹配請求的HTTP方法。
- Action名必須匹配路由字典中的Action條目,如果有的話。
- 對於Action的各個參數,如果參數來自URI,那麼該參數名必須在路由字典或URI查詢字元串中能夠被找到(可選參數和複雜參數類型除外)
- 試圖匹配最多數目的參數。最佳匹配可能是一個無參數的方法。
Extended Points
Web API為路由過程的某些部分提供了擴展點。
要為以上任一介面提供自己的實現,可使用HttpConfiguration對象的Services集合:
var config = GlobalConfiguration.Configuration; config.Services.Replace(typeof(IHttpControllerSelector), new MyControllerSelector(config));