Explorando ASP.NET MVC4 WebAPI–3: Formatos de salida

Bueno… seguimos esta serie explorando ASP.NET WebAPI. En este post vamos a hablar de los formatos de salida. Como ya hemos dicho, de serie ASP.NET WebAPI tiene soporte para XML y para JSON. Pero… como decide el framework si enviar la salida en XML o en JSON?

La cabecera accept

Una de las cabeceras que define HTTP es la cabecera accept. Esta cabecera se usa para que el cliente informe al servidor de los tipos de datos (content type) que acepta. De nuevo un par de pruebas con fiddler nos permiten verlo fácilmente. Este va a ser nuestro controlador:

  1. public class ValuesController : ApiController
  2. {
  3.     public IEnumerable<int> GetAll()
  4.     {
  5.         return Enumerable.Range(1, 30);
  6.     }
  7. }

Y ahora de nuevo usamos fiddler para crear y ver una petición GET a /api/values, como la siguiente:

GET http://worldoffighters.epnuke2.com:55603/api/values HTTP/1.1
User-Agent: Fiddler
Host: worldoffighters.epnuke2.com:55603

La respuesta recibida es:

HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Sun, 19 Feb 2012 20:08:20 GMT
X-AspNet-Version: 4.0.30319
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Content-Type: application/json; charset=utf-8
Connection: Close
Content-Length: 82

[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30]

Bueno, parece pues claro que ante cualquier ausencia de accept, la salida se envía en JSON (content-type: application/json). Añadimos ahora una cabecera accept:

image

Y generar una petición como la que sigue:

GET http://worldoffighters.epnuke2.com:55603/api/values HTTP/1.1
User-Agent: Fiddler
Host: worldoffighters.epnuke2.com:55603
accept: text/xml

Y esta es la respuesta que recibimos ahora:

HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Sun, 19 Feb 2012 20:11:55 GMT
X-AspNet-Version: 4.0.30319
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Content-Type: text/xml; charset=utf-8
Connection: Close
Content-Length: 543

<?xml version="1.0" encoding="utf-8"?><ArrayOfInt xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><int>1</int><int>2</int><int>3</int><int>4</int><int>5</int><int>6</int><int>7</int><int>8</int><int>9</int><int>10</int><int>11</int><int>12</int><int>13</int><int>14</int><int>15</int><int>16</int><int>17</int><int>18</int><int>19</int><int>20</int><int>21</int><int>22</int><int>23</int><int>24</int><int>25</int><int>26</int><int>27</int><int>28</int><int>29</int><int>30</int></ArrayOfInt>

Bueno, hemos visto el mecanismo que usa el framework para determinar el formato de salida: la cabecera accept (no es nada nuevo, es el standard de HTTP y de hecho ya hablé hace tiempo de como aplicarlo en ASP.NET MVC: http://geeks.ms/blogs/etomas/archive/2010/09/10/asp-net-mvc-formato-de-salida-seg-250-n-content-type.aspx).

Bueno, vamos a ver ahora como crear un tipo de salida nuevo y tenerlo vinculado a un content-type determinado.

Usando MediaTypeFormatter

Para añadir un formato de salida nuevo debemos crear una clase que derive de MediaTypeFormatter:

  1. public class MediaBinaryFormatter : MediaTypeFormatter
  2. {
  3.     public MediaBinaryFormatter()
  4.     {
  5.         SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/octet-stream"));
  6.     }
  7.  
  8.     protected override bool CanWriteType(Type type)
  9.     {
  10.         return true;
  11.     }
  12.  
  13.     protected override Task OnWriteToStreamAsync(Type type, object value, System.IO.Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext, System.Net.TransportContext transportContext)
  14.     {
  15.         return Task.Factory.StartNew(() =>
  16.                                          {
  17.                                              var formatter = new BinaryFormatter();
  18.                                              formatter.Serialize(stream, value);
  19.                                          });
  20.     }   
  21. }

Hay tres aspectos interesantes a recalcar:

  1. En el constructor de la clase es donde vinculamos este serializador a un content-type específico, en este caso a application/octet-stream
  2. Indicamos que podemos serializar cualquier tipo .NET. Esto es porque siempre devolvemos true en el método CanWriteType. Pero podríamos devolver true solo para determinados tipos, lo que permite tener serializados personalizados para ciertos tipos de datos 😉
  3. Finalmente en el método OnWriteToStreamAsync es donde realizamos la serialización y escritura en el stream de salida. Fijaos que debemos devolver un objeto de la clase Task con el código a ejecutar, ya que ese método será llamado de forma asíncrona por el framework (aunque a nosotros no nos preocupe demasiado). En este caso el código lo que hace es usar el BinaryFormatter de .NET para enviar la serialización en bnario del objeto que reciba. Por supuesto esto es a modo de demostración, ya que es lo más anti-internet que existe: este formato de deserialización es propio de .NET por lo que tan solo un cliente .NET puede entenderlo.

Y listos! Con esto casi hemos terminado… Nos falta simplemente registrar este MediaTypeFormatter en el framework. Y siguiendo la filosofía clásica de ASP.NET MVC la configuración está en una clase estática que podemos inicializar fácilmente desde gloabal.asax. En nuestro caso basta con añadir (p.ej. en el Application_Start) la línea:

  1. GlobalConfiguration.Configuration.Formatters.Add(new MediaBinaryFormatter());

Y ahora sí que hemos terminado. Si ejecutamos una petición con fiddler poniendo application/octet-stream en la cabecera accept, esta es la respuesta:

image

Como se puede observar es una serialización binaria de .NET!

En resumen hemos visto como a través del header HTTP accept, el cliente puede especificar que formato de respuesta desea y como podemos añadir MediaTypeFormatters propios para dar soporte a otros tipos de datos que no sean JSON o XML.

Un saludo a todos!

Un comentario sobre “Explorando ASP.NET MVC4 WebAPI–3: Formatos de salida”

Deja un comentario

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