MVC WebApi中的方法如何映射到一个http动词?
在下面链接的5分钟video中,Jon Galloway说,在他的CommentsController控制器类中添加一个名为DeleteComment的方法会按约定自动映射到删除http动词。
MVC与WebApi如何知道如何将方法路由到正确的动词? 我知道global.asax.cs文件中的路由将请求路由到正确的控制器,但是如何将删除请求“按照约定映射到”删除方法或任何方法? 特别是当每个动词可以有多于一种的方法? “按照惯例”使我认为它只是查看方法名称中的第一个单词…但是如果是这样,它将不得不读取方法的签名,以告诉两个删除方法或两个get方法分开……并且在哪里是所有这些定义?
video: http : //www.asp.net/web-api/videos/getting-started/delete-and-update
谢谢!
编辑:这是WebApi模板中的示例ValuesController类中的代码。 这是我原来的问题的来源。 区分这些(和控制器中的其他方法)的“惯例”是如何工作的?
// GET /api/values public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; } // GET /api/values/5 public string Get(int id) { return value; }
我提前道歉,这篇文章有些偏离了你提出的问题,但是当我读到你的问题时,所有这一切都冒出来了。
WebAPI匹配语义
(WebAPI中的默认路由)使用的匹配语义相当简单。
- 它将动作的名称与动词相匹配(动词= GET?查找以“get”开头的方法名称)
- 如果传递了一个参数,那么api会用参数寻找一个动作
因此,在您的代码示例中,没有参数的GET请求与不带参数的Get*( )
函数相匹配。 获取包含和ID查找Get***(int id)
。
例子
虽然匹配的语义很简单,但它为MVC开发人员(至less是这个开发人员)造成了一些困惑。 让我们看一些例子:
奇数名称 – 只要以“get”开头,您的get方法就可以被命名。 所以在一个小部件控制器的情况下,你可以命名你的函数GetStrawberry()
,它仍然会被匹配。 将匹配看作是类似于: methodname.StartsWith("Get")
多种匹配方法 – 如果你有两个没有参数的Get方法会发生什么? GetStrawberry()
和GetOrange()
。 最好的我可以告诉,在你的代码中首先定义的函数(文件的顶部)胜利…奇怪。 这有副作用,使你的控制器中的某些方法无法访问(至less与默认路由)….陌生人。
注意 :testing版的“匹配多种方法”的performance与上述相同–RC版本和发布版本更多一些OCD。 如果有多个潜在的匹配,它会抛出一个错误。 这一改变消除了多个模糊匹配的混乱。 同时,它减less了我们在同一个控制器中混合使用REST和RPC风格接口的能力,依赖于顺序和重叠路由。
该怎么办?
那么,WebAPI是新的,共识还在凝聚。 这个社区似乎正在为REST原则做些相当的工作。 然而,并不是每个API都可以或者应该是RESTful,有些更自然地以RPC风格expression。 REST和人们所说的REST似乎是相当 混乱的根源 , 至less对 Roy Fielding来说是这样 。
作为一个实用主义者,我怀疑许多API将会有70%的REST风格,还有一些RPC风格的方法。 首先,控制器的增殖本身(鉴于webapi绑定方法)将驱使开发人员疯狂。 其次,WebAPI实际上并没有内置的方法来创buildapipath的嵌套结构(这意味着: /api/controller/
很容易,但/api/CATEGORY/Sub-Category/Controller
是可行的,但是很痛苦)。
从我的angular度来看,我很想看到webAPI文件夹结构控制默认的APIpath…意思是如果我在我的UI项目中创build一个类别文件夹然后/api/Category
将是默认path( 平行于这个MVC文章 ) 。
我做了什么?
所以,我有一些要求:(1)在大多数情况下能够使用宁静的语法,(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 } );
- 前两个路线创build“子文件夹”路线。 我需要为每个子文件夹创build一个path,但是我限制了自己的主要类别,所以最终只有3-10个。 请注意这些路由如何添加
Namespace
数据标记,以限制search特定路由的类。 这很好地符合典型的命名空间设置,因为您将文件夹添加到UI项目。 - 第三个路由允许调用特定的方法名称(如传统的mvc)。 由于Web API在URL中消除了操作名称,因此可以相对容易地告诉哪些呼叫需要此路由。
- 最后一个路由条目是默认的web api路由。 这捕获了任何类,特别是我的“子文件夹”之外的类。
说另一种方式
我的解决scheme归结为分离控制器多一点,所以/api/XXXX
不会太拥挤。
- 我在我的UI项目中创build一个文件夹(可以说
Category1
),并将api控制器放在文件夹中。 - Visual Studio自然会根据文件夹设置类名称空间。 因此,
Category1
文件夹中的Widget1
将获取UI.Category1.Widget1
的默认名称空间。 - 当然,我希望APIurl能反映文件夹结构(
/api/Category1/Widget
)。 上面看到的第一个映射完成了,通过在路由中硬编码/api/Category1
,namespace
标记将限制将search匹配控制器的类。
注意 :从发行版开始,
DataTokens
默认为空。 我不确定这是一个错误还是一个function 。 所以我写了一个小帮手方法,并添加到我的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的post,因为@Jamie_Ide在评论中指出上述解决scheme在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; }
这经常出现。 对此有不同的看法。 我个人目前还没有订阅任何特定的想法,但它似乎是一个单一资源控制器在REST社区中最受欢迎的。
所以基本上你可以:
- 在路由中公开action(Web API将类似于MVC的单词
action
视为对象),但通常不打算使用此操作。 - 根据这篇文章定义具有不同参数的方法
- 正如我所说的, 大多数build议每个资源使用一个控制器 。 因此,即使在Web API示例中,实体自身的控制器对于实体的收集控制器也是不同的。 阅读这篇文章由罗伯康里, 这里是格伦的答案。