现在的位置: 首页 > 综合 > 正文

使Web API支持二级实体操作,兼对RESTFul风格API设计的疑惑。

2012年07月05日 ⁄ 综合 ⁄ 共 2816字 ⁄ 字号 评论关闭

最近一直在纠结应该创建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 }

由于我们的路由设置其实是扩展默认设置的,因此可以注释掉原来的默认设置。

抱歉!评论已关闭.