是否有可能在ASP.NET MVC中实现X-HTTP-Method-Override?

问题描述:

我正在实现一个使用ASP.NET MVC的RESTful API的原型,除了这里和那里的奇怪的错误之外,我已经实现了我在开始时设置的所有要求,除了来电者能够使用X-HTTP-Method-Override自定义头来覆盖HTTP方法。是否有可能在ASP.NET MVC中实现X-HTTP-Method-Override?

我想的是,下面的请求......

GET /someresource/123 HTTP/1.1 
X-HTTP-Method-Override: DELETE 

...将被分派到实现DELETE的功能,而不是GET功能为行动(假设我的控制器方法有多种方法可以实现该操作,并且它们被标记为具有不同的[AcceptVerbs]属性)。因此,考虑以下两种方法,我想上述请求分派到第二个:

[ActionName("someresource")] 
[AcceptVerbs(HttpVerbs.Get)] 
public ActionResult GetSomeResource(int id) { /* ... */ } 

[ActionName("someresource")] 
[AcceptVerbs(HttpVerbs.Delete)] 
public ActionResult DeleteSomeResource(int id) { /* ... */ } 

有谁知道这是可能的吗?这样做会有多少工作......?

您将无法使用的[AcceptVerbs]属性的,是因为它绑定到请求的实际的HTTP动词。幸运的是[AcceptVerbs]属性非常简单;您可以在http://www.codeplex.com/aspnet/SourceControl/changeset/view/21528#266431处查看源代码。

总之,子类AcceptsVerbsAttribute并重写IsValidForRequest()方法。实施将如下所示:

string incomingVerb = controllerContext.HttpContext.Request.Headers["X-HTTP-Method-Override"] ?? controllerContext.HttpContext.Request.Method; 
return Verbs.Contains(incomingVerb, StringComparer.OrdinalIgnoreCase); 

X-HTTP-Method-Override是一个自定义标题,很可能不受Web容器支持。

你是从网页打这个电话吗?如果是这样,你应该使用XmlHttpRequestDELETE(或者你想要的任何动词)。更好的是,使用JS框架来为你完成繁重的工作。

+0

我不会打电话了,我实现了它,而且我想使实现支持自定义头。 – 2009-01-21 23:59:50

+0

我不明白。如果你控制这个实现,为什么你不会像使用标准动词那样使用HTTP? – Kevin 2009-01-22 01:30:16

+2

因为不幸的是,有些客户端,代理服务器和防火墙不允许GET或POST以外的动词,所以有时候人们需要能够使用GET和PUT来伪造DELETE。这有点蹩脚,但大多数RESTful服务似乎都支持这个头文件。 – 2009-01-22 08:47:15

您可以创建一个ActionFilter实现OnActionExecuting,该控件在控制器操作被调用之前触发。然后,您可以询问请求标题,并根据X-HTTP-Method-Override标题的值(如果存在)重定向。

+0

在那一点上它不会已经完成了它认为它将要调用的动作的参数绑定?这些方法可能有不同的参数。我认为,也许我需要在动作被选中之前挂钩? – 2009-01-22 08:56:28

你看过Simply Restful Routing?它已经这样做了。

编辑2010年2月加入:方法重写内置到MVC 2

+0

我没有看到它实现X-HTTP-Method-Override的任何事情,并且对源代码的快速扫描不会发现任何内容。你确定吗?如果可以的话,你可以指向我在其中实现的文件?干杯! – 2009-01-22 20:24:25

列维的答案是伟大的。此外,我还在检查FORM集合的自定义AcceptsVerbsAttribute中添加了一个检查,因此您可以简单地放置一个隐藏的输入来触发DELETE(类似于MVC 2的Html.HttpMethodOverride(HttpVerbs.Delete))。

<input name="X-HTTP-Method-Override" type="hidden" value="DELETE" /> 

更改incomingVerb分配:

string incomingVerb = controllerContext.HttpContext.Request.Headers["X-HTTP-Method-Override"] ?? controllerContext.HttpContext.Request.Form["X-HTTP-Method-Override"] ??controllerContext.HttpContext.Request.HttpMethod; 

要小心,这种方法!查看Stephen Walther的related post

希望这可以帮助别人。

插入形成:

<%= Html.HttpMethodOverride(HttpVerbs.Delete) %> 

这次谈话是有点老了,但我想和大家分享我已经使用MVC 2发现:

浏览器都支持两个HTTP动词:GET和POST,但ASP.NET MVC 2允许你使用Html.HttpMethodOverride helper方法来模拟Put,Get和Delete。在内部,这通过在X-HTTP-Method-Override表单字段中发送动词来实现。 HttpMethodOverride的行为所使用的作为新的短动词属性的[AcceptVerbs]属性,以及:

例如,行动宣言:

[ActionName("someresource")] 
[HttpDelete] 
public ActionResult DeleteSomeResource() 

应该采取具有X你get请求责任-HTTP-Method-Override设置为Delete。

令人惊讶的是,这还没有被提及,但ASP.NET MVC本身支持X-HTTP-Method-Override,并且至少从版本2开始这样做。没有必要编写自定义代码处理这个(事情。

它以下列方式工作:

内AcceptVerbsAttribute(也由[HttpPut],[HttpPost]等为代表),有一个IsValidForRequest方法。内部的方法,它检查以Request.GetHttpMethodOverride(),它返回用以下条件适当被覆盖的HTTP方法:

  • 重写只在POST请求支持。所有其他人都被忽略。
  • 如果X-HTTP-Method-Override的值是GET或POST,它将被忽略。这是有道理的,因为你永远不需要用这些值覆盖。
  • 它寻找X-HTTP-方法,覆盖在这个优先级以下地方: 1)HTTP头 2)表体 3)查询字符串

如果你真的很好奇,这里的GetHttpMethodOverride()的外观(从MVC 3的源代码):

public static class HttpRequestExtensions { 
    internal const string XHttpMethodOverrideKey = "X-HTTP-Method-Override"; 

    public static string GetHttpMethodOverride(this HttpRequestBase request) { 
     if (request == null) { 
      throw new ArgumentNullException("request"); 
     } 

     string incomingVerb = request.HttpMethod; 

     if (!String.Equals(incomingVerb, "POST", StringComparison.OrdinalIgnoreCase)) { 
      return incomingVerb; 
     } 

     string verbOverride = null; 
     string headerOverrideValue = request.Headers[XHttpMethodOverrideKey]; 
     if (!String.IsNullOrEmpty(headerOverrideValue)) { 
      verbOverride = headerOverrideValue; 
     } 
     else { 
      string formOverrideValue = request.Form[XHttpMethodOverrideKey]; 
      if (!String.IsNullOrEmpty(formOverrideValue)) { 
       verbOverride = formOverrideValue; 
      } 
      else { 
       string queryStringOverrideValue = request.QueryString[XHttpMethodOverrideKey]; 
       if (!String.IsNullOrEmpty(queryStringOverrideValue)) { 
        verbOverride = queryStringOverrideValue; 
       } 
      } 
     } 
     if (verbOverride != null) { 
      if (!String.Equals(verbOverride, "GET", StringComparison.OrdinalIgnoreCase) && 
       !String.Equals(verbOverride, "POST", StringComparison.OrdinalIgnoreCase)) { 
       incomingVerb = verbOverride; 
      } 
     } 
     return incomingVerb; 
    } 
}