問題
怎樣集中的定義路由
解決方案
通過調用 HttpRouteCollectionExtension 類中的 MapHttpRoute 擴展方法在 HttpRouteCollection 中定義路由,可以通過 HttpConfiguration 對象調用。
最基礎的使用就是定義一個非常通用的路由模板,他會通過 {controller} 占位符匹配所有的 Controller。如代碼片段 3-1 所示。
代碼片段 3-1. ASP.NET WEB API 默認定義的路由以及一個簡單的 Controller
1 config.Routes.MapHttpRoute( 2 name: "DefaultApi", 3 routeTemplate: "api/{controller}/{id}", 4 defaults: new {id = RouteParameter.Optional} 5 ); 6 7 8 9 public class OrdersController : ApiController 10 { 11 public Order Get(int id) 12 { 13 // 忽略邏輯 14 } 15 }
在路由模板中,可以定義自己的占位符,如代碼片段 3-1 所示的 {id}。他與 Action 中的參數名稱相匹配匹配,ASP.NET WEB API 會從 request 中提取出相應的值,傳入 Action 方法中。也就是,代碼片段 3-1 中 OrdersController 的 Get請求方法的這種情
況。
工作原理
從最初版本開始,ASP.NET WEB API 就一直使用集中式路由維護路由表,這和 MVC 如出一轍。
ASP.NET WEB API 定義了很多 MapHttpRoute 的變種。所需參數最少的一個方法,只需要一個路由模板和一個路由名稱。
1 public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, 2 string routeTemplate)
除聲明簡單基礎路由以外的方式,也可以通過默認值和約束,或者設置每個路由消息處理程序,將會在下面的章節介紹這個。所有的這些操作都是通過 Map HttpRoute 的重載方法實現的。
1 public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, 2 string routeTemplate, object defaults) 3 4 public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, 5 string routeTemplate, object defaults, object constraints) 6 7 public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, 8 string routeTemplate, object defaults, object constraints, HttpMessageHandler handler)
通過路由默認值,可以直接定位到路由相關的 Controller;可是,這樣的特定路由需要定義在通用路由之前,也就是代碼片段 3-1 的路由之前。原因就是,路由是順序匹配的,路由在選定處理一個 HTTP 請求的時候,是在每次請求的時候,掃描路由集合中的所有路由,選用第一個被匹配的路由模板。簡單的說,越是特殊的路由定義越靠前,越是通用的路由定義越靠后。
在 Self-hosting 中,ASP.NET WEB API 是以 IDictionary<stirng, IHttpRoute> 的形式在維護路由,Idictionary<string, IHttpRoute> 是在 HttpRouteCollection 類中。在 Web host 中也提供了 System.Web.Routing.RouteCollection 的擴展方法,因此,可以直接在這個里面定義 ASP.NET WEB API 路由。
RouteTable.Routes.MapHttpRoute("DefaultApi", "api/{controller}")
如果使用 ASP.NET 運行時 host WEB API,無論使用什么擴展方法聲明路由,都會被添加到同一個 RouteTable 中。內部是通過一個叫做 HostedHttpRouteCollection 的類來實現的,這個類是 HttpRouteCollection 的子類,而不是在字典(比如 self-host)中維護路由,所有轉發路由都是查找 System.Web.RouteCollection。
ASP.NET WEB API 與 ASP.NET MVC 是不一樣的,API 使用的是基于 HTTP 謂詞匹配來處理一個 Controller 請求。換句話說,框架會根據 HTTP 的東西來選擇一個 Action 處理請求,選擇的邏輯如下:
- 通過方法名推斷 HTTP 謂詞,如果名字類似于 PostOrder、GetById 等等。
- 通過 Action 屬性推斷 HTTP 謂詞。
- Action 參數定義與路由模板必須匹配。
典型的 ASP.NET WEB API 路由是指向資源的。可以通過 HTTP 謂詞調用,還需要除了 Action 之外的參數。如代碼片段 3-1 所示的默認路由,可以匹配如下請求:
- GET myapi.com/api/orders
- POST myapi.com/api/inovice
- PUT myapi.com/api/invice/2
小提示 在 ASP.NET WEB API 是可以使用 RPC 的,具體細節會在 3-6 介紹。
代碼演示
ASP.NET WEB API 是基于 HTTP 謂詞進行分發邏輯的,不像 ASP.NET MVC,很容易出現 AmbiguousMatchEception。例如,可以設想一些使用代碼片段 3-1 的路由的例子。
如代碼片段 3-2 所示的例子,這三個方法都是可以處理相同的 GET 請求,如,/api/orders/1.
代碼片段 3-2.
代碼片段 3-2. ASP.NET WEB API Controller
1 public class BadOrdersController : ApiController 2 { 3 [HttpGet] 4 public Order FindById(int id) 5 { 6 // 忽略邏輯 7 } 8 9 public Order GetById(int id) 10 { 11 // 忽略邏輯 12 } 13 14 public Order Get(int id) 15 { 16 // 忽略邏輯 17 } 18 }
同時我們需要注意,定義復雜、多等級、嵌套路由的時候,集中式路由變得有點麻煩。考慮如下路由
- GET myapi.com/api/teams
- GET myapi.com/api/teams/1
- GET myapi.com/api/teams/1/players
使用集中式路由,可以在 Controller 中使用如下三個方法;然而,我們必須注意路由之間沖突的問題,因為有兩個 GET 方法都是只使用了一個 int 的參數。如代碼片段 3-3 所示。有一個特殊的路由指出了 URI 中包含 /players/ 段會被匹配到,而且這個路由定義在通用路由之前。
代碼片段 3-3 使用集中式路由配置嵌套路由
1 public class TeamsController : ApiController 2 { 3 public Team GetTeam(int id) 4 { 5 // 忽略邏輯 6 } 7 public IEnumerable<Team> GetTeams() 8 { 9 // 忽略邏輯 10 } 11 public IEnumerable<Player> GetPlayers(int teamId) 12 { 13 // 忽略邏輯 14 } 15 } 16 17 18 19 config.Routes.MapHttpRoute( 20 name: "players", 21 routeTemplate: "api/teams/{teamid}/players", 22 defaults: new {controller = "teams"} 23 ); 24 config.Routes.MapHttpRoute( 25 name: "DefaultApi", 26 routeTemplate: "api/{controller}/{id}", 27 defaults: new { id = RouteParameter.Optional } 28 );
集中式路由的主要問題是,特殊路由的定義,僅僅是處理特殊的 Controller 特殊的 Action。通過定義特殊路由并添加到一般路由之前,在人為干預下短路了路由匹配。
這種處理并不是最理想,當應用程序變大之后,會有更復雜的、多層級的路由,可能就要在集中式路由中痛苦的掙扎(路由維護和調試)。更好的選擇就是使用直接式路由,下一篇 3-2 定義直接路由,以及后面介紹路由的時候都會涉及直接式路由。
文章列表