如何处理 ASP.NET MVC 中的“OPTIONS 方法”

how to handle "OPTIONS Method" in ASP.NET MVC

提问人:Chase Florell 提问时间:8/10/2011 最后编辑:CommunityChase Florell 更新时间:11/17/2023 访问量:26365

问:

我的 Sencha Touch 应用程序正在向我的 WebService 发布表单,但不是发送而是发送 .POSTOPTIONS

在这里阅读了一个类似的线程,但我只是不知道如何处理代码中的方法。OPTIONS

我确实尝试将 [AllowAjax] 属性添加到我的 Action 中,但它似乎在 MVC3 中不存在。

选项 /GetInTouch/CommunicateCard HTTP/1.1
主机:webservice.example.com
引用者:http://192.168.5.206/ 访问控制请求方法:POST
来源:http://192.168.5.206
User-Agent:Mozilla/5.0 (Macintosh;Intel Mac OS X 10_7_0) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.71 Safari/534.24
Access-Control-Request-Headers: X-Requested-With, Content-Type
Accept: /
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
接受字符集:ISO-8859-1,utf-8;q=0.7,*;q=0.3

在我的 ActionMethod 中,我使用以下代码。

    public JsonpResult CommunicateCard(CommunicateCard communicateCard)
    {

        // Instantiate a new instance of MailMessage
        MailMessage mMailMessage = new MailMessage();

        // removed for security/brevity

        // Set the body of the mail message
        mMailMessage.Body = communicateCard.name; // THIS IS CURRENTLY BLANK :-(

        // removed for security/brevity
        mSmtpClient.Send(mMailMessage);

        // do server side validation on form input
        // if it's valid return true
        // else return false
        // currently returning NULL cuz I don't care at this point.
        return this.Jsonp(null);
    }
asp.net-mvc-3 ajax http-options-方法

评论


答:

15赞 Chase Florell 8/10/2011 #1

事实证明,我必须创建一个ActionFilterAttribute

namespace WebService.Attributes
{
    public class AllowCrossSiteJsonAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
            HttpContext.Current.Response.Cache.SetNoStore();

            filterContext.RequestContext.HttpContext.Response.AppendHeader("Access-Control-Allow-Origin", "*");

            string rqstMethod = HttpContext.Current.Request.Headers["Access-Control-Request-Method"];
            if (rqstMethod == "OPTIONS" || rqstMethod == "POST")
            {
                filterContext.RequestContext.HttpContext.Response.AppendHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
                filterContext.RequestContext.HttpContext.Response.AppendHeader("Access-Control-Allow-Headers", "X-Requested-With, Accept, Access-Control-Allow-Origin, Content-Type");
            }
            base.OnActionExecuting(filterContext);
        }
    }
}

评论

1赞 Nathan R 5/14/2012
使用您发布的代码,我能够让 Plupload 跨站点工作。在我读到这篇文章之前,我无法弄清楚为什么它一直使用 OPTIONS HTTP 方法而不是 POST,所以谢谢!
0赞 War 5/14/2016
嗯,这不会返回正确的标头来响应我的 OPTIONS 请求:(......stackoverflow.com/questions/37216939/cors-requests-and-mvc5
4赞 From Orbonia 8/10/2012 #2

只是为了回答为什么是“OPTIONS”而不是“POST”的问题,那是因为浏览器正在实现CORS(跨域资源共享)。 这是一个由两部分组成的过程,首先发送 OPTIONS 请求,然后如果服务器以可接受的条件回复,则浏览器会发布包含数据/内容的实际请求。

11赞 From Orbonia 8/10/2012 #3

我在 MVC 和 IIS 中以不同的方式解决了这个问题。我发现这个问题的原因是我想从客户端 javascript 发布 POST 数据(JSONP 不适用于),最重要的是想要允许位于 POST 请求内容中的 JSON 数据。

实际上,您的代码希望忽略第一个 CORS OPTIONS 请求,因为这可能是“站点范围的设置”,而不是每个 API 调用设置。

首先,我将 IIS 配置为发送 CORS 响应,这可以通过 IIS 管理器(或通过 web.config 更新)完成,如果您使用 IIS,请转到要添加以下两个值的站点:

  • Access-Control-Allow-Origin 设置为“*”(为了进行测试,为了提高安全性,您可能希望将其限制为某些调用域)
  • Access-Control-Allow-Headers, “Content-Type, Accept” (用于发布 JSON 数据)

然后,我创建了一个自定义 ActionFilter,必须将其应用于要接受 POST 数据的每个控制器,这可能会触发 CORS 请求。自定义操作筛选器为:

public class CORSActionFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.HttpContext.Request.HttpMethod == "OPTIONS")
        {
            // do nothing let IIS deal with reply!
            filterContext.Result = new EmptyResult();
        }
        else
        {
            base.OnActionExecuting(filterContext);
        }
    }
}

