asp.net-mvc 处理对 ASP.NET MVC 操作的 CORS 预检请求

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13624386/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 02:26:28  来源:igfitidea点击:

Handling CORS Preflight requests to ASP.NET MVC actions

asp.net-mvccors

提问by Carl Heinrich Hancke

I'm trying to perform a cross-domain POST request to an ASP.NET MVC controller action. This controller action accepts & uses various parameters. The problem is that when the preflight request happens, the controller action actually attempts to execute & because the OPTIONS request doesn't pass any data, the controller action throws out a 500 HTTP error. If I remove the code that uses the parameter, or the parameter itself, the entire request chain is completed successfully.

我正在尝试对 ASP.NET MVC 控制器操作执行跨域 POST 请求。此控制器操作接受并使用各种参数。问题是当预检请求发生时,控制器动作实际上尝试执行 & 因为 OPTIONS 请求没有传递任何数据,控制器动作抛出 500 HTTP 错误。如果我删除使用参数的代码,或者参数本身,则整个请求链成功完成。

An example of how this is implemented:

这是如何实现的一个例子:

Controller Action

控制器动作

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

Client-side code

客户端代码

<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>

Now, whenever the preflight request happens, it returns a 500 HTTP code, because the "data" parameter is null, seeing as the OPTIONS request doesn't pass any values.

现在,无论何时发生预检请求,它都会返回一个 500 HTTP 代码,因为“数据”参数为空,因为 OPTIONS 请求没有传递任何值。

The server application has been set up in my local IIS on port 8100 & the page running the client-side code is set up on port 8200 to mimic the cross-domain calls.

服务器应用程序已在我本地 IIS 的 8100 端口上设置,并且运行客户端代码的页面在端口 8200 上设置以模拟跨域调用。

I have also configured the host (on 8100) with the following headers:

我还使用以下标头配置了主机(在 8100 上):

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

One workaround I had found, was to check the HTTP method that executes the action & if it's a OPTIONS request to just return blank content, otherwise execute the action code. Like so:

我发现的一种解决方法是检查执行操作的 HTTP 方法,如果它是一个仅返回空白内容的 OPTIONS 请求,则执行操作代码。像这样:

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

But this approach feels very clunky to me. I considered adding this sort of logic to an Attribute, but even this would mean decorating every action that will get called using CORS with it.

但是这种方法对我来说感觉很笨拙。我考虑将这种逻辑添加到Attribute,但即使这样也意味着装饰将使用 CORS 调用的每个操作。

Is there a more elegant solution to getting this functionality to work?

是否有更优雅的解决方案来使此功能正常工作?

回答by Carl Heinrich Hancke

So I have found a solution that works. For each request, I check whether it's a CORS request & whether the request is coming in with the OPTIONS verb, indicating that it's the preflight request. If it is, I just send an empty response back (which only contains the headers configured in IIS of course), thus negating the controller action execution.

所以我找到了一个有效的解决方案。对于每个请求,我检查它是否是 CORS 请求以及该请求是否带有 OPTIONS 动词,表明它是预检请求。如果是,我只是发送一个空响应(当然它只包含在 IIS 中配置的标头),从而否定控制器操作的执行。

Then if the client confirms it's allowed to perform the request based on the returned headers from preflight, the actual POST is performed & the controller action is executed. And example of my code:

然后,如果客户端根据预检返回的标头确认允许执行请求,则执行实际的 POST 并执行控制器操作。以及我的代码示例:

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

As mentioned, this worked for me, but if anyone knows of a better way, or of any flaws in my current implementation, I would appreciate to hear about them.

如前所述,这对我有用,但如果有人知道更好的方法,或我当前实现中的任何缺陷,我将不胜感激。

回答by Jonesopolis

expanding on Carl's answer, i took his code and plugged it into my OWIN pipeline:

扩展 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();
});

Just add this to the beginning (or anywhere before you register the WebAPI) of your IAppBuilder in Startup.cs

只需将此添加到 Startup.cs 中 IAppBuilder 的开头(或注册 WebAPI 之前的任何位置)

