如何在ASP.NET MVC / WebAPI应用程序中支持HTTP OPTIONS动词


80

我已经从MVC 4 / Web API模板开始设置了ASP.NET Web应用程序。似乎一切运行得很好-没有我知道的问题。我已经使用Chrome和Firefox浏览了该网站。我已经使用Fiddler进行了测试,所有的响应似乎都可以赚钱。

因此,现在我继续编写一个简单的Test.aspx来使用此新的Web API。脚本的相关部分:

<script type="text/javascript">
    $(function () {

        $.ajax({
            url: "http://mywebapidomain.com/api/user",
            type: "GET",
            contentType: "json",
            success: function (data) {

                $.each(data, function (index, item) {

                    ....

                    });
                }
                );

            },
            failure: function (result) {
                alert(result.d);
            },

            error: function (XMLHttpRequest, textStatus, errorThrown) {
                alert("An error occurred, please try again. " + textStatus);
            }

        });

    });
</script>

这将生成一个REQUEST标头:

OPTIONS http://host.mywebapidomain.com/api/user HTTP/1.1
Host: host.mywebapidomain.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Origin: http://mywebapidomain.com
Access-Control-Request-Method: GET
Access-Control-Request-Headers: content-type
Connection: keep-alive

Web API照原样返回405方法不允许。

HTTP/1.1 405 Method Not Allowed
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/xml; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Mon, 30 Sep 2013 13:28:12 GMT
Content-Length: 96

<Error><Message>The requested resource does not support http method 'OPTIONS'.</Message></Error>

我了解默认情况下,OPTIONS动词未在Web API控制器中连接起来...因此,我将以下代码放在UserController.cs中:

// OPTIONS http-verb handler
public HttpResponseMessage OptionsUser()
{
    var response = new HttpResponseMessage();
    response.StatusCode = HttpStatusCode.OK;
    return response;
}

...并消除了405 Method Not Allowed错误,但响应完全为空-不返回任何数据:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Mon, 30 Sep 2013 12:56:21 GMT
Content-Length: 0

必须有其他逻辑...我不知道如何正确编码Options方法,或者控制器是否是放置代码的适当位置。(对我来说)很奇怪,当从Firefox或Chrome浏览器查看Web API站点时,它可以正确响应,但是上面的.ajax调用出错了。如何处理.ajax代码中的“预检”检查?也许我应该在客户端的.ajax逻辑上解决此问题?或者,如果由于不处理OPTIONS动词而在服务器端出现问题。

有人可以帮忙吗?这一定是一个非常普遍的问题,如果在这里得到回答,我深表歉意。我进行了搜索,但没有找到有帮助的答案。

UPDATE 恕我直言,这是一个客户端的问题,并与阿贾克斯jQuery代码上面做。我说这是因为,当我从Web浏览器访问mywebapidomain / api / user时,Fiddler不会显示任何405错误标头。我可以复制此问题的唯一地方是来自JQuery .ajax()调用。另外,在服务器(相同域)上运行时,上面相同的Ajax调用也可以正常工作。

我发现了另一篇文章:原型AJAX请求是作为OPTIONS而不是GET发送的;导致501错误似乎与之相关,但我修改了他们的建议,但没有成功。显然,对JQuery进行了编码,因此,如果Ajax请求是跨域的(属于我的),它将添加几个标头,它们以某种方式触发OPTIONS标头。

'X-Requested-With': 'XMLHttpRequest',
'X-Prototype-Version': Prototype.Version,

似乎应该有比在JQuery中修改核心代码更好的解决方案...

下面提供的答案假定这是服务器端问题。我想也许是这样,但是我倾向于客户,致电托管服务提供商也无济于事。


您将如何发送您的期权要求?
丹尼尔·怀特

我完全不需要发送OPTIONS请求。由于某种原因,当跨域进行Ajax调用时,此操作就完成了。因此,正如您在Javascript中所看到的,我正在做的就是指定GET,但由于HTTP协议而发送了OPTIONS标头。这是“预检”检查。
rwkiii

2
哦,您应该在iis服务器上启用cors。
丹尼尔·A·怀特

这是Arvixe服务器-商务舱专业版。两个站点都托管在同一台物理服务器上,同一托管帐户中。只是不同的主机名。是否可以在不致电Arvixe的情况下启用CORS?
rwkiii

我会打电话给您的托管服务提供商。
丹尼尔·怀特

Answers:


52

正如Daniel A. White在他的评论中所说,OPTIONS请求很可能是由客户端创建的,作为跨域JavaScript请求的一部分。这是由跨源资源共享(CORS)兼容的浏览器自动完成的。该请求是初步请求或飞行前请求,在实际AJAX请求之前进行,以确定CORS支持哪些请求动词和标头。服务器可以选择不支持,不支持全部或某些HTTP动词。

