¡Hola a todos! Bienvenidos a este Post donde aprenderemos qué es el CORS, cómo funciona y cómo solucionar un problema bastante común con peticiones AJAX con una WebApi. Empecemos.

¿Qué es CORS?

CORS (Cross Origin Resource Sharing, o bien en español Intercambio de Recursos de Origen Cruzado) es un mecanismo en el que permite solicitar recursos restringidos desde una página web de un dominio a otro recurso web de otro dominio. De esta manera, CORS define una manera en la que el navegador y el servidor puedan interactuar para determinar si la petición de origen cruzado es segura.

¿Cómo funciona?

CORS trabaja añadiendo cabeceras HTTP que permite a los servidores describir un conjunto de orígenes (dominios) que tienen permisos para obtener una información usando el navegador. Adicionalmente la especificación de estos, sugiere que los navegadores verifiquen la solicitud solicitando métodos soportados desde el servidor con un método de solicitud HTTP OPTIONS, luego con la aprobación del servidor se envía la verdadera solicitud con el método HTTP.

Ahora nos enfrentaremos al siguiente error:

errorCORS

Como podéis observar, no tenemos acceso a la WebApi porque la política de CORS ha bloqueado nuestro origen al no tener cabeceras en la petición. Entonces…

¿Cómo solucionamos este problema?

Para ver mejor este escenario, tendremos 2 tipos de proyectos: WebApi y MVC.

Proyecto WebApi

Comenzamos creando un nuevo proyecto Web ASP.NET WebApplication (.NET Framework).

webAPI1

Seleccionamos la opción Empty y más abajo marcamos la casilla Web API.

webAPI2

Una vez creado el proyecto, nos dirigimos a la carpeta Models y creamos una clase llamada Pelicula (por ejemplo) con sus variables y sus constructores.

 public class Pelicula
    {
        public int IdPelicula { get; set; }
        public String Titulo { get; set; }
        public String Descripcion { get; set; }
        public DateTime FechaEstreno { get; set; }
        public String Url { get; set; }

        public Pelicula() { }

        public Pelicula(int id, String titulo, String desc, DateTime fecha, String url)
        {
            this.IdPelicula = id;
            this.Titulo = titulo;
            this.Descripcion = desc;
            this.FechaEstreno = fecha;
            this.Url = url;
        }
    }

Ya terminada la clase Pelicula, creamos un controlador en la carpeta Controllers (click derecho sobre la  carpeta controllers, Add, Controllers). Elegimos la opción Web API 2 Controller – Empty y lo llamaremos PeliculasController. Este controller tendrá un objeto List de Pelicula en el que le insertaremos películas, un constructor y 2 métodos GET: uno para obtener la listas de películas y otro para obtener sola película.

Creamos el objeto List para tener una lista de películas:

public List<Pelicula> listaPeliculas = new List<Pelicula>();

