web-dev-qa-db-ja.com

ASP.NET MVCアクションへのCORSプリフライトリクエストの処理

クロスドメインPOST ASP.NET MVCコントローラーアクションへのリクエストを実行しようとしています。このコントローラーアクションはさまざまなパラメーターを受け入れ、使用します。問題は、プリフライトリクエストが発生すると、 OPTIONSリクエストがデータを渡さないため、コントローラーアクションは実際に実行を試みます。コントローラーアクションは500 HTTPエラーをスローします。パラメーターまたはパラメーター自体を使用するコードを削除すると、リクエストチェーン全体が正常に完了します。 。

これがどのように実装されるかの例:

コントローラーアクション

public ActionResult GetData(string data)
{
    return new JsonResult
    {
        Data = data.ToUpper(),
        JsonRequestBehavior = JsonRequestBehavior.AllowGet
    };
}

クライアント側コード

<script type="text/javascript">
        $(function () {
            $("#button-request").click(function () {
                var ajaxConfig = {
                    dataType: "json",
                    url: "http://localhost:8100/Host/getdata",
                    contentType: 'application/json',
                    data: JSON.stringify({ data: "A string of data" }),
                    type: "POST",
                    success: function (result) {
                        alert(result);
                    },
                    error: function (jqXHR, textStatus, errorThrown) {
                        alert('Error: Status: ' + textStatus + ', Message: ' + errorThrown);
                    }
                };

                $.ajax(ajaxConfig);
            });
        });
    </script>

現在、プリフライトリクエストが発生すると、「data」パラメータがnullであるため、500 HTTPコードを返します。OPTIONSリクエストは値を渡さないためです。

サーバーアプリケーションは、ポート8100でローカルIISに設定されています。クライアント側コードを実行しているページは、クロスドメインコールを模倣するためにポート8200に設定されています。

また、次のヘッダーを使用してホスト(8100)を構成しました。

Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Origin: http://localhost:8200

私が見つけた回避策の1つは、アクションを実行するHTTPメソッドをチェックすることであり、空のコンテンツを返すだけのOPTIONSリクエストである場合はアクションコードを実行することでした。そのようです:

public ActionResult GetData(string data)
{
    if (Request.HttpMethod == "OPTIONS") {
        return new ContentResult();
    } else {
        return new JsonResult
        {
            Data = data.ToUpper(),
            JsonRequestBehavior = JsonRequestBehavior.AllowGet
        };
    }
}

しかし、このアプローチは私にとって非常に不格好です。このようなロジックをAttributeに追加することを検討しましたが、これでも、CORSを使用して呼び出されるすべてのアクションを装飾することを意味します。

この機能を動作させるためのよりエレガントなソリューションはありますか?

47

だから私は動作する解決策を見つけました。各リクエストについて、CORSリクエストであるかどうか、およびリクエストがOPTIONS動詞とともに来ているかどうかをチェックし、プリフライトリクエストであることを示します。そうである場合、空の応答(IIS)で構成されたヘッダーのみを含む)を返送するだけで、コントローラーアクションの実行を無効にします。

その後、クライアントがプリフライトから返されたヘッダーに基づいてリクエストの実行が許可されていることを確認すると、実際のPOSTが実行され、コントローラーアクションが実行されます。私のコードの例:

protected void Application_BeginRequest()
{
    if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) &&
        Request.HttpMethod == "OPTIONS") {
        Response.Flush();
    }
}

前述のように、これは私にとってはうまくいきましたが、誰かがより良い方法、または私の現在の実装の欠陥を知っているなら、私はそれらについて聞いていただければ幸いです。

61

carlの答えを拡張して、彼のコードをOWINパイプラインにプラグインしました。

app.Use((context, next) =>
{
     if (context.Request.Headers.Any(k => k.Key.Contains("Origin")) && context.Request.Method == "OPTIONS")
     {
         context.Response.StatusCode = 200;
         return context.Response.WriteAsync("handled");
     }

     return next.Invoke();
});

Startup.csのIAppBuilderの先頭(またはWebAPIを登録する前の任意の場所)に追加するだけです

10
Jonesopolis

受け入れられた答えは魅力のように機能しますが、要求は実際にコントローラーに渡されていることがわかりました。 _200_ステータスコードを受信して​​いましたが、コントローラーからの例外を除いて、応答本文に多くのHTMLが含まれていました。したがって、Response.Flush()を使用する代わりに、リクエストの実行を停止するResponse.End()を使用する方がよいことがわかりました。この代替ソリューションは次のようになります。

