net mvc headers enablecors enable control asp allow asp.net-mvc cors

asp.net-mvc - mvc - enablecors c#



Manejo de solicitudes de comprobaciĆ³n previa de CORS para acciones de ASP.NET MVC (6)

Estoy tratando de realizar una solicitud POST entre dominios a una acción de controlador ASP.NET MVC. Esta acción del controlador acepta y usa varios parámetros. El problema es que cuando se produce la solicitud de verificación previa, la acción del controlador realmente intenta ejecutar y porque la solicitud de OPCIONES no pasa ningún dato, la acción del controlador arroja un error HTTP 500. Si elimino el código que usa el parámetro o el parámetro en sí, toda la cadena de solicitud se completa con éxito.

Un ejemplo de cómo esto se implementa:

Acción del controlador

public ActionResult GetData(string data) { return new JsonResult { Data = data.ToUpper(), JsonRequestBehavior = JsonRequestBehavior.AllowGet }; }

Código del lado del cliente

<script type="text/javascript"> $(function () { $("#button-request").click(function () { var ajaxConfig = { dataType: "json", url: "http://localhost:8100/host/getdata", contentType: ''application/json'', data: JSON.stringify({ data: "A string of data" }), type: "POST", success: function (result) { alert(result); }, error: function (jqXHR, textStatus, errorThrown) { alert(''Error: Status: '' + textStatus + '', Message: '' + errorThrown); } }; $.ajax(ajaxConfig); }); }); </script>

Ahora, cada vez que se realiza la solicitud de verificación previa, devuelve un código HTTP 500, porque el parámetro "datos" es nulo, ya que la solicitud OPCIONES no pasa ningún valor.

La aplicación del servidor se ha configurado en mi IIS local en el puerto 8100 y la página que ejecuta el código del lado del cliente está configurada en el puerto 8200 para imitar las llamadas entre dominios.

También configuré el host (en 8100) con los siguientes encabezados:

Access-Control-Allow-Headers: Content-Type Access-Control-Allow-Methods: POST, GET Access-Control-Allow-Origin: http://localhost:8200

Una solución que encontré fue verificar el método HTTP que ejecuta la acción y si es una solicitud de OPCIONES simplemente devolver el contenido en blanco, de lo contrario ejecute el código de acción. Al igual que:

public ActionResult GetData(string data) { if (Request.HttpMethod == "OPTIONS") { return new ContentResult(); } else { return new JsonResult { Data = data.ToUpper(), JsonRequestBehavior = JsonRequestBehavior.AllowGet }; } }

Pero este enfoque me parece muy torpe. Consideré agregar este tipo de lógica a un Attribute , pero incluso esto significaría decorar cada acción que se llamará usando CORS con él.

¿Hay alguna solución más elegante para que funcione esta funcionalidad?


Así es como manejé los problemas de Preflight / CORS con ASP.Net Web Api. Simplemente agregué el paquete Microsoft.AspNet.WebApi.Cors Nuget a mi proyecto web. Luego en mi archivo WebApiConfig.cs agregué esta línea:

config.EnableCors(new ApplicationCorsPolicy());

y creó una clase PolicyProvider personalizada

public class ApplicationCorsPolicy : Attribute, ICorsPolicyProvider { public async Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var corsRequestContext = request.GetCorsRequestContext(); var originRequested = corsRequestContext.Origin; if (await IsOriginFromAPaidCustomer(originRequested)) { // Grant CORS request var policy = new CorsPolicy { AllowAnyHeader = true, AllowAnyMethod = true }; policy.Origins.Add(originRequested); return policy; } // Reject CORS request return null; } private async Task<bool> IsOriginFromAPaidCustomer(string originRequested) { // Do database look up here to determine if origin should be allowed. // In my application I have a table that has a list of domains that are // allowed to make API requests to my service. This is validated here. return true; } }

Vea, el marco de Cors le permite agregar su propia lógica para determinar qué orígenes están permitidos, etc. Esto es muy útil si está exponiendo una API REST al mundo exterior y la lista de personas (orígenes) que pueden acceder a su sitio son en un entorno controlado como una base de datos. Ahora, si simplemente está permitiendo todos los orígenes (lo que podría no ser una buena idea en todos los casos), puede hacerlo en WebApiConfig.cs para habilitar CORS globalmente:

config.EnableCors();

Al igual que Filters y Handlers en WebApi, también puedes agregar anotaciones de nivel de clase o método a tus controladores de la siguiente manera:

[EnableCors("*, *, *, *")]

Tenga en cuenta que el atributo EnableCors tiene un constructor que acepta los siguientes parámetros

