Habilitar solicitudes entre orígenes en ASP.NET Web API 2
Por Mike Wasson
Este contenido es para una versión anterior de .NET. El nuevo desarrollo debe utilizar ASP.net Core. Para obtener más información sobre el uso de Web API y solicitudes entre orígenes (CORS) en ASP.NET Core, consulte:
- Tutorial: Creación de una API web con ASP.NET Core
- Habilitación de solicitudes entre orígenes (CORS) en ASP.NET Core
La seguridad del explorador impide que una página web realice solicitudes AJAX a otro dominio. Esta restricción se conoce como directiva de mismo origen y evita que un sitio malintencionado lea información confidencial de otro sitio. Sin embargo, a veces es posible que quiera permitir que otros llamen a su API web.
El uso compartido de recursos entre orígenes (CORS) es un estándar del W3C que permite a un servidor relajar la Directiva del mismo origen. Con CORS, un servidor puede permitir explícitamente algunas solicitudes de origen cruzado y rechazar otras. CORS es más seguro y más flexible que las técnicas anteriores, como JSONP. En este tutorial se muestra cómo habilitar CORS en la aplicación de API Web.
Software usado en el tutorial
- Visual Studio
- API Web 2,2
Introducción
En este tutorial se muestra la compatibilidad con CORS en ASP.NET Web API. Comenzaremos creando dos proyectos de ASP.NET: uno denominado "WebService", que hospeda un controlador de API Web y el otro denominado "WebClient", que llama a WebService. Dado que las dos aplicaciones se hospedan en dominios diferentes, una solicitud AJAX de WebClient a WebService es una solicitud entre orígenes.

¿Qué es el "mismo origen"?
Dos direcciones URL tienen el mismo origen si tienen esquemas, hosts y puertos idénticos. (RFC 6454)
Estas dos direcciones URL tienen el mismo origen:
http://example.com/foo.htmlhttp://example.com/bar.html
Estas direcciones URL tienen distintos orígenes que los dos anteriores:
http://example.net-Diferente dominiohttp://example.com:9000/foo.html-Puerto diferentehttps://example.com/foo.html-Esquema diferentehttp://www.example.com/foo.html-Subdominio diferente
Note
Internet Explorer no tiene en cuenta el puerto al comparar los orígenes.
Crear el proyecto WebService
Note
En esta sección se da por supuesto que ya sabe cómo crear proyectos de Web API. Si no es así, vea Introducción con ASP.net web API.
Inicie Visual Studio y cree un nuevo proyecto de aplicación Web de ASP.net (.NET Framework) .
En el cuadro de diálogo nueva aplicación Web de ASP.net , seleccione la plantilla de proyecto vacía . En Agregar carpetas y referencias principales para, active la casilla API Web .

Agregue un controlador de API Web denominado
TestControllercon el código siguiente:using System.Net.Http; using System.Web.Http; namespace WebService.Controllers { public class TestController : ApiController { public HttpResponseMessage Get() { return new HttpResponseMessage() { Content = new StringContent("GET: Test message") }; } public HttpResponseMessage Post() { return new HttpResponseMessage() { Content = new StringContent("POST: Test message") }; } public HttpResponseMessage Put() { return new HttpResponseMessage() { Content = new StringContent("PUT: Test message") }; } } }Puede ejecutar la aplicación de forma local o implementarla en Azure. (Para las capturas de pantallas de este tutorial, la aplicación se implementa en Azure App Service Web Apps). Para comprobar que la API Web funciona, vaya a
http://hostname/api/test/, donde hostname es el dominio en el que ha implementado la aplicación. Debería ver el texto de la respuesta, " get: test Message " .
Crear el proyecto de WebClient
Cree otro proyecto de aplicación Web de ASP.net (.NET Framework) y seleccione la plantilla de proyecto de MVC . Opcionalmente, seleccione cambiar autenticación > sin autenticación. No necesita autenticación para este tutorial.