Creamos el constructor y en él añadiremos películas en la lista de películas:

        public PeliculasController()
        {
            Pelicula pelicula = new Pelicula(1, "Origen", "Dom Cobb es un ladrón con una extraña habilidad para entrar a los sueños de la gente y robarles los secretos de sus subconscientes. Su habilidad lo ha vuelto muy popular en el mundo del espionaje corporativo, pero ha tenido un gran costo en la gente que ama. Cobb obtiene la oportunidad de redimirse cuando recibe una tarea imposible: plantar una idea en la mente de una persona. Si tiene éxito, será el crimen perfecto, pero un enemigo se anticipa a sus movimientos.", new DateTime(2010, 8, 6), "https://is1-ssl.mzstatic.com/image/thumb/Video4/v4/86/ac/14/86ac14f2-8c9b-2a5a-80be-49b8ee402228/pr_source.lsr/268x0w.png");
            this.listaPeliculas.Add(pelicula);
            pelicula = new Pelicula(2, "El caballero oscuro", "Batman tiene que mantener el equilibrio entre el heroísmo y el vigilantismo para pelear contra un vil criminal conocido como el Joker, que pretende sumir Gotham City en la anarquía", new DateTime(2008, 8, 13), "https://images-na.ssl-images-amazon.com/images/I/41BorWGJegL._SX302_BO1,204,203,200_.jpg");
            this.listaPeliculas.Add(pelicula);
            pelicula = new Pelicula(3, "Piratas del Caribe: La maldición del Perla Negra", "Un herrero y un extraño pirata se unen para rescatar a una dama secuestrada de un capitán de piratas y su tripulación.", new DateTime(2003, 8, 14), "http://viveloya.com/images/stories/virtuemart/product/maldición%20del%20perla%20negra.jpg");
            this.listaPeliculas.Add(pelicula);
            pelicula = new Pelicula(4, "Avengers: Inifinity War", "Los superhéroes se alían para vencer al poderoso Thanos, el peor enemigo al que se han enfrentado. Si Thanos logra reunir las seis gemas del infinito: poder, tiempo, alma, realidad, mente y espacio, nadie podrá detenerlo.", new DateTime(2018, 4, 27), "http://t1.gstatic.com/images?q=tbn:ANd9GcS2xHmABIm07FlykPsdTlijgtafbiTD5UuRCFAdtkYl6doxJDGi");
            this.listaPeliculas.Add(pelicula);
            pelicula = new Pelicula(5, "Spider-Man: Un nuevo universo", "Luego de ser mordido por una araña radioactiva, el joven Miles Morales desarrolla misteriosos poderes que lo transforman en el Hombre Araña. Ahora deberá usar sus nuevas habilidades ante el malvado Kingpin, un enorme demente que puede abrir portales hacia otros universos.", new DateTime(2018, 12, 21), "http://t0.gstatic.com/images?q=tbn:ANd9GcQyfxXHWbpUJOUSgnBv2AyF0uL1Br9P3b_Erf1uB_gG8yZHUiv-");
            this.listaPeliculas.Add(pelicula);
        }

Y por último los métodos GET:

        public List<Pelicula> GetPeliculas()
        {
            return this.listaPeliculas;
        }

        public Pelicula GetPelicula(int id)
        {
            return this.listaPeliculas.Find(x => x.IdPelicula == id);
        }

Una vez terminado el controlador, pulsamos F5 Y se ejecutará la solución.

Ya ejecutada la aplicación, nos saldrá una ventana con el error 403.14 – Forbiden, no hay que preocuparse por ello, lo ignoramos y escribimos en la url /api/Peliculas despues del localhost:[puerto].

localhost:56823/api/Peliculas

A continuación veremos esto:

webAPI3

Como podemos observar, nos devuelve la información de las películas que hemos añadido en la lista de las películas.

Ahora vamos cambia la información a un formato JSON para que nuestro proyecto MVC lo pueda leer correctamente.

Nos dirigimos al archivo Global.asax y añadimos la siguiente línea:

GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear();

El resultado sería este:

protected void Application_Start() {             GlobalConfiguration.Configure(WebApiConfig.Register);             GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear();         }

Una vez hecho esto, nos vamos al archivo PeliculasController, ejecutamos la solución y volvemos a escribir la Url.

webAPI4

Ahora ya tenemos preparada nuestra información para que nuestro proyecto MVC lo pueda leer.

Proyecto MVC

Empezamos creando un nuevo proyecto EMPTY y marcamos la casilla MVC.

webMVC1

A continuación vamos a la carpeta Controller y añadimos un controlador.

webMVC2

Elegimos la opción MVC 5 Controller – EMPTY  y lo llamamos HomeController.

Una vez creado nos abrirá automáticamente el controlador. Ponemos el cursor sobre Index(), le damos Click derecho y clicamos sobre la opción Add View…

webMVC3

Nos aparecerá la siguiente ventana:

webMVC4

