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}、 消去

私はそれらを「ネイティブ」メソッドで呼び出すことができ、期待どおりに機能します。X-HTTP-Method-Overrideただし、 「DELETE」または「PUT」を使用してヘッダーを送信し、POSTを介してそれらを呼び出そうとすると、 Not Found(404)エラーが発生します。このエラーが発生した場合、それが到達することは決してないことを追加することが重要ですMethodOverrideHandler-私は決してヒットされないブレークポイントを設定しました。通常のDELETEとPUTを呼び出すと、ブレークポイントに到達します。

別のメソッドを追加してみました:

  • persons/{id}、 役職

これを行うと、代わりに許可されていないメソッド(405)が表示されます。

メッセージハンドラーは、ルーティングディスパッチャーとコントローラーディスパッチャーの前に実行されると思いました。なぜこれが私に404を与えるのですか?

これは関連しているとは思いませんが、デフォルトのWebAPIルーティングを使用していません。代わりに、次のように、各メソッドに割り当てられたカスタム属性を使用してマッピングしています。

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

エラーを再現しようとしましたが、再現できませんでした。ここで、メッセージハンドラーを使用して私の単純なプロジェクトをダウンロードできます:https ://dl.dropbox.com/u/20568014/WebApplication6.zip

アクション選択ロジックが実行される前にメッセージハンドラーが実行されることを指摘したいと思います。したがって、あなたの場合、おそらく他の何かが問題を引き起こします。メッセージハンドラが実行されないという事実が原因で問題が発生するため、他のメッセージハンドラ、メッセージハンドラの登録コードなどを確認する必要があると思います。

また、あなたのIRouteConstraint実装はGetHttpMethodConstraint、私には疑わしいように見えると思います。

メッセージハンドラの登録コードは次のとおりです。

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 に答える