En Explorador de soluciones, abra el archivo views/home/index. cshtml. Reemplace el código de este archivo por lo siguiente:
<div> <select id="method"> <option value="get">GET</option> <option value="post">POST</option> <option value="put">PUT</option> </select> <input type="button" value="Try it" onclick="sendRequest()" /> <span id='value1'>(Result)</span> </div> @section scripts { <script> // TODO: Replace with the URL of your WebService app var serviceUrl = 'http://mywebservice/api/test'; function sendRequest() { var method = $('#method').val(); $.ajax({ type: method, url: serviceUrl }).done(function (data) { $('#value1').text(data); }).fail(function (jqXHR, textStatus, errorThrown) { $('#value1').text(jqXHR.responseText || textStatus); }); } </script> }Para la variable serviceUrl , use el URI de la aplicación WebService.
Ejecutar la aplicación WebClient localmente o publicarla en otro sitio Web.
Al hacer clic en el botón "pruébelo", se envía una solicitud AJAX a la aplicación WebService mediante el método HTTP que se muestra en el cuadro desplegable (GET, POST o PUT). Esto le permite examinar diferentes solicitudes entre orígenes. Actualmente, la aplicación WebService no es compatible con CORS, por lo que si hace clic en el botón obtendrá un error.

Note
Si observa el tráfico HTTP en una herramienta como Fiddler, verá que el explorador envía la solicitud GET y la solicitud se realiza correctamente, pero la llamada AJAX devuelve un error. Es importante comprender que la Directiva del mismo origen no impide que el explorador envíe la solicitud. En su lugar, impide que la aplicación vea la respuesta.

Habilitación de CORS
Ahora vamos a habilitar CORS en la aplicación WebService. En primer lugar, agregue el paquete NuGet de CORS. En Visual Studio, en el menú herramientas , seleccione Administrador de paquetes NuGet y, a continuación, seleccione consola del administrador de paquetes. En la ventana de la consola del administrador de paquetes, escriba el siguiente comando:
Install-Package Microsoft.AspNet.WebApi.Cors
Este comando instala el paquete más reciente y actualiza todas las dependencias, incluidas las bibliotecas principales de API Web. Use la -Version marca para tener como destino una versión específica. El paquete CORS requiere Web API 2,0 o posterior.
Abra la aplicación de archivo _ Start/WebApiConfig. CS. Agregue el código siguiente al método WebApiConfig. Register :
using System.Web.Http;
namespace WebService
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// New code
config.EnableCors();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
A continuación, agregue el atributo [EnableCors] a la TestController clase:
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Cors;
namespace WebService.Controllers
{
[EnableCors(origins: "http://mywebclient.azurewebsites.net", headers: "*", methods: "*")]
public class TestController : ApiController
{
// Controller methods not shown...
}
}
En el caso del parámetro Origins, use el URI en el que implementó la aplicación WebClient. Esto permite las solicitudes entre orígenes de WebClient, a la vez que no se permiten todas las demás solicitudes entre dominios. Más adelante, describiré los parámetros de [EnableCors] con más detalle.
No incluya una barra diagonal al final de la dirección URL de origen .
Vuelva a implementar la aplicación WebService actualizada. No es necesario actualizar WebClient. Ahora la solicitud AJAX de WebClient debe realizarse correctamente. Se permiten los métodos GET, PUT y POST.

Cómo funciona CORS
En esta sección se describe lo que sucede en una solicitud de CORS, en el nivel de los mensajes HTTP. Es importante comprender cómo funciona CORS, de modo que pueda configurar el atributo [EnableCors] correctamente y solucionar los problemas si no funciona como se espera.
La especificación CORS presenta varios encabezados HTTP nuevos que permiten solicitudes entre orígenes. Si un explorador admite CORS, establece estos encabezados automáticamente para las solicitudes entre orígenes. no es necesario hacer nada especial en el código de JavaScript.
A continuación se muestra un ejemplo de una solicitud entre orígenes. El encabezado "Origin" proporciona el dominio del sitio que realiza la solicitud.
GET http://myservice.azurewebsites.net/api/test HTTP/1.1
Referer: http://myclient.azurewebsites.net/
Accept: */*
Accept-Language: en-US
Origin: http://myclient.azurewebsites.net
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net
Si el servidor permite la solicitud, establece el encabezado Access-Control-Allow-Origin. El valor de este encabezado coincide con el encabezado de origen o es el valor de carácter comodín " * ", lo que significa que se permite cualquier origen.
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/plain; charset=utf-8
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Date: Wed, 05 Jun 2013 06:27:30 GMT
Content-Length: 17
GET: Test message
Si la respuesta no incluye el encabezado Access-Control-Allow-Origin, se produce un error en la solicitud AJAX. En concreto, el explorador no permite la solicitud. Aunque el servidor devuelva una respuesta correcta, el explorador no pone la respuesta a disposición de la aplicación cliente.
Solicitudes preparatorias
En algunas solicitudes de CORS, el explorador envía una solicitud adicional, denominada "solicitud preparatoria", antes de enviar la solicitud real para el recurso.
El explorador puede omitir la solicitud preparatoria si se cumplen las condiciones siguientes:
El método de solicitud es GET, HEAD o POST, y
La aplicación no establece ningún encabezado de solicitud que no sea Accept, Accept-Language, Content-Language, Content-Type o Last-Event-ID, y
El encabezado Content-Type (si se establece) es uno de los siguientes:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
La regla sobre los encabezados de solicitud se aplica a los encabezados que establece la aplicación mediante una llamada a setRequestHeader en el objeto XMLHttpRequest . (La especificación CORS llama a estos "encabezados de solicitud de autor"). La regla no se aplica a los encabezados que el Explorador puede establecer, como el agente de usuario, el host o la longitud del contenido.
Este es un ejemplo de una solicitud preparatoria:
OPTIONS http://myservice.azurewebsites.net/api/test HTTP/1.1
Accept: */*
Origin: http://myclient.azurewebsites.net
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: accept, x-my-custom-header
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net
Content-Length: 0
La solicitud previa al vuelo usa el método HTTP OPTIONs. Incluye dos encabezados especiales:
- Access-Control-request-Method: método HTTP que se utilizará para la solicitud real.
- Access-Control-request-headers: una lista de encabezados de solicitud que la aplicación estableció en la solicitud real. (De nuevo, esto no incluye los encabezados que establece el explorador).
A continuación se muestra una respuesta de ejemplo, suponiendo que el servidor permite la solicitud:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 0
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Access-Control-Allow-Headers: x-my-custom-header
Access-Control-Allow-Methods: PUT
Date: Wed, 05 Jun 2013 06:33:22 GMT
La respuesta incluye un encabezado Access-Control-Allow-Methods que enumera los métodos permitidos y, opcionalmente, un encabezado Access-Control-Allow-headers, que enumera los encabezados permitidos. Si la solicitud preparatoria se realiza correctamente, el explorador envía la solicitud real, como se describió anteriormente.
Las herramientas que se usan normalmente para probar extremos con solicitudes de opciones preparatorias (por ejemplo, Fiddler y Postman) no envían de forma predeterminada los encabezados de opciones necesarios. Confirme que los Access-Control-Request-Method Access-Control-Request-Headers encabezados y se envían con la solicitud y que los encabezados de opciones llegan a la aplicación a través de IIS.
Para configurar IIS de forma que permita que una aplicación ASP.NET reciba y controle las solicitudes de opción, agregue la siguiente configuración al archivo de web.config de la aplicación en la <system.webServer><handlers> sección:
<system.webServer>
<handlers>
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<remove name="OPTIONSVerbHandler" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
</system.webServer>
La eliminación de OPTIONSVerbHandler impide que IIS controle las solicitudes Options. El reemplazo de ExtensionlessUrlHandler-Integrated-4.0 permite que las solicitudes de opciones lleguen a la aplicación porque el registro del módulo predeterminado solo permite solicitudes GET, Head, post y Debug con direcciones URL sin extensión.
Reglas de ámbito para [EnableCors]
Puede habilitar CORS por acción, por controlador o globalmente para todos los controladores de la API Web de la aplicación.
Por acción
Para habilitar CORS para una única acción, establezca el atributo [EnableCors] en el método de acción. En el ejemplo siguiente se habilita CORS GetItem solo para el método.
public class ItemsController : ApiController
{
public HttpResponseMessage GetAll() { ... }
[EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
public HttpResponseMessage GetItem(int id) { ... }
public HttpResponseMessage Post() { ... }
public HttpResponseMessage PutItem(int id) { ... }
}
Por controlador
Si establece [EnableCors] en la clase del controlador, se aplica a todas las acciones del controlador. Para deshabilitar CORS para una acción, agregue el atributo [DisableCors] a la acción. En el ejemplo siguiente se habilita CORS para cada método excepto PutItem .
[EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
public class ItemsController : ApiController
{
public HttpResponseMessage GetAll() { ... }
public HttpResponseMessage GetItem(int id) { ... }
public HttpResponseMessage Post() { ... }
[DisableCors]
public HttpResponseMessage PutItem(int id) { ... }
}
Globalmente
Para habilitar CORS para todos los controladores de API Web de la aplicación, pase una instancia de EnableCorsAttribute al método EnableCors :
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var cors = new EnableCorsAttribute("www.example.com", "*", "*");
config.EnableCors(cors);
// ...
}
}
Si establece el atributo en más de un ámbito, el orden de prioridad es:
- Acción
- Controller
- Global
Establecer los orígenes permitidos
El parámetro Origins del atributo [EnableCors] especifica qué orígenes tienen permiso para acceder al recurso. El valor es una lista separada por comas de los orígenes permitidos.
[EnableCors(origins: "http://www.contoso.com,http://www.example.com",
headers: "*", methods: "*")]
También puede usar el valor de carácter comodín " * " para permitir solicitudes de cualquier origen.
Considere detenidamente antes de permitir solicitudes de cualquier origen. Esto significa que, literalmente, cualquier sitio web puede realizar llamadas AJAX a la API Web.
// Allow CORS for all origins. (Caution!)
[EnableCors(origins: "*", headers: "*", methods: "*")]
Establecer los métodos HTTP permitidos
El parámetro Methods del atributo [EnableCors] especifica qué métodos http pueden tener acceso al recurso. Para permitir todos los métodos, use el valor de carácter comodín " * ". En el ejemplo siguiente solo se permiten las solicitudes GET y POST.
[EnableCors(origins: "http://www.example.com", headers: "*", methods: "get,post")]
public class TestController : ApiController
{
public HttpResponseMessage Get() { ... }
public HttpResponseMessage Post() { ... }
public HttpResponseMessage Put() { ... }
}
Establecer los encabezados de solicitud permitidos
En este artículo se ha descrito anteriormente cómo una solicitud preparatoria podría incluir un encabezado Access-Control-request-headers, donde se enumeran los encabezados HTTP establecidos por la aplicación (denominados "encabezados de solicitud de autor"). El parámetro headers del atributo [EnableCors] especifica qué encabezados de solicitud de autor se permiten. Para permitir cualquier encabezado, establezca los encabezados en " * ". Para permitir encabezados específicos, establezca los encabezados en una lista separada por comas de los encabezados permitidos:
[EnableCors(origins: "http://example.com",
headers: "accept,content-type,origin,x-my-header", methods: "*")]
Sin embargo, los exploradores no son totalmente coherentes en cómo establecen los encabezados Access-Control-request-. Por ejemplo, Chrome actualmente incluye "Origin". FireFox no incluye encabezados estándar, como "Accept", incluso cuando la aplicación los establece en un script.
Si establece encabezados en un valor distinto de " * ", debe incluir al menos "Accept", "Content-Type" y "Origin", además de los encabezados personalizados que desee admitir.
Establecer los encabezados de respuesta permitidos
De forma predeterminada, el explorador no expone todos los encabezados de respuesta a la aplicación. Los encabezados de respuesta que están disponibles de forma predeterminada son:
- Cache-Control
- Content-Language
- Content-Type
- Expira
- Last-Modified
- Omiti
La especificación CORS llama a estos encabezados de respuesta sencillos. Para que otros encabezados estén disponibles para la aplicación, establezca el parámetro exposedHeaders de [EnableCors].
En el ejemplo siguiente, el método del controlador Get establece un encabezado personalizado denominado ' X-custom-header '. De forma predeterminada, el explorador no expondrá este encabezado en una solicitud entre orígenes. Para que el encabezado esté disponible, incluya "X-custom-header" en exposedHeaders.
[EnableCors(origins: "*", headers: "*", methods: "*", exposedHeaders: "X-Custom-Header")]
public class TestController : ApiController
{
public HttpResponseMessage Get()
{
var resp = new HttpResponseMessage()
{
Content = new StringContent("GET: Test message")
};
resp.Headers.Add("X-Custom-Header", "hello");
return resp;
}
}
Pasar credenciales en solicitudes entre orígenes
Las credenciales requieren un tratamiento especial en una solicitud de CORS. De forma predeterminada, el explorador no envía ninguna credencial con una solicitud entre orígenes. Las credenciales incluyen cookies y esquemas de autenticación HTTP. Para enviar credenciales con una solicitud entre orígenes, el cliente debe establecer XMLHttpRequest. withCredentials en true.
Usar XMLHttpRequest directamente:
var xhr = new XMLHttpRequest();
xhr.open('get', 'http://www.example.com/api/test');
xhr.withCredentials = true;
En jQuery:
$.ajax({
type: 'get',
url: 'http://www.example.com/api/test',
xhrFields: {
withCredentials: true
}
Además, el servidor debe permitir las credenciales. Para permitir las credenciales entre orígenes en Web API, establezca la propiedad SupportsCredentials en true en el atributo [EnableCors] :
[EnableCors(origins: "http://myclient.azurewebsites.net", headers: "*",
methods: "*", SupportsCredentials = true)]
Si esta propiedad es true, la respuesta HTTP incluirá un encabezado Access-Control-Allow-Credentials. Este encabezado indica al explorador que el servidor permite credenciales para una solicitud entre orígenes.
Si el explorador envía credenciales, pero la respuesta no incluye un encabezado Access-Control-Allow-Credentials válido, el explorador no expondrá la respuesta a la aplicación y se producirá un error en la solicitud de AJAX.
Tenga cuidado al establecer SupportsCredentials en true, ya que significa que un sitio web en otro dominio puede enviar las credenciales del usuario que ha iniciado sesión a la API Web en nombre del usuario, sin que el usuario sea consciente. La especificación CORS también indica que establecer orígenes en " * " no es válido si SupportsCredentials es true.
Proveedores de directivas de CORS personalizados
El atributo [EnableCors] implementa la interfaz ICorsPolicyProvider . Puede proporcionar su propia implementación creando una clase que derive de Attribute e implemente ICorsPolicyProvider.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class MyCorsPolicyAttribute : Attribute, ICorsPolicyProvider
{
private CorsPolicy _policy;
public MyCorsPolicyAttribute()
{
// Create a CORS policy.
_policy = new CorsPolicy
{
AllowAnyMethod = true,
AllowAnyHeader = true
};
// Add allowed origins.
_policy.Origins.Add("http://myclient.azurewebsites.net");
_policy.Origins.Add("http://www.contoso.com");
}
public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request)
{
return Task.FromResult(_policy);
}
}
Ahora puede aplicar el atributo a cualquier lugar que colocaría [EnableCors].
[MyCorsPolicy]
public class TestController : ApiController
{
.. //
Por ejemplo, un proveedor de directivas de CORS personalizado podría leer la configuración de un archivo de configuración.
Como alternativa al uso de atributos, puede registrar un objeto ICorsPolicyProviderFactory que cree objetos ICorsPolicyProvider .
public class CorsPolicyFactory : ICorsPolicyProviderFactory
{
ICorsPolicyProvider _provider = new MyCorsPolicyProvider();
public ICorsPolicyProvider GetCorsPolicyProvider(HttpRequestMessage request)
{
return _provider;
}
}
Para establecer ICorsPolicyProviderFactory, llame al método de extensión SetCorsPolicyProviderFactory en el inicio, como se indica a continuación:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.SetCorsPolicyProviderFactory(new CorsPolicyFactory());
config.EnableCors();
// ...
}
}
Compatibilidad con exploradores
El paquete CORS de la API Web es una tecnología del lado servidor. El explorador del usuario también debe admitir CORS. Afortunadamente, las versiones actuales de todos los exploradores principales incluyen la compatibilidad con CORS.