然后,在每个控制器的开头,您需要将其应用于添加属性,例如:

[CORSActionFilter]
public class DataSourcesController : Controller

现在我确信有一种方法可以在整个 MVC 解决方案中做到这一点(欢迎解决方案),但需要制作一个烧烤,并且上面的解决方案有效!

评论

0赞 GibboK 9/11/2012
这里有一篇关于如何在 web.config 上设置的简短文章 enable-cors.org/#how-iis7
9赞 mike kozelsky 1/23/2013 #4

我在配置部分添加了以下内容:<system.webServer>

<httpProtocol>
  <customHeaders>
    <add name="Access-Control-Allow-Headers" value="Content-Type, Accept, X-Requested-With"/>
    <add name="Access-Control-Allow-Methods" value="GET, POST, OPTIONS"/>
    <add name="Access-Control-Allow-Origin" value="*"/>
  </customHeaders>
</httpProtocol>

评论

0赞 Chase Florell 1/23/2013
很好的方法,除了它是全球性的。可接受的答案可以基于“每个控制器”应用。
2赞 r590 10/27/2016 #5

我在这里尝试了所有答案,但没有一个有效。我最终意识到,如果预检返回非 200,浏览器会将其视为失败。就我而言,IIS 返回 404,即使有标头也是如此。这是因为我的控制器方法上有 2 个属性 - [HttpPost] 和 [HttpOptions]。显然,这不是表达多个动词的有效机制。我不得不改用这个属性:[AcceptVerbs(HttpVerbs.Options | HttpVerbs.Post)]

1赞 Mahmood Dehghan 2/13/2019 #6

经过一番挣扎,我发现处理 CORS 预检请求的唯一方法是使用一对 HttpModule 和 HttpHandler 来处理它。 仅发送所需的标头是不够的。您必须尽早处理 OPTIONS 请求,并且不允许它到达您的控制器,因为它会在那里失败。

我能做到这一点的唯一方法是使用 HttpModule。

我关注了这篇博文:

http://geekswithblogs.net/abhijeetp/archive/2016/06/04/adding-cors-support-for-asp.net--webapi-the-no-hassle.aspx

总结一下工作,代码如下:

namespace WebAPI.Infrastructure
{
    using System;
    using System.Web;
    using System.Collections;
    using System.Net;
    public class CrossOriginModule : IHttpModule
    {
        public String ModuleName
        {
            get { return "CrossOriginModule"; }
        }

        public void Init(HttpApplication application)
        {
            application.BeginRequest += (new EventHandler(this.Application_BeginRequest));
        }

        private void Application_BeginRequest(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;
            CrossOriginHandler.AddCorsResponseHeaders(context);
        }

        public void Dispose()
        {
        }
    }

    public class CrossOriginHandler : IHttpHandler
    {
        #region Data Members
        const string OPTIONS = "OPTIONS";
        const string PUT = "PUT";
        const string POST = "POST";
        const string PATCH = "PATCH";
        static string[] AllowedVerbs = new[] { OPTIONS, PUT, POST, PATCH };
        const string Origin = "Origin";
        const string AccessControlRequestMethod = "Access-Control-Request-Method";
        const string AccessControlRequestHeaders = "Access-Control-Request-Headers";
        const string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
        const string AccessControlAllowMethods = "Access-Control-Allow-Methods";
        const string AccessControlAllowHeaders = "Access-Control-Allow-Headers";
        const string AccessControlAllowCredentials = "Access-Control-Allow-Credentials";
        const string AccessControlMaxAge = "Access-Control-Max-Age";
        const string MaxAge = "86400";
        #endregion

