【问题标题】:how to handle "OPTIONS Method" in ASP.NET MVC如何在 ASP.NET MVC 中处理“OPTIONS 方法”
【发布时间】:2011-10-23 12:43:42
【问题描述】:

我的 Sencha Touch 应用程序正在向我的 WebService 发布一个表单,但不是发送 POST,而是发送 OPTIONS。

我正在阅读类似的线程here,但我只是不知道如何处理我的代码中的OPTIONS 方法。

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

选项 /GetInTouch/CommunicateCard HTTP/1.1
主机:webservice.example.com
推荐人:http://192.168.5.206/ 访问控制请求方法:POST
来源:http://192.168.5.206
用户代理: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
访问控制请求标头:X-Requested-With、Content-Type
接受:/
接受编码:gzip、deflate、sdch
接受语言: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 asp.net-mvc-3 http-options-method


    【解决方案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);
            }
        }
    }
    

    【讨论】:

    【解决方案2】:

    我在 MVC 和 IIS 中以不同的方式解决了这个问题。我发现这个问题的原因是因为我想从客户端 javascript(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 解决方案中执行此操作(欢迎使用解决方案),但需要进行烧烤并且上述解决方案有效!

    【讨论】:

    【解决方案3】:

    我将以下内容添加到我的 <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>
    

    【讨论】:

    • 好方法,除了它是全球性的。接受的答案可以在“每个控制器”的基础上应用。
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        【解决方案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。

        【讨论】:

        • 这对我也有用。我遇到了允许 CORS 请求但 OPTIONS 请求直接进入控制器的问题,或者被[HttpPost] 属性阻止。感谢您发布此信息!
        • 您的解决方案确实运行良好,但根本不需要处理程序,因为您使用“Response.End”将模块中的任何 OPTIONS 请求短路。因此,选项请求永远不会到达处理程序。我认为您正在做的是消除对添加到所有请求的响应标头的 WebConfig 条目的需求,而不仅仅是处理 OPTIONS
        猜你喜欢
        • 2012-03-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-02
        • 2016-12-08
        • 1970-01-01
        相关资源
        最近更新 更多