Как обрабатывать "метод OPTIONS" в ASP.NET MVC
Мое приложение Sencha Touch отправляет форму на мой веб-сервис asp.net-mvc-3, но вместо отправки POST
это отправка OPTIONS
,
Я читаю подобную ветку здесь, но я просто не знаю, как обращаться с OPTIONS
метод в моем коде.
Я попытался добавить [AllowAjax]
атрибут моего действия, однако он не существует в MVC3.
ОПЦИИ /GetInTouch/CommunicateCard HTTP/1.1
Хост: webservice.example.com
Реферер: http://192.168.5.206/ Access-Control-Request-Method: POST
Происхождение: http://192.168.5.206/
Пользователь-агент: 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-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);
}
5 ответов
Оказывается, я должен был создать 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);
}
}
}
Я решил это по-другому в 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 (решения приветствуются), но нужно сделать барбекю, и решение выше работает!
Я добавил следующее к моему <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>
Просто чтобы ответить на вопрос, почему "OPTIONS", а не "POST", это потому, что браузер реализует CORS ( совместное использование ресурсов из разных источников). Это процесс, состоящий из двух частей: сначала отправляется запрос OPTIONS, затем, если сервер отвечает с приемлемыми условиями, браузер помещает фактический запрос с данными / содержимым.
Я попробовал все ответы здесь, и ни один не работал. В конце концов я понял, что браузеры будут считать проверку перед полетом неудачной, если она возвращает не 200. В моем случае IIS возвращал 404, даже с заголовками. Это потому, что у меня было 2 атрибута в моем методе контроллера - [HttpPost] и [HttpOptions]. По-видимому, это недопустимый механизм выражения нескольких глаголов. Вместо этого мне пришлось использовать этот атрибут: [AcceptVerbs(HttpVerbs.Options | HttpVerbs.Post)]
После долгих попыток я обнаружил, что единственный способ обработать предварительный запрос CORS - это обработать его парой HttpModule и HttpHandler. Отправка требуемых заголовков не достаточно. Вы должны обработать запрос OPTIONS на ранней стадии и не позволить ему добраться до ваших контроллеров, потому что он потерпит неудачу.
Единственный способ, которым я мог сделать это, был с HttpModule.
Я следил за этим сообщением в блоге:
Подводя итог работы, это код:
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.