        #region IHttpHandler Members
        public bool IsReusable
        {
            get { return true; }
        }

        public void ProcessRequest(HttpContext context)
        {
            switch (context.Request.HttpMethod.ToUpper())
            {
                //Cross-Origin preflight request
                case OPTIONS:
                    AddCorsResponseHeaders(context);
                    break;

                default:
                    break;
            }
        }
        #endregion

        #region Static Methods
        public static void AddCorsResponseHeaders(HttpContext context)
        {
            if (Array.Exists(AllowedVerbs, av => string.Compare(context.Request.HttpMethod, av, true) == 0))
            {
                var request = context.Request;
                var response = context.Response;
                var originArray = request.Headers.GetValues(Origin);
                var accessControlRequestMethodArray = request.Headers.GetValues(AccessControlRequestMethod);
                var accessControlRequestHeadersArray = request.Headers.GetValues(AccessControlRequestHeaders);
                if (originArray != null &&
                    originArray.Length > 0)
                    response.AddHeader(AccessControlAllowOrigin, originArray[0]);
                response.AddHeader(AccessControlAllowCredentials, bool.TrueString.ToLower());

                if (accessControlRequestMethodArray != null &&
                    accessControlRequestMethodArray.Length > 0)
                {
                    string accessControlRequestMethod = accessControlRequestMethodArray[0];
                    if (!string.IsNullOrEmpty(accessControlRequestMethod))
                    {
                        response.AddHeader(AccessControlAllowMethods, accessControlRequestMethod);
                    }
                }
                if (accessControlRequestHeadersArray != null &&
                    accessControlRequestHeadersArray.Length > 0)
                {
                    string requestedHeaders = string.Join(", ", accessControlRequestHeadersArray);
                    if (!string.IsNullOrEmpty(requestedHeaders))
                    {
                        response.AddHeader(AccessControlAllowHeaders, requestedHeaders);
                    }
                }
            }
            if (context.Request.HttpMethod == OPTIONS)
            {
                context.Response.AddHeader(AccessControlMaxAge, MaxAge);
                context.Response.StatusCode = (int)HttpStatusCode.OK;
                context.Response.End();
            }
        } 
        #endregion
    }
}

并将它们添加到:web.config

<system.webServer>  
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="WebDAVModule" />
      <add name="CrossOriginModule" preCondition="managedHandler" type="WebAPI.Infrastructure.CrossOriginModule, Your_Assembly_Name" />
    </modules>
    <handlers>
      <remove name="WebDAV"/>
      <remove name="OPTIONSVerbHandler"/>
      <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" />
      <add name="CrossOrigin" verb="OPTIONS" path="*" type="WebAPI.Infrastructure.CrossOriginHandler, Your_Assembly_Name" />
    </handlers>   
     <security>
       <authorization>
         <remove users="*" roles="" verbs=""/>
         <add accessType="Allow" users="*" verbs="GET,HEAD,POST,PUT,PATCH,DELETE,DEBUG"/>
       </authorization>
     <requestFiltering>
       <requestLimits maxAllowedContentLength="6000"/>
       <verbs>
         <remove verb="OPTIONS"/>
         <remove verb="PUT"/>
         <remove verb="PATCH"/>
         <remove verb="POST"/>         
         <remove verb="DELETE"/>
       </verbs>
     </requestFiltering>
   </security> 
  </system.webServer>

这适用于 Web API 和 MVC。

评论

0赞 mcheah 5/4/2019
这也是对我有用的东西。我遇到了允许 CORS 请求但 OPTIONS 请求直接进入控制器的问题,或者以其他方式被属性阻止。感谢您发布此内容![HttpPost]
0赞 Rob Von Nesselrode 11/8/2019
您的解决方案确实运行良好,但根本不需要处理程序,因为您使用“Response.End”短路模块中的任何 OPTIONS 请求。因此,Options 请求永远不会到达 Handler。我认为您正在做的是消除添加到所有请求的响应标头的 WebConfig 条目的需求,而不仅仅是处理 OPTIONS