Lo dejamos tal cual está y le damos al botón Add.

Con esto nos creará la vista en la que obtendremos la información de nuestro ProyectoApi.

En la vista añadimos una tabla para visualizar las películas que recibiremos de forma ordenada, se hace de la siguiente manera:

<table class="table table-bordered">
    <thead>
        <tr>
            <th>Titulo</th>
            <th>Sinopsis</th>
            <th>Fecha de estreno</th>
            <th>Imagen</th>
        </tr>
    </thead>
    <tbody id="informacion">

    </tbody>
</table>

En el cuerpo de la tabla, que es la etiqueta “<tbody>” le hemos añadido un id llamado información para indicar donde meteremos la información de las películas.

El siguiente paso es añadir un script en la sección scripts donde haremos la petición de la información de las películas.

Primero, dentro de la sección scripts añadimos la etiqueta “<script>”. Dentro, creamos una función llamada “cargarPeliculas”.

La función tiene el siguiente código:

function cargarPeliculas() {
        var url = "http://localhost:56823/api/Peliculas";
        $.getJSON(url, function (data) {
            var html = "";
            $.each(data, function (key, pelicula) {
                html += "<tr>";
                html += "<td>" + pelicula.Titulo + "</td>";
                html += "<td>" + pelicula.Descripcion + "</td>";
                html += "<td>" + pelicula.FechaEstreno + "</td>";
                html += "<td><img src='" + pelicula.Url + "' style='width: 75px; height:150px' /></td>";
                html += "</tr>";
            });
            $("#informacion").append(html);
        });
    }

Lo que hace esta función es obtener la información de nuestra API mediante la función JQuery “$.getJSON”. Una vez obtenida la información metemos la información, que es una cadena de texto, en una variable llamada html. Finalmente, insertamos el valor de la variable html en el div “informacion”.

Una vez hecho esto, vamos a invocar la función cuando el documento html esté preparado.

Se hace de la siguiente manera:

    $(document).ready(function () {
        cargarPeliculas();
    });

La sección quedaría de esta manera:

@section scripts {
    <script>
    $(document).ready(function () {
        cargarPeliculas();
    });

    function cargarPeliculas() {
        var url = "http://localhost:51374/api/Peliculas";
        $.getJSON(url, function (data) {
            var html = "";
            $.each(data, function (key, pelicula) {
                html += "<tr>";
                html += "<td>" + pelicula.Titulo + "</td>";
                html += "<td>" + pelicula.Descripcion + "</td>";
                html += "<td>" + pelicula.FechaEstreno + "</td>";
                html += "<td><img src='" + pelicula.Url + "' style='width: 75px; height:150px' /></td>";
                html += "</tr>";
            });
            $("#informacion").append(html);
        });
    }
    </script>
}

Lo siguiente sería ir a la carpeta Views>Shared>_Layout.cshtml, vamos a la parte de abajo del documento por la parte de las etiquetas»<script>» y añadimos este código depues de las 2 etiquetas de esta manera:

    <script src="~/Scripts/jquery-1.10.2.min.js"></script>
    <script src="~/Scripts/bootstrap.min.js"></script>
    @RenderSection("scripts", false)

Con esto, ya tendríamos preparado nuestro ProyectoMVC para recibir la información de nuestro ProyectoApi.

Para probar que funciona todo necesitamos tener nuestros dos proyectos abiertos, primero iniciamos el ProyectoApi y luego el ProyectoMVC.

¡Probemos!

Si ves que no aparece ninguna película y pulsando F12 y yendo al apartado de console te sale el siguiente error…

errorCORS

No te  preocupes, es que aún no hemos configurado nuestra API para que pueda aceptar las peticiones de nuestro ProyectoMVC.

Configuración del ProyectoApi

Primero, creamos una clase llamada AccessPolicyCors en la carpeta App_Start de nuestro ProyectoApi, para ello, pulsamos click derecho sobre la carpeta App_Start, vamos a Add y elegimos la última opción que es Class…