为了完成图片,AJAX请求具有一个附加的“ Origin”标头,该标头标识了从何处提供托管JavaScript的原始页面。服务器可以选择支持来自任何来源的请求,或仅支持一组已知的可信来源的请求。允许任何来源都是安全风险,因为这样做可能会增加跨站请求伪造(CSRF)的风险。

因此,您需要启用CORS。

这是解释如何在ASP.Net Web API中执行此操作的链接

http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api#enable-cors

此处描述的实现可让您指定其他内容

  • 针对每个动作,每个控制器或全局的CORS支持
  • 支持的来源
  • 启用CORS aa控制器或全局级别时,支持的HTTP动词
  • 服务器是否支持通过跨域请求发送凭据

通常,这可以正常工作,但是您需要确保您已意识到安全风险,尤其是在允许来自任何域的跨源请求的情况下。在允许这样做之前,请仔细考虑。

关于哪种浏览器支持CORS,维基百科表示以下引擎支持它:

  • 壁虎1.9.1(FireFox 3.5)
  • WebKit(Safari 4,Chrome 3)
  • MSHTML / Trident 6(IE10),在IE8和9中部分支持
  • 普雷斯托(歌剧12)

http://en.wikipedia.org/wiki/Cross-origin_resource_sharing#Browser_support


嗨,迈克。谢谢您的链接,这也是另一个很好的链接:codeguru.com/csharp/.net/net_asp/…-尽管这些都没有为我解决问题。我现在已经将测试页放置在服务器上,这对我有帮助。我尝试安装Microsoft.AspNet.WebApi.Cors,但遇到一个奇怪的错误,即我的应用程序没有任何WebApi依赖项,因此安装回滚。谢谢您的回答-我知道这是正确的。+1!
rwkiii

@rwkiii链接确实是一个解决方案,其中涉及添加对Web API 5.2.2的依赖关系,但是该解决方案比强迫MVC支持OPTIONS飞行前请求的黑客方法更具扩展性。您可能还想查看Dominick的答案,因为飞行前请求可能是接受或内容类型标头的结果,该标头要求来自客户端的此类呼叫
Sudhanshu Mishra

请注意,但如果将内容类型设置为:“ application / x-www-form-urlencoded”,“ multipart / form-data”或“ text / plain”,则该请求将被视为“简单”,并且不会发出飞行前要求。
mbx-mbx 2015年

94

迈克·古德温(Mike Goodwin)的回答很棒,但是当我尝试使用它时,它似乎是针对MVC5 / WebApi 2.1的。Microsoft.AspNet.WebApi.Cors的依赖性在我的MVC4项目中不能很好地发挥作用。

下面是使用MVC4在WebApi上启用CORS的最简单方法。

请注意,我已允许所有操作,建议您将Origin限制为只希望您的API服务的客户端。允许一切都存在安全风险。

Web.config:

<system.webServer>
    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Methods" value="GET, PUT, POST, DELETE, HEAD" />
        <add name="Access-Control-Allow-Headers" value="Origin, X-Requested-With, Content-Type, Accept" />
      </customHeaders>
    </httpProtocol>
</system.webServer>

BaseApiController.cs:

我们这样做是为了允许OPTIONS http动词

 public class BaseApiController : ApiController
  {
    public HttpResponseMessage Options()
    {
      return new HttpResponseMessage { StatusCode = HttpStatusCode.OK };
    }
  }

@Castaldi,这很可能是因为提供的答案针对的是没有属性路由的WebApi 1。对于WebApi 2,我建议使用Microsoft的CORS nuget软件包。nuget.org/packages/Microsoft.AspNet.WebApi.Cors
奥利弗(Oliver)

您也可以使用[ApiExplorerSettings(IgnoreApi = true)]忽略Swagger上的OPTIONS端点。
马里奥Meyrelles

这适用于我的WebApi 2 App。尤其是相关/基本控制器的Options()方法
lazyList '19

24

只需将其添加到您的Application_OnBeginRequest方法中(这将为您的应用程序全局启用CORS支持)并“处理”预检请求:

var res = HttpContext.Current.Response;
var req = HttpContext.Current.Request;
res.AppendHeader("Access-Control-Allow-Origin", req.Headers["Origin"]);
res.AppendHeader("Access-Control-Allow-Credentials", "true");
res.AppendHeader("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name");
res.AppendHeader("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS");

// ==== Respond to the OPTIONS verb =====
if (req.HttpMethod == "OPTIONS")
{
    res.StatusCode = 200;
    res.End();
}

*安全性:请注意,这将启用从任何​​位置到服务器的ajax请求(如果愿意,可以只允许使用逗号分隔的Origins / url列表)。

我使用的是当前客户端来源,而不是*因为这将允许凭据=>设置Access-Control-Allow-Credentials为true将启用跨浏览器会话管理

还需要启用删除,并把,补丁和选项动词在你的webconfig部分system.webServer,否则IIS将阻止他们:

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

希望这可以帮助


3
谢谢。只有这Application_OnBeginRequest帮助了我。但是,如果您也希望能够获得授权数据,则还应添加AuthorizationAccess-Control-Allow-Headers
Ehsan88 '16