編集:元の回答からの誤字を修正しました。

_protected void Application_BeginRequest()
{
    if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) &&
        Request.HttpMethod == "OPTIONS") {
        Response.End();
    }
}
_
5

ASP.Net Web Apiでプリフライト/ CORSの問題をどのように処理したかを以下に示します。 Microsoft.AspNet.WebApi.Cors NugetパッケージをWebプロジェクトに追加した後、WebApiConfig.csファイルに次の行を追加しました。

config.EnableCors(new ApplicationCorsPolicy());

カスタムPolicyProviderクラスを作成しました

public class ApplicationCorsPolicy : Attribute, ICorsPolicyProvider
{
    public async Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var corsRequestContext = request.GetCorsRequestContext();
        var originRequested = corsRequestContext.Origin;

        if (await IsOriginFromAPaidCustomer(originRequested))
        {
            // Grant CORS request
            var policy = new CorsPolicy
            {
                AllowAnyHeader = true,
                AllowAnyMethod = true
            };
            policy.Origins.Add(originRequested);
            return policy;
        }
        // Reject CORS request
        return null;
    }

    private async Task<bool> IsOriginFromAPaidCustomer(string originRequested)
    {
        // Do database look up here to determine if Origin should be allowed.
        // In my application I have a table that has a list of domains that are
        // allowed to make API requests to my service. This is validated here.
        return true;
    }
}

Corsフレームワークを使用すると、許可される発信元を決定するための独自のロジックなどを追加できます。これは、REST APIを外部の世界と人々のリストに公開する場合に非常に役立ちます(オリジン)サイトにアクセスできる人は、データベースのような管理された環境にいます。今、すべてのオリジンを許可するだけの場合(すべての場合にこれは良い考えではないかもしれません)、WebApiConfig.csでこれを有効にしてグローバルなCORS:

config.EnableCors();

WebApiのフィルターおよびハンドラーと同様に、次のようにコントローラーにクラスまたはメソッドレベルの注釈を追加することもできます。

[EnableCors("*, *, *, *")]

EnableCors属性には、次のパラメーターを受け入れるコンストラクターがあることに注意してください。

  1. 許可された起源のリスト
  2. 許可されるリクエストヘッダーのリスト
  3. 許可されるHTTPメソッドのリスト
  4. 許可される応答ヘッダーのリスト

各リソースへのアクセスを許可されている各コントローラー/エンドポイントで静的に指定できます。

2016年6月24日更新:Web.configに次のものがあることに言及する必要があります。これらは誰にとってもデフォルトではないようです。

<system.webServer>
    <handlers>
        <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
        <remove name="OPTIONSVerbHandler" />
        <remove name="TRACEVerbHandler" />
        <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
        </handlers>
</system.webServer>

ソース: Microsoft

4
Rob L

これらの答えはどれもうまくいきませんでしたが、次のwebconfig設定はうまくいきました。私にとって2つの重要な設定はAccess-Control-Allow-HeadersからContent-TypeおよびOPTIONSVerbHandlerを削除する行をコメントアウトします。

  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"></modules>
    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Headers" value="Content-Type" />
      </customHeaders>
    </httpProtocol>
    <handlers>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <!--<remove name="OPTIONSVerbHandler" />-->
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
  </system.webServer>
3
TreeAndLeaf

これはニシンかもしれません。私は最近、あなたがしているフープのいずれかをジャンプすることなく、CORSが正常に機能するようになりました。

これは、Thinktecture.IdentityModel nugetパッケージの組み合わせを使用して行われました。さらに重要なことは、WebDAVへのすべての参照の削除です。これには、IISからwebdavモジュールを削除し、Web設定で次の行を確認することが含まれます。

<system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="WebDAVModule" />
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" preCondition="managedHandler" />
    </modules>
    <handlers>
      <remove name="WebDAV" />
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
  <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
  <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>

次に、thinktectureを使用して、次のような静的クラスを使用してGlobal.asaxからCORSを構成できます。

public class CorsConfig
{
    public static void RegisterCors(HttpConfiguration httpConfiguration)
    {
        var corsConfig = new WebApiCorsConfiguration();
        corsConfig.RegisterGlobal(httpConfiguration);

        corsConfig.ForAllResources().AllowAllOriginsAllMethodsAndAllRequestHeaders();
    }
}

ソース: http://brockallen.com/2012/06/28/cors-support-in-webapi-mvc-and-iis-with-thinktecture-identitymodel/

2
beyond-code