MVC WebApi 中的方法如何映射到 http 动词? [英] How does a method in MVC WebApi map to an http verb?

查看:38
本文介绍了MVC WebApi 中的方法如何映射到 http 动词?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在以下链接的 5 分钟视频中,在 1:10 标记处,Jon Galloway 说将一个名为 DeleteComment 的方法添加到他的 CommentsController 控制器类将按照约定自动映射到删除 http 动词.

带有 WebApi 的 MVC 如何知道如何将方法路由到正确的动词?我知道 global.asax.cs 文件中的路由将请求路由到正确的控制器,但是删除请求如何按约定映射"到删除方法或任何方法?特别是当每个动词可以有不止一种方法时?按惯例"让我认为它只是查看方法名称中的第一个单词......但如果是这样,它必须阅读方法的签名来告诉两个删除方法或两个获取方法分开......以及在哪里这都定义了吗?

视频:http://www.asp.net/web-api/videos/getting-started/delete-and-update

谢谢!

这是 WebApi 模板中的示例 ValuesController 类中的代码.这是我最初问题的来源.区分这些(以及控制器中的任何其他方法)的约定"如何工作?

//GET/api/values公共 IEnumerable得到(){返回新字符串[] { "value1", "value2" };}//获取/api/values/5公共字符串 Get(int id){返回值;}

解决方案

我提前道歉,这篇文章与您的要求有所不同,但是当我阅读您的问题时,所有这些都冒出来了.

