2012-10-25 9 views
6

hereの手順に従ってHTTPメソッドオーバーライドを実装しようとしています。基本的には、次のようなDelegatingHandlerを作成し、Application_Startにメッセージハンドラとして追加しています。X-HTTP-Method-Overrideは、ASP.NET Web APIでNotFound(404)を返します。

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}をGET、
  • persons/{id}をPUT、私は彼らを介してそれらを呼び出すことができます

をDELETE "ネイティブ"メソッドと期待どおりに動作します。しかし、POSTで呼び出すときに、X-HTTP-Method-Overrideヘッダーを「DELETE」または「PUT」で送信すると、が見つかりません(404)エラーが発生します。それは、このエラーを与えるとき、それがMethodOverrideHandlerに到達しないことを追加することが重要です - 私は決してヒットしないブレークポイントを置いた;通常のDELETEとPUTを呼び出すと、ブレークポイントにヒットします。

私も別の方法を追加してみました:

  • persons/{id}、POST

私はこれを行うと、私が代わりに(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) 
{ 
    // ... 
} 

EDITGetHttpMethodConstraintコードは以下の通りです。

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

あなたは 'DeletePerson'からDELETE''にあなたのアクションメソッドを変更しようとしたことがありますか? –

+0

私は以下のように回答しましたが、アプリケーションを解凍してどこかに置くことができれば、問題を再現することができなかったように見えます。 – tugberk

+0

ちょうど記録のために、私の問題はこの答えによって明確になった:http://stackoverflow.com/a/13959148/1288760。 – alextercete

答えて

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()); 
} 
+0

ありがとうございました!私は '' GetHttpMethodConstraint'が正しく働いていると確信しています。なぜなら、 "PUT"と "DELETE"として宣言されたメソッドを "ネイティブ"動詞を使って呼び出すことができるからです。この問題は、POSTを使用して呼び出して、メソッドオーバーライドヘッダーが発生した場合にのみ発生します。私はqueルートのマッピングの前後にメッセージハンドラを登録しようとしましたが、動作しませんでした。私はそれが問題を引き起こしていたかどうかを確認するために登録した他のメッセージハンドラを削除しましたが、運はありません。私はあなたのアプリケーションが動作していることを確認することができるので、インフラストラクチャなどとは関係ありません。 – alextercete

+0

@alextercete 'GetHttpMethodConstraint'はメッセージハンドラの前で起動します。私のお金はあなたの問題が 'GetHttpMethodConstraint'に関連しているということです。 – tugberk

+0

質問に 'GetHttpMethodConstraint'のコードを追加しました。私はそれについて特別なことはないと思う... – alextercete

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); 
    } 
} 
関連する問題