  1. Lista de orígenes permitidos
  2. Lista de encabezados de solicitud permitidos
  3. Lista de métodos HTTP permitidos
  4. Lista de encabezados de respuesta permitidos

Puede especificar estáticamente en cada controlador / punto final que tiene permiso para acceder a qué recurso.

Actualización 24/06/2016: Debo mencionar que tengo lo siguiente en mi Web.config. Parece que estos pueden no ser los valores predeterminados para todos.

<system.webServer> <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> </system.webServer>

Fuente: Microsoft


Entonces encontré una solución que funciona. Para cada solicitud, compruebo si se trata de una solicitud CORS y si la solicitud viene con el verbo OPTIONS, lo que indica que es la solicitud de verificación previa. Si es así, simplemente devuelvo una respuesta vacía (que solo contiene los encabezados configurados en IIS, por supuesto), anulando la ejecución de la acción del controlador.

Luego, si el cliente confirma que puede realizar la solicitud en función de los encabezados devueltos desde la verificación previa, se realiza la POST real y se ejecuta la acción del controlador. Y ejemplo de mi código:

protected void Application_BeginRequest() { if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) && Request.HttpMethod == "OPTIONS") { Response.Flush(); } }

Como mencioné, esto funcionó para mí, pero si alguien sabe de una mejor manera, o de cualquier falla en mi implementación actual, agradecería saber de ellos.


Esto puede ser una pista falsa. Recientemente, CORS ha trabajado bien sin saltarte ninguno de los aros que estás haciendo.

Esto se hizo usando una combinación del paquete nute Thinktecture.IdentityModel, y más importante aún ... REMOCIÓN de todas las referencias a WebDAV. Esto incluye eliminar el módulo webdav de IIS y garantizar las siguientes líneas en su configuración web:

<system.webServer> <validation validateIntegratedModeConfiguration="false" /> <modules runAllManagedModulesForAllRequests="true"> <remove name="WebDAVModule" /> <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" /> <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" /> <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" preCondition="managedHandler" /> </modules> <handlers> <remove name="WebDAV" /> <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>

Luego puede usar thinktecture para configurar su CORS desde su Global.asax usando una clase estática como esta:

public class CorsConfig { public static void RegisterCors(HttpConfiguration httpConfiguration) { var corsConfig = new WebApiCorsConfiguration(); corsConfig.RegisterGlobal(httpConfiguration); corsConfig.ForAllResources().AllowAllOriginsAllMethodsAndAllRequestHeaders(); } }

FUENTE: http://brockallen.com/2012/06/28/cors-support-in-webapi-mvc-and-iis-with-thinktecture-identitymodel/


La respuesta aceptada funciona como un amuleto, pero descubrí que la solicitud en realidad se transmitía al controlador. Estaba recibiendo un código de estado de 200 , pero el cuerpo de la respuesta contenía una gran cantidad de HTML con una excepción del controlador. Entonces, en lugar de usar Response.Flush() , encontré que era mejor usar Response.End() , que detiene la ejecución de la solicitud. Esta solución alternativa se vería así:

EDITAR: corrigió un error tipográfico realizado a partir de la respuesta original.

protected void Application_BeginRequest() { if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) && Request.HttpMethod == "OPTIONS") { Response.End(); } }


Ninguna de estas respuestas funcionó para mí, pero sí las siguientes configuraciones de webconfig. Las dos configuraciones clave para mí fueron configurar Access-Control-Allow-Headers para Content-Type y comentar la línea que elimina el OPTIONSVerbHandler :

<system.webServer> <modules runAllManagedModulesForAllRequests="true"></modules> <httpProtocol> <customHeaders> <add name="Access-Control-Allow-Origin" value="*" /> <add name="Access-Control-Allow-Headers" value="Content-Type" /> </customHeaders> </httpProtocol> <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> </system.webServer>


expandiendo la respuesta de Carl, tomé su código y lo conecté a mi canal OWIN:

app.Use((context, next) => { if (context.Request.Headers.Any(k => k.Key.Contains("Origin")) && context.Request.Method == "OPTIONS") { context.Response.StatusCode = 200; return context.Response.WriteAsync("handled"); } return next.Invoke(); });

Simplemente agregue esto al principio (o en cualquier lugar antes de registrar la WebAPI) de su IAppBuilder en Startup.cs