54

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.

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.

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.

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

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:

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.

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

4

6 回答 6

67

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

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

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

如前所述,这对我有用,但如果有人知道更好的方法,或者我当前的实现中有任何缺陷,我会很高兴听到他们的消息。

于 2012-11-30T13:24:50.583 回答
12

扩展卡尔的答案,我把他的代码插入到我的 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 之前的任何位置)

于 2015-07-31T19:46:01.840 回答
7

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

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

protected void Application_BeginRequest()
{
    if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) &&
        Request.HttpMethod == "OPTIONS") {
        Response.End();
    }
}
于 2017-05-25T20:14:00.280 回答
6

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

资料来源:微软

于 2016-05-05T09:37:07.950 回答
3

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

于 2012-11-30T13:36:35.537 回答
3

这些答案都不适合我,但以下 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>
于 2016-08-01T06:57:39.613 回答