回答by Gabriel Diéguez

The accepted answer works like a charm, but I found that the request was actually being passed down to the controller. I was receiving a 200status code, but the response body contained a lot of HTML with an exception from the controller. So instead of using Response.Flush(), I found it was better to use Response.End(), which does stop the execution of the request. This alternative solution would look like this:

接受的答案就像一个魅力,但我发现请求实际上是传递给控制器​​的。我收到了一个200状态代码,但响应正文包含大量 HTML,但控制器例外。因此Response.Flush(),我发现最好使用,而不是使用Response.End(),它会停止执行请求。此替代解决方案如下所示:

EDIT:fixed a typo carried from the original answer.

编辑:修复了原始答案中的错字。

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

回答by Rob L

Here is how I handled the preflight/CORS issues with ASP.Net Web Api. I simply added the Microsoft.AspNet.WebApi.Cors Nuget package to my Web project.Then in my WebApiConfig.cs file I added this line:

以下是我如何处理 ASP.Net Web Api 的预检/CORS 问题。我只是将 Microsoft.AspNet.WebApi.Cors Nuget 包添加到我的 Web 项目中。然后在我的 WebApiConfig.cs 文件中我添加了这一行:

config.EnableCors(new ApplicationCorsPolicy());

and created a custom PolicyProvider class

并创建了一个自定义 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;
    }
}

See, the Cors framework allows you to add your own logic for determining which origins are allowed, etc. This is very helpful if you are exposing a REST API to the outside world and the list of people (origins) who can access your site are in a controlled environment like a database. Now, if you are simply allowing all origins (which might not be such a good idea in all cases) you can just do this in WebApiConfig.cs to enable CORS globally:

看,Cors 框架允许您添加自己的逻辑来确定允许哪些来源等。如果您将 REST API 暴露给外界并且可以访问您网站的人员(来源)列表是在像数据库这样的受控环境中。现在,如果您只是允许所有来源(在所有情况下这可能不是一个好主意),您可以在 WebApiConfig.cs 中执行此操作以全局启用 CORS:

config.EnableCors();

Just like Filters and Handlers in WebApi you can also add class or method level annotations to your controllers like so:

就像 WebApi 中的过滤器和处理程序一样,您也可以向控制器添加类或方法级别的注释,如下所示:

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

Note that the EnableCors attribute has a constructor that accepts the following parameters

请注意,EnableCors 属性有一个接受以下参数的构造函数

  1. List of Origins Allowed
  2. List of request headers allowed
  3. List of HTTP methods allowed
  4. List of response headers allowed
  1. 允许的来源列表
  2. 允许的请求标头列表
  3. 允许的 HTTP 方法列表
  4. 允许的响应头列表

You can specify statically at each controller/end point who is allowed to access what resource.

您可以在每个控制器/端点静态指定谁可以访问什么资源。

Update 06/24/2016:I should mention that I have the following in my Web.config. It looks like these may not be the defaults for everyone.

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>

Source: Microsoft

资料来源:微软

回答by TreeAndLeaf

None of these answers worked for me, but the following webconfig settings did. The two key settings for me were setting Access-Control-Allow-Headersto Content-Typeand commenting out the line that removes the OPTIONSVerbHandler:

这些答案都不适合我,但以下 webconfig 设置可以。对我来说,两个关键的设置被设置Access-Control-Allow-HeadersContent-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>

回答by beyond-code

This may be a red herring. I have recently got CORS working fine without jumping through any of the hoops that you are doing.

这可能是一个红鲱鱼。我最近让 CORS 工作正常,没有跳过你正在做的任何事情。

This was done using a combination of Thinktecture.IdentityModel nuget package, and more importantly... REMOVAL of all references to WebDAV. This includes removing the webdav module from IIS, and ensuring the following lines in your web config:

这是使用 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>

Then you can just use thinktecture to configure your CORS from your Global.asax using a static class like this:

然后你可以使用 thinktecture 从你的 Global.asax 使用这样的静态类配置你的 CORS:

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

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

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

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