WebAPI 匹配语义
(默认 路由)WebAPI 使用的匹配语义相当简单.

  1. 它将动作名称与动词匹配(动词 = GET?查找以get"开头的方法名称)
  2. 如果传递了一个参数,api 会寻找一个带有参数的动作

因此,在您的代码示例中,不带参数的 GET 请求与不带参数的 Get*( ) 函数相匹配.Get 包含和 ID 查找 Get***(int id).

示例
虽然匹配语义很简单,但它给 MVC 开发人员(至少是这个开发人员)造成了一些困惑.让我们看一些例子:

奇怪的名字 - 你的 get 方法可以命名为任何名字,只要它以get"开头.因此,在小部件控制器的情况下,您可以将函数命名为 GetStrawberry() 并且它仍然会匹配.将匹配视为类似:methodname.StartsWith("Get")

多种匹配方法 - 如果您有两个没有参数的 Get 方法会怎样?GetStrawberry()GetOrange().尽我所知,在您的代码中首先定义的函数(文件顶部)获胜......奇怪.这会产生副作用,使您的控制器中的某些方法无法访问(至少使用默认路由)......陌生人.

<块引用>

注意:测试版的匹配多种方法"表现如上 - RC &发行版有点强迫症.如果存在多个潜在匹配项,则会引发错误.此更改消除了多个不明确匹配的混淆.同时,它降低了我们在同一个控制器中混合 REST 和 RPC 风格的接口的能力,依赖于顺序 &重叠路线.

怎么办?
好吧,WebAPI 是新的,共识仍在凝聚.社区似乎对 REST 原则有相当多的了解.然而,并非每个 API 都可以或应该是 RESTful,有些更自然地以 RPC 风格表达.休息&人们所说的 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-必要时喜欢方法.实现这些要求归结为巧妙的路由.

//有关 DataToken 从 RC 更改为 RTM 的注意事项路线 r;r = routes.MapHttpRoute( name : "Category1",routeTemplate : "api/Category1/{controller}/{id}",默认值:新 { id = RouteParameter.Optional } );r.DataTokens["Namespaces"] = new string[] {" UI.Controllers.Category1"};r = routes.MapHttpRoute( name : "Category2",routeTemplate : "api/Category2/{controller}/{id}",默认值:新 { id = RouteParameter.Optional } );r.DataTokens["Namespaces"] = new string[] {" UI.Controllers.Category2"};路线.MapHttpRoute(名称:ApiAllowingBL",routeTemplate : "api/{controller}/{action}/{id}",默认值:新 { id = RouteParameter.Optional } );routes.MapHttpRoute( name : "DefaultApi",routeTemplate : "api/{controller}/{id}",默认值:新 { id = RouteParameter.Optional } );

  • 前两条路由创建子文件夹"路由.我需要为每个子文件夹创建一个路径,但我将自己限制在主要类别中,所以我最终只得到其中的 3-10 个.请注意这些路由如何添加 Namespace 数据标记,以限制搜索特定路由的类.这与您将文件夹添加到 UI 项目时的典型命名空间设置非常吻合.
  • 第三条路线允许调用特定的方法名称(如传统的 mvc).由于 Web API 取消了 URL 中的操作名称,因此相对容易判断哪些调用需要此路由.
  • 最后一个路由条目是默认的 web api 路由.这会捕获任何类,尤其是我的子文件夹"之外的类.

另一种说法
我的解决方案归结为更多地分离控制器,以便 /api/XXXX 不会变得太拥挤.

  • 我在我的 UI 项目中创建了一个文件夹(比如 Category1),并将 api 控制器放在该文件夹中.
  • Visual Studio 自然会根据文件夹设置类命名空间.所以Category1 文件夹中的Widget1 获得了一个默认的UI.Category1.Widget1 命名空间.
  • 当然,我希望 api URL 能够反映文件夹结构 (/api/Category1/Widget).您在上面看到的第一个映射实现了这一点,通过将 /api/Category1 硬编码到路由中,然后 namespace 标记限制将搜索匹配控制器的类.
<块引用>

注意:从发布开始,DataTokens 默认为空.我不是确定这是错误还是功能.所以我写了一个小帮手方法并添加到我的 RouteConfig.cs 文件....

r.AddRouteToken("Namespaces", new string[] {"UI.Controllers.Category1"});私有静态路由 AddRouteToken(此路由 r,字符串键,字符串 [] 值){//从RC更改为RTM ...datatokens为空如果(r.DataTokens == null){r.DataTokens = new RouteValueDictionary();}r.DataTokens[key] = 值;返回 r;}

<块引用>

注意 2:即使认为这是一篇 WebAPI 1 帖子,正如@Jamie_Ide 在评论中指出的,上述解决方案在 WebAPI 2 中不起作用,因为 IHttpRoute.DataTokens 没有二传手.为了解决这个问题,您可以使用一个简单的扩展方法,如下所示:

private static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, string routeTemplate, object defaults, object constraint, string[] namespaceTokens){HttpRouteValueDictionary defaultsDictionary = new HttpRouteValueDictionary(defaults);HttpRouteValueDictionary constraintDictionary = new HttpRouteValueDictionary(constraints);IDictionary<字符串,对象>令牌 = 新字典<字符串,对象>();token.Add("Namespaces", namespaceTokens);IHttpRoute route = routes.CreateRoute(routeTemplate, defaultsDictionary, constraintDictionary, dataTokens: tokens, handler:null);路线.添加(名称,路线);回程路线;}

In the 5-minute video at the following link, at the 1:10 mark, Jon Galloway says that adding a method called DeleteComment to his CommentsController controller class will by convention map automatically to the delete http verb.

How does MVC with WebApi know how to rout the methods to the right verbs? I know the routing in the global.asax.cs file routes requests to the correct controller, but how does a delete request get "mapped by convention" to the delete method, or any method? Especially when there can be more than 1 method for each verb? "By convention" makes me think it's just looking at the first word in a method name ... but if so, it would have to read the signature of the methods to tell two delete methods or two get methods apart ... and where is all this defined?

Video: http://www.asp.net/web-api/videos/getting-started/delete-and-update

Thanks!

Edit: Here is the code in the sample ValuesController class that comes in the WebApi template. This was the source of my original question. How does the "convention" that differentiates between these (and any other methods in the controller) work?

// GET /api/values
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

    // GET /api/values/5
    public string Get(int id)
    {
        return value;
    }

解决方案

I apologize in advance, this post strays a bit from what you asked, but all of this bubbled up when I read your question.

WebAPI Matching Semantic
The matching semantic used by (the default routes in) WebAPI is fairly simple.

  1. It matches the name of the action with the verb (verb = GET? look for method name starting with "get")
  2. if a parameter is passed, the api seeks an action with a parameter

So in your code sample a GET request without a parameter matches the Get*( ) function without an parameters. A Get containing and ID looks for a Get***(int id).

Examples
While the matching semantic is simple, it creates some confusion for MVC developers (well at least this developer). Lets look at some examples :

Odd Names - Your get method can be named anything, so long as it starts with "get". So in the case of a widget controller you can name your functions GetStrawberry() and it will still be matched. Think of the matching as something like : methodname.StartsWith("Get")

Multiple Matching Methods - What happens if you have two Get methods with no parameters? GetStrawberry() and GetOrange(). As best I can tell, the function defined first (top of the file) in your code wins ...strange. This has the side effect of making some methods in your controller unreachable (at least with the default routes)....stranger.

NOTE : the beta behaved as above for 'matching multiple methods' - the RC & Release version is a bit more OCD. It throws an error if there are multiple potential matches. This change removes the confusion of multiple ambiguous matches. At the same time, it reduces our ability to mix REST and RPC style interfaces in the same controller, relying on the order & overlapping routes.

What to do?
Well, WebAPI is new and consensus is still coalescing. The community seems to be reaching for REST principles quite a bit. Yet, not every API can or should be RESTful, some are more naturally expressed in an RPC style. REST & what people call REST seems to be the source of quite a bit of confusion, well at least to Roy Fielding.

As a pragmatist, i suspect that many API's will be 70% RESTful, with a smattering of RPC style methods. First, the the controller proliferation alone (given the webapi binding method) is going to drive developers bonkers. Second, WebAPI doesn't really have a built in way to create a nested structure of api paths (meaning: /api/controller/ is easy, but /api/CATEGORY/Sub-Category/Controller is doable, but a pain).

From my perspective, I would love to see the webAPI folder structure control the default API paths... meaning if I create a Category folder in my UI project then /api/Category would be the default path (something parallel to this MVC article).

What did I do?
So, I had a few requirements: (1) to be able to use restful syntax in most case, (2) have some "namespace" separation of controllers (think sub-folders), (3) be able to call additional rpc-like methods when necessary. Implementing these requirements came down to clever routing.

// 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 } );

  • The first two routes create "sub-folder" routes. I need to create a route for each sub-folder, but I limited myself to major categories, so I only end up with 3-10 of these. Notice how these routes add the Namespace data token, to restrict what classes are searched for a particular route. This corresponds nicely to the typical namespace setup as you add folders to a UI project.
  • The third route allows specific method names to be called (like traditional mvc). Since web API does away with action name in the URL, it's relatively easy to tell which calls want this route.
  • The last route entry is the default web api route. This catches any classes, particularly ones outside my 'sub-folders'.

Said Another Way
My solution came down to down to separating controllers a bit more so /api/XXXX didn't get too crowded.

  • I create a folder in my UI project(lets say Category1), and put api controllers within the folder.
  • Visual studio naturally sets class namespaces based on folder. So Widget1 in the Category1 folder gets a default namespace of UI.Category1.Widget1.
  • Naturally, I wanted api URLs to reflect the folder structure (/api/Category1/Widget). The first mapping you see above accomplishes that, by hard coding /api/Category1 into the route, then the namespace token restricts classes that will be searched for a matching controller.

NOTE: as of the release DataTokens are null by default. I'm not sure if this is a bug, or a feature. So I wrote a little helper method and added to my RouteConfig.cs file....

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;
}

NOTE 2: even thought this is a WebAPI 1 post, as @Jamie_Ide points out in the comments the above solution doesn't work in WebAPI 2 because IHttpRoute.DataTokens has no setter. To get around this you can use a simple extension method like this:

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;
}

这篇关于MVC WebApi 中的方法如何映射到 http 动词?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