我提前道歉,这篇文章与你的要求有点偏离,但是当我读到你的问题时,所有这些都冒了出来。
WebAPI 匹配语义 WebAPI
(默认路由)使用的匹配语义相当简单。
- 它将动作的名称与动词匹配(动词 = GET?查找以“get”开头的方法名称)
- 如果传递了参数,则 api 会寻找带有参数的操作
因此,在您的代码示例中,不带参数的 GET 请求与不带参数的Get*( )
函数匹配。包含和 ID 的 Get 查找Get***(int id)
.
示例
虽然匹配语义很简单,但它给 MVC 开发人员(至少这个开发人员)造成了一些混乱。让我们看一些例子:
奇数名称- 您的 get 方法可以命名为任何名称,只要它以“get”开头。因此,在小部件控制器的情况下,您可以命名您的功能GetStrawberry()
,它仍然会匹配。将匹配想象为: methodname.StartsWith("Get")
多种匹配方法- 如果您有两个没有参数的 Get 方法会发生什么? GetStrawberry()
和GetOrange()
。据我所知,代码中首先定义的函数(文件顶部)胜出……奇怪。这具有使控制器中的某些方法无法访问的副作用(至少使用默认路由)......陌生人。
注意:测试版在“匹配多种方法”方面的表现与上述相同——RC 和发布版本有点强迫症。如果有多个潜在匹配项,则会引发错误。此更改消除了多个不明确匹配的混淆。同时,它降低了我们在同一个控制器中混合 REST 和 RPC 样式接口的能力,依赖于顺序和重叠的路由。
该怎么办?
嗯,WebAPI 是新的,共识仍在凝聚。社区似乎对 REST 原则有相当多的了解。然而,并不是每个 API 都可以或应该是 RESTful 的,有些 API 更自然地以 RPC 风格表达。REST 和人们所说的 REST 似乎是相当 混乱的根源, 至少对 Roy Fielding来说是这样。
作为一个实用主义者,我怀疑许多 API 将是 70% 的 RESTful,带有少量 RPC 风格的方法。首先,单是控制器的扩散(考虑到 webapi 绑定方法)就会让开发人员发疯。其次,WebAPI 并没有真正内置方法来创建 api 路径的嵌套结构(意思是: /api/controller/
很容易,但/api/CATEGORY/Sub-Category/Controller
可行,但很痛苦)。
从我的角度来看,我希望看到 webAPI 文件夹结构控制默认的 API 路径......这意味着如果我在我的 UI 项目中创建一个 Category 文件夹,那么/api/Category
它将是默认路径(与这篇 MVC 文章平行的东西)。
我做了什么?
所以,我有几个要求:(1) 在大多数情况下能够使用 restful 语法,(2) 有一些控制器的“命名空间”分离(想想子文件夹),(3)能够调用额外的 rpc-必要时喜欢方法。实现这些要求归结为巧妙的路由。
// SEE NOTE AT END ABOUT DataToken change from RC to RTM
Route r;
r = routes.MapHttpRoute( name : "Category1",
routeTemplate : "api/Category1/{controller}/{id}",
defaults : new { id = RouteParameter.Optional } );
r.DataTokens["Namespaces"] = new string[] {" UI.Controllers.Category1"};
r = routes.MapHttpRoute( name : "Category2",
routeTemplate : "api/Category2/{controller}/{id}",
defaults : new { id = RouteParameter.Optional } );
r.DataTokens["Namespaces"] = new string[] {" UI.Controllers.Category2"};
routes.MapHttpRoute( name : "ApiAllowingBL",
routeTemplate : "api/{controller}/{action}/{id}",
defaults : new { id = RouteParameter.Optional } );
routes.MapHttpRoute( name : "DefaultApi",
routeTemplate : "api/{controller}/{id}",
defaults : new { id = RouteParameter.Optional } );
- 前两条路线创建“子文件夹”路线。我需要为每个子文件夹创建一个路由,但我将自己限制在主要类别中,所以我最终只得到了其中的 3-10 个。请注意这些路由如何添加
Namespace
数据标记,以限制搜索特定路由的类。当您将文件夹添加到 UI 项目时,这很好地对应于典型的命名空间设置。
- 第三条路线允许调用特定的方法名称(如传统的 mvc)。由于 Web API 取消了 URL 中的操作名称,因此可以相对容易地判断哪些调用需要此路由。
- 最后一个路由条目是默认的 web api 路由。这会捕获任何类,尤其是我的“子文件夹”之外的类。
说另一种方式
我的解决方案归结为更多地分离控制器,因此/api/XXXX
不会变得太拥挤。
- 我在我的 UI 项目中创建了一个文件夹(比如说
Category1
),并将 api 控制器放在该文件夹中。
- Visual Studio 自然地根据文件夹设置类命名空间。所以
Widget1
在Category1
文件夹中获得一个默认命名空间UI.Category1.Widget1
.
- 自然,我希望 api URL 能够反映文件夹结构 (
/api/Category1/Widget
)。您在上面看到的第一个映射实现了这一点,通过硬编码/api/Category1
到路由中,然后namespace
令牌限制将搜索匹配控制器的类。
注意:在发布DataTokens
时默认为空。我不确定这是错误还是功能。所以我写了一个小助手方法并添加到我的RouteConfig.cs
文件中......
r.AddRouteToken("Namespaces", new string[] {"UI.Controllers.Category1"});
private static Route AddRouteToken(this Route r, string key, string[] values) {
//change from RC to RTM ...datatokens is null
if (r.DataTokens == null) {
r.DataTokens = new RouteValueDictionary();
}
r.DataTokens[key] = values;
return r;
}
注意 2:即使这是 WebAPI 1 帖子,正如 @Jamie_Ide 在评论中指出的那样,上述解决方案在 WebAPI 2 中不起作用,因为IHttpRoute.DataTokens
没有设置器。要解决此问题,您可以使用如下简单的扩展方法:
private static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, string routeTemplate, object defaults, object constraints, string[] namespaceTokens)
{
HttpRouteValueDictionary defaultsDictionary = new HttpRouteValueDictionary(defaults);
HttpRouteValueDictionary constraintsDictionary = new HttpRouteValueDictionary(constraints);
IDictionary<string, object> tokens = new Dictionary<string, object>();
tokens.Add("Namespaces", namespaceTokens);
IHttpRoute route = routes.CreateRoute(routeTemplate, defaultsDictionary, constraintsDictionary, dataTokens: tokens, handler:null);
routes.Add(name, route);
return route;
}