[WEB API RC] Negociación de contenido

Por defecto WEB API solo nos permite responder a peticiones en formato json y xml, pero puede que en ocasiones necesitemos responder a una petición que nos solicita por ejemplo… imágenes.

En este post mostrare un par de posibles técnicas a implementar para responder a estas solicitudes.

Dada la clase Persona:

Y dado el controlador PersonaController:

    public class PersonaController : ApiController
    {
        private readonly List<Persona> _personas = Persona.Todos();

        public List<Persona> Get()
        {
            return _personas;
        }
        public HttpResponseMessage Get(int id)
        {
            var persona = _personas.FirstOrDefault(x => x.Id == id);
            if (persona == null)
            {
                var mensaje404 = new HttpResponseMessage(HttpStatusCode.NotFound);
                return mensaje404;
            }
            var personaMensaje = Request.CreateResponse(HttpStatusCode.OK,persona);
            return personaMensaje;
        }
    }

Al hacer una petición al servicio, este nos retornara el json o xml como estamos acostumbrados, incluso si especificamos en la cabecera de la solicitud que “queremos” una imagen:

Pero… ¿Como solucionamos esto?

  • Creando un controlador de imágenes:

La primera solución que pasa por la cabeza es simplemente crear un nuevo controlador encargado de retornarnos la fotografía de la persona dado su Id, sería algo como lo siguiente:

    public class FotoPersonaController : ApiController
    {
        private readonly List<Persona> _personas = Persona.Todos();

        public HttpResponseMessage Get(int id)
        {
            var persona = _personas.FirstOrDefault(p => p.Id == id);
            var message = new HttpResponseMessage();
            message.Content = new StreamContent(new MemoryStream(persona.Foto));
            message.Content.Headers.ContentType = new MediaTypeHeaderValue("image/jpg");
            return message;
        }
    }

Y a simple vista sí que funciona:

Pero aquí no hay negociación de contenido, siempre vamos a retornar una imagen a esta petición, sin importar que se quiera en la respuesta siempre le vamos a retornar una imagen. Además de esto, debemos escribir un método para cada una de las entidades que trabajen con este tipo de contenido… y eso ya es trivial.

  • Media Type Formatters al rescate.

Para crear un media formatter solo debemos implementar de una de estas clases: MediaTypeFormatter o BufferedMediaTypeFormatter, la diferencia es que la ultima implementa de MediaTypeFormatter y nos abstrae de un par de métodos asíncronos.

Creamos entonces una clase llamada JpegFormatter donde heredamos de la clase BufferedMediaTypeFormatter y seguimos los siguientes pasos:

  1. Definimos que tipos de contenido vamos a trabajar con este (en su constructor).
            public JpegFormatter()
            {
                SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpeg"));
            }
  2. Sobrescribimos los métodos booleanos CanReadType y CanWriteType, el primero indica que tipos podemos deserializar (en este ejemplo no aplica, así que siempre retorna false) y el segundo nos indica que tipos podemos serializar.
            public override bool CanReadType(Type type)
            {
                return false;
            }
    
            public override bool CanWriteType(Type type)
            {
                return (type == typeof (Persona));
            }
  3. Finalmente sobrescribimos el método WriteToStream()para que dada la solicitud con dicho encabezado nos retorne lo que necesitamos, una imagen.
            public override void WriteToStream(Type type, object value, System.IO.Stream stream,
                                               HttpContentHeaders contentHeaders)
            {
                var persona = value as Persona;
                if (persona != null)
                {
                    stream.Write(persona.Foto, 0, persona.Foto.Length);
                    stream.Flush();
                }
            }

El codigo completo de nuestra clase es el siguiente:

using System;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using MvcApplication2.Models;

namespace MvcApplication.MediaTypesFotmatters
{
    public class JpegFormatter : BufferedMediaTypeFormatter
    {
        public JpegFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpeg"));
        }

        public override bool CanReadType(Type type)
        {
            return false;
        }

        public override bool CanWriteType(Type type)
        {
            return (type == typeof (Persona));
        }

        public override void WriteToStream(Type type, object value, System.IO.Stream stream,
                                           HttpContentHeaders contentHeaders)
        {
            var persona = value as Persona;
            if (persona != null)
            {
                stream.Write(persona.Foto, 0, persona.Foto.Length);
                stream.Flush();
            }
        }
    }
}

Como paso final, registramos nuestro Type Formatter, si estamos usamos hosting asp.net lo registramos en el global.asax en el metodo Application_Start, así:

        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            //REGISTRAR TYPE FORMATTER 
            GlobalConfiguration.Configuration.Formatters.Add(new JpegFormatter());
        }

Con esto ya no necesitamos ese controlador adicional pues podemos responder a este tipo de contenido desde nuestro PersonaController.

La solicitud aceptando JSON:

Solicitud aceptando Image/jpeg:

Como vemos podemos extender de una forma muy fácil la negociación de contenido de WEB API haciendo uso de todo el poder de http.

Hasta el próximo post.

Anuncios
[WEB API RC] Negociación de contenido

5 comentarios en “[WEB API RC] Negociación de contenido

  1. d-aaron88@gmail.com dijo:

    Muy buena la explicacion, pero 1) no seria mejor dar la URL de la imagen que es un string en vez de hacerlo de esta manera como dice el popular refran: una imagen vale mas que mil palabras, pero pesa mil veces mas. 2) parece que funciona bien para el caso de imagenes de poco tamaño, que sucede si deseo por ejemplo transfir una imagen de mas de 1MB o mejor aun transferir cualquier tipo de fichero de cualquier tamaño, que pasa con mi server tendra que hacer un buffer del tamaño del fichero en cuestion o es posible usar streamming con web api. y 3) es posible usar informacion binaria para enviar los ficheros o ya es binario usando web api. Por ultimo veo que que puedes tener acceso a los metodos de manera muy sencilla usando cualquier herramienta como fiddler, que puedo hacer para protegerme de ataques XSS y que mis metodos requieran autenticacion pues supongo que seria mala idea implementar un CRUD si no es posible protegerse como se debe.

    Por cierto podrias hacer un ejemplo practico y no simplemente mostrar como se usan las cosas creo que eso ya esta en la msdn.

    1. Hola 🙂

      Antes que nada, gracias por comentar 😉 Ahora veamos si puedo responderte.

      1- Imagino que te refieres a que ante la peticion responder con el location del recurso. Si!! es una excelente opción, de hecho es así como se suele trabajar, solo almacenando Paths y retornando Locations. Solo que no se me ocurrio otro ejemplo para mostrar lo que de fondo queria mostrar.

      2- Hacer Streaming con WEB API de poder se puede… lo que no se, es que tan REST seria este comportamiento, A primera vista delegaria la capacidad de streaming a otro servicio. O con http 1.1 tratar de usar algun tipo de conexión persistente.

      3- Esta no te la entendí. Osea, pordefecto es Binario, pero no entiendo bien a que te refieres.

      4- El tema de seguridad si que es bello 😉 … desde el Autorize, todo el modelo que maneja http, el poder de los SSL, el OAuth, modelos de autenticación federada… en fin!! Eso si que da para varios articulos 🙂

      Y a tu comentario final… la verdad que no eh encontrado mucha documentacion en MSDN, menos ejemplos!! y es que apenas salieron de un Beta!! llegamos a hasta hace poco al RC… Así que si es dificil verlo en MSDN, no te imaginas lo que es encontrar algo en Español 🙂

      Saludos.

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