webMVC5

Antes de seguir con la clase tenemos que añadir los paquetes CORS. Para esto se hace con click derecho sobre nuestro proyecto y seleccionamos la opción Manage Nugget Package.

webAPI5

Vamos A Browse e instalamos estos 2 paquetes:

webAPI6

Una vez descargados los paquetes, vamos a nuestra clase y hacemos los siguientes using:

using System.Web.Http.Cors;
using System.Threading.Tasks;
using System.Web.Cors;
using System.Net.Http;
using System.Threading;

Con esto ya estamos listos para seguir.

Dentro de la clase que acabamos de crear vamos a hacer que herede de otras 2 clases: Attribute e ICorsPolicyProvider.

public class AccessPolicyCors: Attribute, ICorsPolicyProvider

Luego de haber heredado, crearemos un método privado y asíncrono llamado IsOriginFromCustomer. Tendrá e siguiente código:

private async Task<bool> IsOriginFromCustomer(string originRequested)
{
  return true;
}

Este método nos será útil para más adelante, su función es validar si hemos recibido una petición.

El siguiente método que crearemos se llamará GetCorsPolicyAsync, será un método público, asíncrono y tendrá como tarea el CorsPolicy. Este método es propio de la interfaz ICorsPolicyProvider.

public async Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)

Lo siguiente que haremos es recoger el contexto de la solicitud y el origen de la misma.

var corsRequestContext = request.GetCorsRequestContext();
var originRequested = corsRequestContext.Origin;

Ahora usaremos nuestro método privado que creamos anteriormente, lo usaremos dentro de una condición “if”.

if (await IsOriginFromCustomer(originRequested))
{

Dentro del “if” crearemos un nuevo objeto CorsPolicy donde le diremos que acepte cualquier cabecera y cualquier método.

var policy = new CorsPolicy
{
       AllowAnyHeader = true,
       AllowAnyMethod = true
};

Y a continuación añadimos el origen de nuestra solicitud al origen de nuestro objeto “policy”.

policy.Origins.Add(originRequested);

Finalmente devolvemos el objeto “policy” y cerramos nuestra condición “if”.

return policy;
}

Si no cumple la condición, devolveremos null.

return null;

NOTA:  si queremos una IP speceifica, debemos especificar el origen, por ejemplo:

policy.Origins.Add("http://localhost:56360");

Si echamos un vistazo general a la clase, quedaría de la siguiente manera:

 public async Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var corsRequestContext = request.GetCorsRequestContext();
            var originRequested = corsRequestContext.Origin;

            if (await IsOriginFromCustomer(originRequested))
            {
                var policy = new CorsPolicy
                {
                    AllowAnyHeader = true,
                    AllowAnyMethod = true
                };
                
                policy.Origins.Add(originRequested);

                //IP ESPECIFICA
                //policy.Origins.Add("http://localhost:56360");
                return policy;
            }
            return null;
        }

        private async Task<bool> IsOriginFromCustomer(string originRequested)
        {
            return true;
        }

Nos dirigimos al archivo WebApiConfig que está en la misma carpeta que nuestra clase y añadimos la siguiente línea:

config.EnableCors(new AccessPolicyCors());

Como podéis suponer, habilita el CORS a través de la clase que hemos creado.

Con esto ya tendríamos configurada nuestra API para recibir peticiones.

webMVC6

Y efectivamente, funciona correctamente nuestra API.

¡Espero que os haya servido de ayuda! ¡Un saludo a todos y a todas!

Autor/a: David Valencia Beltrán

Curso: Microsoft MCSA Web Applications + Microsoft MCSD App Builder + Xamarin

Centro: Tajamar

Año académico: 2018-2019

Código / recursos utilizados / Otros datos de interés: https://github.com/Davos95/CORSWebAPI

This Post Has One Comment

Leave a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.