как обрабатывать метод OPTIONS в ASP.NET MVC

Мое приложение Sencha Touch отправляет форму в мой asp.net-mvc-3 WebService, но вместо отправки POST он отправляет OPTIONS.

Я читаю аналогичную ветку здесь , но я просто не знаю, как обращаться с методом OPTIONS в моем коде.

Я попытался добавить атрибут [AllowAjax] к моему действию, но, похоже, его нет в 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, например 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
Accept-Charset: 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);
    }

person Chase Florell    schedule 09.08.2011    source источник


Ответы (6)


Оказывается, мне пришлось создать 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);
        }
    }
}
person Chase Florell    schedule 09.08.2011
comment
Используя ваш опубликованный код, я смог заставить Plupload работать на разных сайтах. Я не мог понять, почему он продолжал использовать HTTP-метод OPTIONS вместо POST, пока не прочитал это, так что спасибо! - person Nathan R; 14.05.2012
comment
Хммм, это не возвращает правильные заголовки в ответ на запрос OPTIONS для меня :( ... stackoverflow.com/questions/37216939/cors-requests-and-mvc5 - person War; 13.05.2016

Я решил это по-другому в MVC и IIS. Причина, по которой я обнаружил эту проблему, заключалась в том, что я хотел отправить данные POST из javascript на стороне клиента (для которого JSONP не работает), и, кроме того, хотел разрешить данные JSON, которые находятся внутри содержимого запроса POST.

На самом деле ваш код хочет игнорировать первый запрос 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 (решения приветствуются), но нужно сделать барбекю, и решение, приведенное выше, работает!

person From Orbonia    schedule 10.08.2012
comment
здесь краткая статья о том, как установить в web.config enable-cors.org/#how- iis7 - person GibboK; 11.09.2012

Я добавил следующее в свой раздел конфигурации <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>
person mike kozelsky    schedule 22.01.2013
comment
хороший подход, за исключением того, что он глобальный. Принятый ответ может применяться для каждого контроллера. - person Chase Florell; 23.01.2013

Просто чтобы ответить на вопрос, почему «OPTIONS», а не «POST», это потому, что браузер реализует CORS (Совместное использование ресурсов из разных источников). Это двухэтапный процесс: сначала отправляется запрос OPTIONS, а затем, если сервер отвечает приемлемыми условиями, браузер затем ОТПРАВЛЯЕТ фактический запрос с данными/контентом.

person From Orbonia    schedule 10.08.2012

Я пробовал все ответы здесь, и никто не работал. В конце концов я понял, что браузеры будут считать предварительную проверку неудачной, если она возвращает не 200. В моем случае IIS возвращал 404 даже с заголовками. Это потому, что у меня было 2 атрибута в моем методе контроллера — [HttpPost] и [HttpOptions]. По-видимому, это недопустимый механизм для выражения нескольких глаголов. Вместо этого мне пришлось использовать этот атрибут: [AcceptVerbs(HttpVerbs.Options | HttpVerbs.Post)]

person r590    schedule 26.10.2016

После долгих мучений я обнаружил, что единственный способ обработать предварительный запрос 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>

Это работает для веб-API и MVC.

person Mahmood Dehghan    schedule 12.02.2019
comment
Это то, что сработало и для меня. У меня была проблема, что запросы CORS были разрешены, но запросы OPTIONS шли прямо к контроллеру или иным образом блокировались атрибутом [HttpPost]. Спасибо, что опубликовали это! - person mcheah; 04.05.2019
comment
Ваше решение прекрасно работает, но ему вообще не нужен обработчик, поскольку вы замыкаете любой запрос OPTIONS в модуле с помощью «Response.End». Следовательно, запрос параметров никогда не доходит до обработчика. Я думаю, что вы устраняете необходимость в записях WebConfig для заголовков ответов, добавленных ко всем запросам, а не только для работы с ОПЦИЯМИ. - person Rob Von Nesselrode; 08.11.2019