【问题标题】:Handling CORS Preflight requests to ASP.NET MVC actions处理对 ASP.NET MVC 操作的 CORS 预检请求
【发布时间】:2012-11-17 10:39:36
【问题描述】:

我正在尝试对 ASP.NET MVC 控制器操作执行跨域 POST 请求。此控制器操作接受并使用各种参数。问题是当预检请求发生时,控制器动作实际上尝试执行 & 因为 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>

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

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

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

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

我发现的一种解决方法是检查执行操作的 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 调用的每个操作。

有没有更优雅的解决方案来让这个功能发挥作用?

【问题讨论】:

  • 为我工作。为什么微软的一切都如此细致入微?

标签: asp.net-mvc cors


【解决方案1】:

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

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

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

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

【讨论】:

  • Cors 确实很有趣。我遇到了tinyurl.com/nxz65ac 的问题,我在我的WebAPI 上实现了。文件上传很小时工作正常,我可以上传倍数就好,但是当我选择一个更大的文件说大约 50MB 并尝试上传时,我得到“没有'Access-Control-Allow-Origin'标头存在于请求的资源。”在铬。事件虽然如果我选择多个文件,较小的文件可以正常上传,但大文件会使浏览器停止。我会创建一个新问题,但我不知道如何标记像你这样解决了令人痛苦的 Cors 问题的用户。有人知道怎么做吗?
  • 这不是 CORS 问题,但奇怪的是 Chrome 说它是。 WebAPI 中有两个文件大小限制区域,我必须同时设置:stackoverflow.com/a/17324840
  • 这进入 global.asax 并且是您可以挂钩的应用程序级事件之一。
  • 这对我来说不太奏效,但调试和检查使我做出了这个小改动。添加特定的标头并确保明确 命名 Access-Control-Allow-Headers(星号不够用)。 if (Request.Headers.AllKeys.Contains("Origin") &amp;&amp; Request.HttpMethod == "OPTIONS") { Response.Headers.Add("Access-Control-Allow-Origin", "*"); Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, Session"); Response.Flush(); }
  • 标头键不区分大小写,我遇到过 Chrome 中的 jQuery 会为 HTTP 发送“Origin”而为 HTTPS 发送“origin”的情况。不过,jQuery 和 Chrome 的这种组合不太可能做到这一点。我已更新您的答案以不区分大小写检查以解决此差异。
【解决方案2】:

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

编辑:修正了原始答案中的一个错字。

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

【讨论】:

  • 你把这个函数放在哪里了?
  • 看起来它在应用程序根文件夹的 Global.asax.cs 中。
【解决方案3】:

以下是我使用 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】:

    这些答案都不适合我,但以下 webconfig 设置可以。我的两个关键设置是将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>
    

    【讨论】:

    • 需要注意的是,上面的代码对每个来源都启用了 CORS。通常,您想尽可能地尝试限制来源。您是否在 WebApiConfig.cs 文件中尝试过 config.EnableCors()
    【解决方案5】:

    扩展 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 之前的任何位置)

    【讨论】:

      【解决方案6】:

      这可能是一个红鲱鱼。我最近让 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/

      【讨论】:

      • 我在研究期间一直在研究 Thinktecture,但这似乎是一件很简单的事情,我想找到一种无需第三方库的方式来实现它。不过,感谢您花时间发布您的答案:)
      • 没问题,有时候自己做会更有益也更有趣:)
      • 谢谢!您的回答为我指明了正确的方向。 CORS 为我工作,但我不小心从我的 web.config 中删除了 ExtensionlessUrlHandler 东西,这破坏了我的 CORS(我应该删除无异常的东西......)
      猜你喜欢
      • 2014-10-31
      • 1970-01-01
      • 2016-02-17
      • 2019-08-29
      • 2014-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-14
      相关资源
      最近更新 更多