18

在Web API 2项目中遇到了相同的问题(并且由于不值得在此介绍的原因而无法使用标准CORS软件包)之后,我能够通过实现自定义DelagatingHandler来解决此问题:

public class AllowOptionsHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var response = await base.SendAsync(request, cancellationToken);

        if (request.Method == HttpMethod.Options &&
            response.StatusCode == HttpStatusCode.MethodNotAllowed)
        {
            response = new HttpResponseMessage(HttpStatusCode.OK);
        }

        return response;
    }
}

对于Web API配置:

config.MessageHandlers.Add(new AllowOptionsHandler());

请注意,我还在Web.config中启用了CORS标头,类似于此处发布的其他一些答案:

<system.webServer>
  <modules runAllManagedModulesForAllRequests="true">
    <remove name="WebDAVModule" />
  </modules>

  <httpProtocol>
    <customHeaders>
      <add name="Access-Control-Allow-Origin" value="*" />
      <add name="Access-Control-Allow-Headers" value="accept, cache-control, content-type, authorization" />
      <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
    </customHeaders>
  </httpProtocol>

  <handlers>
    <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
    <remove name="TRACEVerbHandler" />
    <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
  </handlers>
</system.webServer>

请注意,我的项目不包括MVC,仅包括Web API 2。


10

我设法克服了仅由global.asax中的自定义代码在飞行前Ajax选项请求上引发的405和404错误

protected void Application_BeginRequest()
    {            
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
        if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
        {
            //These headers are handling the "pre-flight" OPTIONS call sent by the browser
            HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
            HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
            HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
            HttpContext.Current.Response.End();
        }
    }

PS:允许所有内容时考虑安全问题*。

我必须禁用CORS,因为它返回了“ Access-Control-Allow-Origin”标头,其中包含多个值。

在web.config中还需要这样做:

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

并且app.pool需要设置为集成模式。


8

我有同样的问题。对我来说,解决方法是从jQuery AJAX调用中删除自定义内容类型。自定义内容类型触发飞行前请求。我找到了这个:

如果满足以下条件,则浏览器可以跳过预检请求:

所述请求的方法是GETHEAD,或POST

该应用程序不设置任何请求标头以外AcceptAccept-LanguageContent-LanguageContent-Type,或Last-Event-ID

Content-Type报头(如果设置)是下列之一:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

在此页面上:http : //www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api (在“ Preflight请求”下)



2
    protected void Application_EndRequest()
    {
        if (Context.Response.StatusCode == 405 && Context.Request.HttpMethod == "OPTIONS" )
        {
            Response.Clear();
            Response.StatusCode = 200;
            Response.End();
        }
    }

1

我也面临同样的问题。

请按照以下步骤解决浏览器中的(CORS)合规性问题。

在Cors参考中将REDRock包含在您的解决方案中。包括WebActivatorEx对Web API解决方案的引用。

然后在Web API App_Start文件夹中添加文件CorsConfig。

[assembly: PreApplicationStartMethod(typeof(WebApiNamespace.CorsConfig), "PreStart")]

namespace WebApiNamespace
{
    public static class CorsConfig
    {
        public static void PreStart()
        {
            GlobalConfiguration.Configuration.MessageHandlers.Add(new RedRocket.WebApi.Cors.CorsHandler());
        }
    }
}

完成这些更改后,我可以在所有浏览器中访问webapi。


4
什么是红石?我进行了Google搜索和Nuget软件包搜索,但未返回任何内容。链接会很好。
hofnarwillie

1

我遇到了同样的问题,这就是我解决的方法:

只需将其放在您的web.config中:

<system.webServer>
    <modules>
      <remove name="WebDAVModule" />
    </modules>

    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Expose-Headers " value="WWW-Authenticate"/>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Methods" value="GET, POST, OPTIONS, PUT, PATCH, DELETE" />
        <add name="Access-Control-Allow-Headers" value="accept, authorization, Content-Type" />
        <remove name="X-Powered-By" />
      </customHeaders>
    </httpProtocol>

    <handlers>
      <remove name="WebDAV" />
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
</system.webServer>

0
//In the Application_OnBeginRequest method in GLOBAL.ASX add the following:-  

var res = HttpContext.Current.Response;  
var req = HttpContext.Current.Request;  
res.AppendHeader("Access-Control-Allow-Origin", "*");  
res.AppendHeader("Access-Control-Allow-Credentials", "true");  
res.AppendHeader("Access-Control-Allow-Headers", "Authorization");  
res.AppendHeader("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS");  

    // ==== Respond to the OPTIONS verb =====
    if (req.HttpMethod == "OPTIONS")
    {
        res.StatusCode = 200;
        res.End();
    }

//Remove any entries in the custom headers as this will throw an error that there's to  
//many values in the header.  

<httpProtocol>
    <customHeaders>
    </customHeaders>
</httpProtocol>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.