处理CORS预检请求到ASP.NET MVC操作
我正在尝试执行一个跨域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>
现在,无论何时发生预检请求,它都会返回500个HTTP代码,因为“数据”参数为空,因为OPTIONS请求没有传递任何值。
服务器应用程序已经在端口8100的本地IIS中设置,运行客户端代码的页面设置在端口8200上,以模拟跨域调用。
我也configuration了主机(在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调用的动作。
有没有一个更优雅的解决scheme来获得这个function的工作?
所以我find了一个可行的解决scheme。 对于每个请求,我检查它是否是一个CORS请求,以及请求是否带有OPTIONS谓词,表示它是预检请求。 如果是这样,我只是发回一个空的响应(当然只包含在IIS中configuration的头文件),从而否定控制器的动作执行。
然后,如果客户端确认允许根据预检中返回的头文件执行请求,则会执行实际的POST并执行控制器操作。 我的代码的例子:
protected void Application_BeginRequest() { if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OridinalIgnoreCase) && Request.HttpMethod == "OPTIONS") { Response.Flush(); } }
如前所述,这对我很有帮助,但是如果有人知道更好的方法,或者我目前执行中的任何缺陷,我会很乐意听到他们的消息。
扩展卡尔的答案,我把他的代码,并插入到我的OWINpipe道:
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之前的任何地方)即可
这些答案都没有为我工作,但下面的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>
以下是我如何处理ASP.Net Web Api的预检/ CORS问题。 我只是简单地将Microsoft.AspNet.WebApi.Cors Nuget包添加到我的Web项目中。然后在我的WebApiConfig.cs文件中添加以下行:
config.EnableCors(new ApplicationCorsPolicy());
并创build了一个自定义的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中的filter和处理程序一样,您也可以将类或方法级别的注释添加到您的控制器中,如下所示:
[EnableCors("*, *, *, *")]
请注意,EnableCors属性具有接受以下参数的构造函数
- 允许的起源清单
- 允许的请求头列表
- 允许的HTTP方法列表
- 允许的响应头列表
您可以在每个允许访问哪个资源的控制器/端点处静态指定。
更新06/24/2016:我应该提到,我有我的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>
资料来源: 微软
这可能是一个红鲱鱼。 最近我的CORS工作正常,没有跳过任何你正在做的事情。
这是使用Thinktecture.IdentityModel nuget包的组合,更重要的是删除所有对WebDAV的引用。 这包括从IIS中删除webdav模块,并确保您的webconfiguration中有以下几行:
<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>
然后你可以使用思想来从你的Global.asax中使用静态类来configuration你的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/
被接受的答案就像一个魅力,但我发现这个请求实际上被传递给控制器。 我收到了200
状态代码,但是响应主体包含了很多HTML,但是控制器有一个例外。 所以不是使用Response.Flush()
,我发现最好使用Response.End()
,它会停止执行请求。 这种替代解决scheme看起来像这样:
编辑:修复从原来的答案进行错字。
protected void Application_BeginRequest() { if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) && Request.HttpMethod == "OPTIONS") { Response.End(); } }