最近一直在糾結應該創建RESTFul風格的API還是以前那種函數調用風格的API。如果創建RESTFul風格的API,又有很多設計問題有待理清,這暫且不論,在用Web API創建RESTFul風格的API的時候,對於二級實體操作又該如何設計API接口呢?比如一個Client實體,它有很多屬於它的Order實體,而每個Order實體又有很多Product實體,API接口如何設計才能更好的體現這種關系和操作呢?如果大家對此有想法,歡迎留言為我解惑。
我目前嘗試設計和實現一種層次性的API接口,我不確定這是否是最佳的做法,調用的時候看起來是這樣的:
/api/Clients/123/Orders/456/Products/789
Route看起來是這樣的:
/api/{controller}/{id}/{subController1}/{subID1}/{subController2}/{subID2}
當然,需要的話,可以繼續往后追加subController3,4,5,6...
而Controller應該看起來是什么樣子的呢?我的做法是,分別為Client、Order和Product建立Controller:
ClientsController
ClientsOrdersController
ClientsOrdersProductsConroller
這樣我可以將以上3個Controller的名字映射到{controller}、{subController1}、{subController2},抽象一點說,就是Controller名字的每一部分對應映射中的一個{controller}/{subcontrollerX}.
剩下的一個問題就是如何讓MVC引擎能將這個路由映射到正確的Controller上。我們都知道(其實我們不都知道,包括在研究這個問題之前的我),MVC引擎根據路由找Controller的操作之一要靠IHttpControllerSelector接口,默認情況下具體干活的就是DefaultHttpControllerSelector類,我要做的就是繼承這個類,然后從路由數據中查看那些subController參數是否被映射上的具體數據,如果沒有,就調用默認行為,如果有,就把所有的controller參數的值拼接成真正的controller名字返回,代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web.Http; 5 using System.Web.Http.Dispatcher; 6 using System.Net.Http; 7 using System.Web.Http.Routing; 8 9 namespace Ricky 10 { 11 public class HttpControllerSelectorEx : DefaultHttpControllerSelector 12 { 13 private HttpConfiguration _HttpCfg; 14 15 public HttpControllerSelectorEx(HttpConfiguration cfg) 16 : base(cfg) 17 { 18 _HttpCfg = cfg; 19 } 20 21 public override string GetControllerName(HttpRequestMessage request) 22 { 23 string name = base.GetControllerName(request); 24 IHttpRouteData routeData = request.GetRouteData(); 25 IEnumerable<KeyValuePair<string, object>> subControllers = routeData.Values 26 .Where(d => d.Key.StartsWith("subController", StringComparison.CurrentCultureIgnoreCase)); 27 if (subControllers.Count() == 0) 28 return name; 29 30 List<string> names = new List<string>(1 + subControllers.Count()); 31 names.Add(name); 32 foreach (KeyValuePair<string,object> subController in subControllers) 33 { 34 names.Add(subController.Value.ToString()); 35 } 36 37 return string.Join("", names); 38 } 39 } 40 }
完成之后需要用我們的controller selector替換系統的默認設置。這在Global.asax.cs的Application_Start方法中做:
1 GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector) 2 , new Ricky.HttpControllerSelectorEx(GlobalConfiguration.Configuration));
最后,我們修改一下路由設置:
1 public static void Register(HttpConfiguration config) 2 { 3 config.Routes.MapHttpRoute( 4 name: "DefaultApiWithSubControllers", 5 routeTemplate: "api/{controller}/{id}/{subController}/{subID}/{subController2}/{subID2}", 6 defaults: new 7 { 8 id = RouteParameter.Optional, 9 subController = RouteParameter.Optional, 10 subID = RouteParameter.Optional, 11 subController2 = RouteParameter.Optional, 12 subID2 = RouteParameter.Optional 13 } 14 ); 15 16 //config.Routes.MapHttpRoute( 17 // name: "DefaultApi", 18 // routeTemplate: "api/{controller}/{id}", 19 // defaults: new { id = RouteParameter.Optional } 20 //); 21 }
由於我們的路由設置其實是擴展默認設置的,因此可以注釋掉原來的默認設置。