【问题标题】:How does a method in MVC WebApi map to an http verb?MVC WebApi 中的方法如何映射到 http 动词?
【发布时间】:2012-05-13 11:04:32
【问题描述】:

在以下链接的 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
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

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

【问题讨论】:

  • 在WebApi中我认为你应该有一个控制器每个资源所以你不会有两个删除方法。
  • 谢谢Shane,这是很好的信息,但我的意思是特别像单记录方法和多记录方法。 GET 可能是一个更合适的示例,但您可能有一个方法通过 ID 获取一条记录,而另一种方法通过某个 FK 获取所有记录。但他们都使用 GET 动词;我不明白区分这些的路由智能存在于哪里。

标签: asp.net-mvc asp.net-mvc-routing asp.net-web-api


【解决方案1】:

这经常出现。对此有不同的看法。我个人目前还没有订阅任何特定的想法,但似乎每个资源一个控制器的想法在 REST 社区中最受欢迎。

所以基本上你可以:

  1. 在您的路由中公开操作(Web API 将词 action 视为类似于 MVC),但这通常不打算使用。
  2. 按照post定义不同参数的方法
  3. 正如我所说,建议使用每个资源一个控制器。因此,即使在 Web API 示例中,用于收集实体的控制器对于实体本身的控制器也是不同的。请阅读 Rob Conery 撰写的 posthere 是 Glenn 的回答。

【讨论】:

  • 感谢 Aliostad 的好文章和答案 - 但有一个后续:我专门询问这个问题(这在 WebApi 模板项目中的 ValuesController 类中的示例代码中出现)。我不认为这就是 Glenn 所说的,因为这一切都在一个控制器中:我忘记了这些 cmets 格式不正确。我已将要在此处粘贴的代码附加到我的原始帖子中。
【解决方案2】:

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

WebAPI 匹配语义
WebAPI(default 中的路由)使用的匹配语义相当简单。

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

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

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

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

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

注意:测试版的行为与上述“匹配多种方法”相同 - RC & Release 版本有点强迫症。如果有多个潜在匹配项,则会引发错误。此更改消除了多个不明确匹配的混淆。同时,它降低了我们在同一个控制器中混合 REST 和 RPC 样式接口的能力,依赖于顺序和重叠路由。

怎么办?
嗯,WebAPI 是新的,共识仍在凝聚。社区似乎对 REST 原则有相当多的了解。然而,并不是每个 API 都可以或应该是 RESTful 的,有些 API 更自然地以 RPC 风格表达。 REST 和人们所说的 REST 似乎是quite a bitof confusionwellat least toRoy Fielding 的来源。

作为一个实用主义者,我怀疑许多 API 将是 70% 的 RESTful,并带有少量 RPC 风格的方法。首先,单是控制器的扩散(考虑到 webapi 绑定方法)就会让开发人员发疯。其次,WebAPI 并没有真正内置方法来创建 api 路径的嵌套结构(意思是:/api/controller/ 很容易,但/api/CATEGORY/Sub-Category/Controller 是可行的,但很痛苦)。

从我的角度来看,我希望看到 webAPI 文件夹结构控制默认 API 路径...这意味着如果我在我的 UI 项目中创建一个 Category 文件夹,那么 /api/Category 将是默认路径(parallel to this MVC article) .

我做了什么?
所以,我有几个要求:(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 自然地根据文件夹设置类命名空间。因此Category1 文件夹中的Widget1 获得UI.Category1.Widget1 的默认命名空间。
  • 当然,我希望 api URL 反映文件夹结构 (/api/Category1/Widget)。您在上面看到的第一个映射实现了这一点,通过将 /api/Category1 硬编码到路由中,然后 namespace 令牌限制将搜索匹配控制器的类。

注意:自发布之日起,DataTokens 默认为 null。我不是 确定this is a bug, or a feature。所以我写了一个小帮手 方法并添加到我的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 在 cmets 中指出的那样,上述解决方案在 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;
}

【讨论】:

  • 感谢 EBarr,这很有帮助。最后你的解决方案有点超出我的想象,但第一部分肯定回答了我的问题,我认为最后一部分在我了解更多后重新审视将非常有用。我曾怀疑您的控制器类中的方法名称与 http 动词之间存在某种比较,但直到有人确认并告诉我它是如何工作的,我才真正相信自己的怀疑。谢谢!
  • @AndrewBSchultz - 很高兴为您提供帮助。 ..发布更好的解决方案说明。
  • 这是一个非常有用的答案,谢谢!只是一个简单的问题,而不是使用带有“命名空间”数据令牌的自定义路由,您可以使用传统的 MVC“区域”吗?
  • 如果您想将 url 分成子文件夹(或者我听说它被称为“按命名空间”),例如 localhost:xxxx/api/mySubfolder/myController,并且您将控制器放在控制器下的子文件夹中,您可以使用 {小路}。示例:---- 文件:MyProject\Controllers\Users\AdminUsersController.cs---- 映射到 uri:localhost:xxxx/api/Users/AdminUsers---- 路由代码:RouteTable.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{path }/{controller}/{id}",默认值:new { id = RouteParameter.Optional });
  • 在 Web Api 2 中,IHttpRoute.DataTokens 没有设置器,因此此解决方案不再有效。我开始得到暗示,Web Api 真的不想适应这种路由风格。您始终可以将控制器组织到文件夹中,并且可以定义访问它们的路由,但是它们仍然可以通过默认路由访问,并且重复名称会引发运行时异常。
猜你喜欢
  • 1970-01-01
  • 2017-08-09
  • 1970-01-01
  • 2013-10-06
  • 2012-08-06
  • 2017-08-12
  • 2013-10-30
  • 2023-03-15
  • 2013-12-20
相关资源
最近更新 更多