Content negotiation en MVC 6 (o cómo puedo simplemente escribir JSON).

Este artículo provee una manera simplificada de cómo hacer que las cosas funcionen y cómo cambiar el comportamiento. No es una explicación profunda de la negociación de contenido (content negotiation).

Desde la versión 1 del Web API el código del controlador puede devolver un objeto de un tipo arbitrario y el “framework” lo enviará como JSON o XML al cliente. El proceso de escoger el formato de salida es llamado “content negotiation” o negociación de contenido en español. Las reglas básicas pueden ser descritas simplemente como:

  1. El “framework” intentará devolver el formato que el cliente pida utilizando la cabecera “Accept” (Accept Headers).
  2. Si no se pide un formato específico o no se puede inferir uno, el formato por defecto es JSON.
  3. Si el formato que el cliente pidió no está disponible el “framework” devolverá el formato por defecto JSON (Ejemplo: Accept Header era application/formatonoexiste)

MVC 6 combinó Web API y MVC en un sólo “framework”. Aunque la negociación de contenido fue renovada, las reglas básicas no cambiaron. Han algunas mejoras al API, el código ha sido refactorizado y hay unas cuantas mejoras al comportamiento para casos extremos que han sido agregados.

Una pregunta común sobre “content negotiation” es: ¿por qué mi API devuelve XML por defecto. 9 de 10 veces la razón es que el desarrollador está utilizando el navegador para hacer pruebas con la API, usando Chrome o FireFox. El navegador Chrome está pidiendo XML en el “accept header” y el servidor sigue las reglas descritas anteriormente.

Aquí hay una simple petición GET (GET request) de Chrome a un localhost:

GET / HTTP/1.1
Host: localhost:5001
Proxy-Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.21.71.65 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,he;q=0.6

Noten que el accept header pide diferentes formatos incluyendo application/xml, pero no pide application/json. Por eso el servidor debe devolver el único formato que puede encajar el cual es XML.

En contraste si hacemos la misma petición con Internet Explorer, éste simplemente no pide XML:

GET / HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: en-US,en;q=0.7,he;q=0.3
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko
Accept-Encoding: gzip, deflate
Host: localhost:5001
DNT: 1
Proxy-Connection: Keep-Alive

Desde que el navegador no pidió ningún formato que el “framework” reconozca, devuelve la regla por defecto y devuelve datos en JSON.

Nota: Los factores q son asignados a “accept headers” específicos, esto es algo que la negociación de contenido soporta. Este artículo no cubre estos escenarios.

¿Qué hacer al respecto?

Hay unas cuantas maneras de ver esto, la más simple es utilizar la herramienta correcta para el trabajo correcto. El trabajo del navegador es interpretar HTML, ¿por qué utilizarlo para probar tus APIs? Hay herramientas que son mejores para eso, como Fiddler, las herramientas de desarrollo del navegador, o probar tu llamada javascript al API en vez de tratar de usarla directamente en el navegador.

Para mi esto es donde me detengo, la API está comportándose correctamente y soportará cualquier cliente que siga la especificación HTTP.

Gracias por su consejo pero yo igual aún quiero que devuelva sólo JSON.

Muchos desarrolladores sólo les importa devolver JSON, mientras otros todavía quieren poder negociar el contenido para algunas acciones.

Aquí hay algunas maneras de implementar este comportamiento.

Devolver un resultado JSON explícitamente.

  • Pros – El código es simple de leer y no hay magia involucrada. Es fácil de probar y puedes mezclarlo con otros resultados de acción.
  • Contras – El código es explícito, y no hay noción de lo que va ha ser devuelto por una introspección al API tales como “ApiDescriptionProvider”. ApiDescriptionProvider es el reemplazo del ApiExplorer.
public IActionResult GetMeData()
{
   var data = GetDataFromSource();
   if (data == null)
   {
      return HttpNotFound();
   }
   return Json(data);
}

Quitar el formateador de salida de XML del sistema.

  • Pros – Un simple cambio en opciones.
  • Contras – Esto es una manera global que quita XML de la negociación de contenido. Si uno de los clientes requiere XML ya no está disponible por defecto.

Nota: En MVC 6 XmlOutputSerializer fue separado en 2 serializadores distintos. El indicado abajo está registrado por defecto.

services.Configure<MvcOptions>(options =>
   options
   .OuputFormatters
   .RemoveAll(
      formatter => formatter.Instance is XmlDataContractSerializerOutputFormatter)
);

Utilizar el atributo [Produces(“application/json”)] – Nuevo en MVC 6.

  • Pros – Mantiene simple el código en la acción, puede ser aplicado localmente o globalmente de varias maneras.
  • Contras – No se puede mezclar con Produces.

Aplicando el atributo a una acción:

[Produces("application/json")]
public List<Data> GetMeData()
{
   return GetDataFromSource();
}

Agregando el filtro globalmente en startup.cs.

services.Configure<MvcOptions>(options =>
   options.Filters.Addd(new ProducesAttribute("application/json"))
);

Aplicándolo a su clase base.

[Produces("application/json")]
public class JsonController: Controller {}
public class HomeController: JsonController
{
   public List<Data> GetMeData()
   {
      return GetDataFromSource();
   }
}

¿Qué más ha cambiado en negociación de contenido en MVC 6?

1. Si el controlador devuelve una cadena (sin importar el tipo de dato declarado), te devolverá un tipo de contenido text/plain.

public object GetData()
{
   return "Los datos";
}

public string GetString()
{
   return "Los Datos";
}

2. Si el controlador devuelve “null” o el tipo de retorno es “void”, espera el código de estado 204 (No hay contenido “NoContent”) en vez de 200. Y el cuerpo (body) estará vacío.

public Task DoSomethingAsync()
{
   // Hace algo
}

public void DoSomething()
{
  // Hace algo
}

public string GetString()
{
   return null;
}

public List<Data> GetData()
{
   return null;
}

Ambos comportamientos de arriba son controlados por el HttpNoContentOutputFormatter y el TextPlainFormatter. Ambos comportamientos pueden ser anulados quitando los formateadores de la colección Options.OutputFormatter, similar a la forma como el formateador XML fue quitado en el ejemplo de arriba.

.NET Web Development and Tools Blog – Yishai G_

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s