ASP.NET MVC6: Negociación de contenido

Nota: Este post ha sido realizado con versiones previas de ASP.NET 5 y de Visual Studio 2015 (CTP6), lo aquí escrito puede variar con versiones  finales de la plataforma

Una de las novedades más interesantes de ASP.NET5 es la unificación de ASP.NET MVC y WebApi en un mismo framework llamado MVC6. Esto significa que un mismo controlador puede «actuar a lo MVC» y devolver vistas o bien «actuar a lo WebApi» y devolver datos. Se eliminan clases redundantes que estaban duplicadas y se simplifica el desarrollo.

En este post vamos a ver como funciona la negociación de contenido, es decir cuando se devuelven datos (no vistas) como se determina si el formato de dichos datos debe ser XML, JSON o cualquier otro.

Regla 1: Cabecera Accept

Lo primero es saber que MVC6 usa la cabecera HTTP Accept para inferir el formato del mismo modo que lo hacía WebApi anteriormente. Así el siguiente código en un controlador MVC6 devolverá JSON o XML dependiendo del valor de la cabecera Accept:

[snippet id=»217″]

Vale, antes de que corras a probar el código, tal y como está configurada por defecto MVC6 vas a recibir siempre JSON con independencia del valor de la cabecera Accept. ¿Pero… no he dicho que había negociación de contenido? Sí, y la hay, pero para ello hay que añadir a MVC6 los formateadores de salida (que son las clases que se encargan de serializar los datos al formato indicado).  Por defecto solo hay registrado un formateador de salida para JSON y es por ello que siempre recibirás los datos en dicho formato. Para añadir el formateador de XML puedes usar el código siguiente (en el método ConfigureServices de la clase Startup):

[snippet id=»218″]

En MVC6 se ha solucionado el problema de Chrome. Este problema consiste en que mucha gente usa el navegador para probar sus APIs (a pesar de existir mejores herramientas tales como cURL, Fiddler o Postman). Chrome por defecto envía la cabecera Accept con el valor «text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8″. Literalmente Chrome está pidiendo los datos en formato HTML o XHTML si es posible, si no en XML (la parte en negrita), en formato WebP (para imágenes) y si no es posible ninguno de esos formatos, cualquier otro le vale. WebApi haciendo caso de esa cabecera Accept servía adecuadamente XML a Chrome. Por otro lado IE envía una cabecera Accept con el valor «text/html, application/xhtml+xml, */*». Es decir no pide explícitamente XML ni JSON y WebApi entonces enviaba los datos en formato JSON (el preferido por WebApi). Quiero recalcar que el comportamiento de WebApi era acorde al estándard HTTP pero suponía un problema para mucha gente que probaba su API en un navegador y veía datos en XML en lugar de JSON. Pues bien, MVC6 sigue haciendo caso a la cabecera Accept pero si detecta que el cliente informa que acepta cualquier formato (*/*) aunque sea en última instancia, MVC6 envía los datos en JSON incluso aunque XML fuese preferido por la cabecera Accept. Eso es porque, generalmente, los únicos que indican en Accept que aceptan cualquier formato son los navegadores y de esa forma vemos nuestros datos en JSON en cualquier navegador.

Regla 2: Especificando formato directamente

Vale, supongamos que quieres enviar JSON directamente, con independencia de la cabecera Accept. Para ello basta con usar el método Json de la clase Controller para devolver datos en formato JSON con independencia del valor de Accept. Es el mismo comportamiento que teníamos en MVC5:

[snippet id=»219″]

Otra manera de conseguir el mismo efecto es seguir usando ObjectResult pero indicar el formato de salida decorando la acción con el atributo Produces:

[snippet id=»220″]

Regla 3: Null es HTTP 204

En WebApi si un controlador devolvía null el cliente recibía como respuesta un código HTTP 200 (Petición procesada correctamente) y el en cuerpo de la respuesta la serialización de este null. Sí, es posible que el valor null tenga una serialización determinada (dependiendo del formateador y de la firma del método de acción). P. ej. el siguiente método de acción en WebApi:

[snippet id=»221″]

Devuelve la siguiente respuesta en el caso de usar XML:

[snippet id=»222″]

En MVC6 por su parte si se devuelve un valor null el código de respuesta usado es HTTP 204 (No content). Este código de respuesta HTTP sirve precisamente para indicar que el servidor ha procesado la petición correctamente pero que no devuelve ningún dato.  El responsable de que se envíe un  204 cuando se devuelven valores nulos en MVC6 es un formateador específico (HttpNoContentOutputFormatter) que por defecto está activado. Además de devolver un 204 el tipo de respuesta se pasa a text/plain (aunque no tiene mucha importancia puesto que el 204 ya indica que no hay valor a leer, así que el formato en que esté el valor da igual).

Regla 4: text/plain para cadenas

En WebApi si se devolvía una cadena (string) esta se serializaba a JSON o a XML y se devolvía con una respuesta con el content-type adecuado. Así si un controlador devuelve la cadena Hola en formato JSON se recibía una respuesta con content-type application/json y en el cuerpo de la cabecera los datos «Hola» (comillas incluídas). Si se devolvía en formato XML el cuerpo de la cabecera contenía los datos:

[snippet id=»223″]

En MVC6 esto ha cambiado y se ha simplificado. Ahora cuando un controlador devuelve una cadena la respuesta usa el content-type text/plain y en el cuerpo de la respuesta está la cadena, sin más.

Esto puede parecer que rompe la negociación de contenido (¿si he pedido datos text/xml porque me los devuelves en text/plain?) pero pensémoslo desde el punto de vista del controlador: efectivamente se piden datos en XML pero si el controlador devuelve una cadena probablemente es porque no tenga otro tipo de datos mejor (en otro caso usaría un objeto que se podría serializar a XML). Así que si envía una cadena, es más conveniente informar al cliente que lo que recibe es precisamente eso, una cadena. Y la mejor forma de hacerlo es usando el content-type text/plain.

Y poca cosa más a añadir a la negociación de contenido en MVC6. En un futuro post hablaremos más a fondo de los formateadores que juegan un rol importante en este punto.

Saludos!

Deja un comentario

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