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

Posted

技术标签:

【中文标题】如何在 ASP.NET MVC/WebAPI 应用程序中支持 HTTP OPTIONS 动词【英文标题】:How to support HTTP OPTIONS verb in ASP.NET MVC/WebAPI application 【发布时间】:2013-10-06 09:47:51 【问题描述】:

我已经设置了一个从 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 Method Not Allowed。

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 方法,或者控制器是否是放置代码的合适位置。奇怪(对我来说)Web API 站点在从 Firefox 或 Chrome 查看时会正确响应,但上面的 .ajax 调用会出错。如何处理 .ajax 代码中的“预检”检查?也许我应该在客户端的 .ajax 逻辑上解决这个问题?或者,如果这是由于未处理 OPTIONS 动词而导致的服务器端问题。

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

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

我发现了另一个帖子:Prototype AJAX request being sent as OPTIONS rather than GET; results in 501 error,这似乎是相关的,但我已经修改了他们的建议,但没有成功。显然,JQuery 是这样编码的,因此如果 Ajax 请求是跨域的(我的请求是跨域的),它会添加几个以某种方式触发 OPTIONS 标头的标头。

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

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

下面提供的答案假定这是一个服务器端问题。也许吧,我猜,但我倾向于客户,打电话给托管服务提供商也无济于事。

【问题讨论】:

您打算向选项请求发送什么? 我根本不需要发送 OPTIONS 请求。出于某种原因,当跨域进行 Ajax 调用时,这会完成。因此,正如您在 Javascript 中看到的,我所做的只是指定 GET,但 OPTIONS 标头是由于 HTTP 协议而发送的。这是一个“预检”检查。 哦,你应该在你的 iis 服务器上启用 cors。 这是一个 Arvixe 服务器 - Business Class Pro。两个站点都托管在同一物理服务器上,同一托管帐户。只是不同的主机名。我可以在不调用 Arvixe 的情况下启用 CORS 吗? 我会打电话给您的托管服务提供商。 【参考方案1】:

正如 Daniel A. White 在他的评论中所说,OPTIONS 请求很可能是由客户端创建的,作为跨域 JavaScript 请求的一部分。这是由跨域资源共享 (CORS) 兼容的浏览器自动完成的。该请求是一个初步或 pre-flight 请求,在实际 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 时,支持的 HTTP 动词 服务器是否支持通过跨域请求发送凭据

一般来说,这可以正常工作,但您需要确保了解安全风险,尤其是当您允许来自任何域的跨源请求时。在允许之前请仔细考虑。

关于哪些浏览器支持 CORS,***表示以下引擎支持它:

Gecko 1.9.1 (FireFox 3.5) WebKit(Safari 4、Chrome 3) MSHTML/Trident 6 (IE10) 部分支持 IE8 和 9 Presto (Opera 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 该链接确实是一个涉及添加对 Web API 5.2.2 的依赖项的解决方案,但该解决方案比强制 MVC 支持 OPTIONS 飞行前请求的黑客更具可扩展性。您可能还想查看 Dominick 的回答,因为飞行前请求可能是 Accept OR Content-Type 标头的结果,需要客户端进行此类调用 请注意,但如果您将内容类型设置为:'application/x-www-form-urlencoded'、'multipart/form-data' 或 'text/plain' 那么请求是被认为“简单”,不会发出飞行前请求。【参考方案2】:

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

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

请求方法为GETHEADPOST

应用程序没有设置除AcceptAccept-LanguageContent-LanguageContent-TypeLast-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(在“预检请求”下)

【讨论】:

【参考方案3】:

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 您也可以使用 [ApiExplorerSettings(IgnoreApi = true)] 忽略 Swagger 上的 OPTIONS 端点。 这适用于我的 WebApi 2 应用程序。特别是相关/基本控制器的 Options() 方法 这适用于我在 net core 3.1 中的 web api - 谢谢!【参考方案4】:

我也遇到了同样的问题。

按照以下步骤解决浏览器中的 (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。

【讨论】:

什么是红岩?我进行了 Google 搜索和 Nuget 包搜索,但没有返回任何内容。一个链接会很好。【参考方案5】:
    protected void Application_EndRequest()
    
        if (Context.Response.StatusCode == 405 && Context.Request.HttpMethod == "OPTIONS" )
        
            Response.Clear();
            Response.StatusCode = 200;
            Response.End();
        
    

【讨论】:

【参考方案6】:

在 ASP.NET web api 2 中,添加了 CORS 支持。请查看链接[http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api]

【讨论】:

【参考方案7】:

只需将其添加到您的 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>

希望对你有帮助

【讨论】:

谢谢。只有这个Application_OnBeginRequest 帮助了我。但是如果您也希望能够通过授权获取数据,您还应该将Authorization添加到Access-Control-Allow-Headers【参考方案8】:

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

只需将其放入您的 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>

【讨论】:

【参考方案9】:

在 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 需要设置为集成模式。

【讨论】:

【参考方案11】:
//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>

【讨论】:

以上是关于如何在 ASP.NET MVC/WebAPI 应用程序中支持 HTTP OPTIONS 动词的主要内容,如果未能解决你的问题,请参考以下文章

Asp.Net MVC WebApi CORS 请求失败 - 没有建议的解决方案影响结果

asp.net mvc webapi接收参数问题

asp.net mvc webapi 实用的接口加密方法

ASP.NET MVC WebApi 返回数据类型序列化控制(json,xml)

了解 ASP.NET(MVC、Web API)中的令牌生成/验证

asp.net core 3.1 MVC/WebApi JSON 全局配置