7

我正在尝试按照此处描述的步骤实现 HTTP 方法覆盖。基本上,我正在创建一个 DelegatingHandler,类似于以下内容,并将其作为消息处理程序添加到Application_Start.

public class MethodOverrideHandler : DelegatingHandler      
{
    readonly string[] _methods = { "DELETE", "HEAD", "PUT" };
    const string _header = "X-HTTP-Method-Override";

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // Check for HTTP POST with the X-HTTP-Method-Override header.
        if (request.Method == HttpMethod.Post && request.Headers.Contains(_header))
        {
            // Check if the header value is in our methods list.
            var method = request.Headers.GetValues(_header).FirstOrDefault();
            if (_methods.Contains(method, StringComparer.InvariantCultureIgnoreCase))
            {
                // Change the request method.
                request.Method = new HttpMethod(method);
            }
        }
        return base.SendAsync(request, cancellationToken);
    }
}

我在控制器上定义了以下方法:

  • persons/{id}, 得到
  • persons/{id}, 放
  • persons/{id}, 删除

我可以通过他们的“本机”方法调用它们,并且它们按预期工作。但是,当我尝试通过 POST 调用它们时,发送X-HTTP-Method-Override带有“DELETE”或“PUT”的标头时,会出现Not Found (404)错误。重要的是要补充一点,当它给出这个错误时,它永远不会到达MethodOverrideHandler- 我已经放置了一个永远不会命中的断点;当我调用正常的 DELETE 和 PUT 时,它确实达到了断点。

我什至尝试添加另一种方法:

  • persons/{id}, 邮政

当我这样做时,我得到了一个不允许的方法(405)

我认为消息处理程序是在路由和控制器调度程序之前运行的。为什么这给了我404?

我不认为这是相关的,但我没有使用默认的 Web API 路由。相反,我使用分配给每个方法的自定义属性进行映射,如下所示:

routes.MapHttpRoute(
    String.Format("{0}_{1}", operation.Name, service.ServiceId),
    String.Format("{0}/{1}", service.RoutePrefix, routeTemplateAttribute.Template),
    defaults,
    new { httpMethod = GetHttpMethodConstraint(operation) });
[HttpDelete, RouteTemplate("persons/{id}")]
public HttpResponseMessage DeletePerson(string id)
{
    // ...
}

编辑GetHttpMethodConstraint代码如下。

private static HttpMethodConstraint GetHttpMethodConstraint(MethodInfo methodInfo)
{
    var methodResolver = HttpMethodResolver.FromMethodInfo(methodInfo);
    return new HttpMethodConstraint(methodResolver.Resolve());
}
internal class HttpMethodResolver
{
    private MethodInfo _methodInfo;

    private HttpMethodResolver(MethodInfo methodInfo)
    {
        _methodInfo = methodInfo;
    }

    public static HttpMethodResolver FromMethodInfo(MethodInfo methodInfo)
    {
        return new HttpMethodResolver(methodInfo);
    }

    public string[] Resolve()
    {
        var verbs = new List<HttpMethod>();

        if (MethodHasAttribute<HttpGetAttribute>())
        {
            verbs.Add(HttpMethod.Get);
        }
        else if (MethodHasAttribute<HttpPostAttribute>())
        {
            verbs.Add(HttpMethod.Post);
        }
        else if (MethodHasAttribute<HttpDeleteAttribute>())
        {
            verbs.Add(HttpMethod.Delete);
        }
        else if (MethodHasAttribute<HttpPutAttribute>())
        {
            verbs.Add(HttpMethod.Put);
        }
        else
        {
            throw new ServiceModelException("HTTP method attribute should be used");
        }

        return verbs.Select(v => v.Method).ToArray();
    }

    private bool MethodHasAttribute<T>() where T : Attribute
    {
        return GetMethodAttribute<T>() != null;
    }

    private T GetMethodAttribute<T>() where T : Attribute
    {
        return _methodInfo.GetCustomAttributes(typeof(T), true).FirstOrDefault() as T;
    }
}
4

2 回答 2

1

我想我也有同样的问题。看起来确实在任何消息处理程序之前检查了路由约束。

所以我创建了一个自定义约束,它知道检查是否有被覆盖的 HTTP 方法:

class OverrideableHttpMethodConstraint : HttpMethodConstraint
{
    public OverrideableHttpMethodConstraint(HttpMethod httpMethod) : base(httpMethod)
    {
    }

    protected override bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
    {
        IEnumerable<string> headerValues;
        if (request.Method.Method.Equals("POST", StringComparison.OrdinalIgnoreCase) && 
            request.Headers.TryGetValues("X-HTTP-Method-Override", out headerValues))
        {
            var method = headerValues.FirstOrDefault();
            if (method != null)
            {
                request.Method = new HttpMethod(method);
            }
        }

        return base.Match(request, route, parameterName, values, routeDirection);
    }
}
于 2013-07-15T11:24:41.493 回答
0

I have tried to reproduce your error but I wasn't able to. Here, you can download my simple project with your message handler: https://dl.dropbox.com/u/20568014/WebApplication6.zip

I would like to point out that message handlers run before the action selection logic is performed. So, in your case, probably something else causes the problem and I think you should look at your other message handlers, your message handler's registration code, etc because the problem occurs due to the fact that your message handler never runs.

Also, I think your IRouteConstraint implementation, GetHttpMethodConstraint, looks suspicious to me.

Here is my registration code for the message handler:

protected void Application_Start(object sender, EventArgs e) {

    var config = GlobalConfiguration.Configuration;
    config.Routes.MapHttpRoute(
        "DefaultHttpRoute",
        "api/{controller}/{id}",
        new { id = RouteParameter.Optional }
    );

    config.MessageHandlers.Add(new MethodOverrideHandler());
}
于 2012-10-30T09:16:49.323 回答