处理对ASP.NET MVC操作的CORS预检请求

时间:2012-11-29 11:07:07

标签: asp.net-mvc cors

我正在尝试对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”参数为null,因为OPTIONS请求没有传递任何值。

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

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

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

我发现的一个解决方法是检查执行操作的HTTP方法&amp;如果是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来装饰每个将被调用的动作。

是否有更优雅的解决方案让这项功能发挥作用?

6 个答案:

答案 0 :(得分:59)

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

然后,如果客户确认允许根据预检返回的标题执行请求,则执行实际的POST&amp;控制器动作被执行。我的代码示例:

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

如上所述,这对我有用,但如果有人知道更好的方式,或者我目前的实施中存在任何缺陷,我将很感激地听到它们。

答案 1 :(得分:10)

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

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

答案 2 :(得分:4)

以下是我使用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. 允许的回复标题列表
  5. 您可以静态指定允许访问哪个资源的每个控制器/端点。

    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

答案 3 :(得分:4)

接受的答案就像魅力一样,但我发现请求实际上是传递给控制器​​的。我收到了200状态代码,但响应正文包含很多HTML,但控制器有异常。因此,我发现最好不要使用Response.Flush(),而是使用Response.End(),它会停止执行请求。这个替代解决方案看起来像这样:

编辑:修复了原始答案中的拼写错误。

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

答案 4 :(得分:3)

这些答案都不适合我,但以下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>

答案 5 :(得分:2)

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