WebApi–¿objeto o HttpResonseMessage?

 ·  ☕ 5 min  ·  ✍️ eiximenis

    Nota: Este post ha sido importado de mi blog de geeks.ms. Es posible que algo no se vea del todo "correctamente". En cualquier caso puedes acceder a la versión original aquí

    El otro día hablando con Carlos (aka @3lcarry) surgió el tema de como devolver datos en una WebApi. En WebApi tenemos hasta tres maneras generales de devolver unos datos desde una acción de un controlador. Devolver un objeto, devolver un HttpResponseMessage o devolver un IHttpActionResult. (Una discusión equivalente aparece en MVC6 donde tenemos dos alternativas, devolver un objeto o bien un IActionResult.) ¿Cuál es la mejor?

    Devolver un objeto

    En este caso nuestro controlador tiene un código como el siguiente:

    1. public IEnumerable<string> Get()
    2. {
    3.     return new string[] { “value1”, “value2” };
    4. }

    Cuando devolvemos un objeto, WebApi usa el serializador (formatter en la jerga de WebApi) correspondiente, serializa el objeto y lo envía de respuesta.

    La ventaja principal de devolver un objeto es que simplemente viendo la firma del método de acción, ya se puede ver que tipo de datos se devuelven. Y el compilador, por supuesto, nos verificará que estemos devolviendo el tipo correcto. En este ejemplo el compilador nos deja devolver un array de cadenas, pero no podríamos devolver un array de enteros. También al tener la firma en el método de acción, herramientas como Swagger pueden generar documentación indicando que este método devuelve un conjunto de cadenas.

    Parece la forma perfecta, ¿verdad? Pero, si lo fuese, no existiría este post, claro. El principal problema que pagamos es que WebApi siempre devuelve un código HTTP 200. No tenemos manera de indicar qué codigo HTTP queremos establecer.

    Supongamos un código como el siguiente:

    1. public string Get(int id)
    2. {
    3.     if (id > 1) { return “value”; }
    4.     else { return null; }
    5. }

    En una aplicación real haríamos una consulta a nuestro repositorio de datos y buscaríamos por el id. La pregunta es… ¿qué hacemos si no existe el elemento? En este caso devolvemos null. No parece una mala opción, pero el problema es que nos estamos pasando REST por el forro:

    image

    Observa que hacemos una petición pasando un 1 como id, y lo que obtenemos es un 200, con el null serializado. Esto no es para nada bonito y como digo antes rompe REST. En este caso lo suyo sería devolver un 404. Pero no tenemos manera de hacerlo, porque Web Api no nos deja establecer el código HTTP.

    Vale, estoy mintiendo. Sí que tenemos una manera de hacerlo, que es la siguiente:

    1. public string Get(int id)
    2. {
    3.     if (id > 1) { return “value”; }
    4.     else { throw new HttpResponseException(HttpStatusCode.NotFound); }
    5. }

    La solución pasa por lanzar una excepción para establecer el código HTTP.  Ahora sí que si pedimos el id 1 obtenemos nuestro 404. He de decir que no me gusta para nada esa aproximación, las excepciones deberían ser para eso… cosas excepcionales. Buscar un id y no encontrarlo no tiene nada de excepcional. Es control de flujo.

    Vale… alguien puede argumentar que tiene lógica tratar eso como un error (de hecho 404 es un código de error). Muy bien, pero entonces imagina que quieres devolver un 201 (código que se usa para indicar que una entidad ha sido creada). De verdad ¿vas a lanzar una excepción para devolver un 201? El código 201 no es, para nada, un código de error. Es un código de éxito, que indica que la petición ha sido procesada, y el resultado de procesar dicha petición ha implicado la creación de alguna entidad (que se puede mandar en el cuerpo). Si usas una API y quieres ser realmente RESTful deberías mandar códigos de éxito un poco más específicos que el 200, que para eso existen.

    Es en este punto donde entra la otra alternativa. Ya sea devolviendo un HttpResponseMessage o un IHttpActionResult (el segundo se introdujo en WebApi 2 y no es más que una interfaz que define una factoría para crear objetos del primero).

    En este caso, al especificar un mensaje de respuesta, tenemos mucho más control que en el caso anterior:

    1. public HttpResponseMessage Get(int id)
    2. {
    3.     if (id > 1) { return Request.CreateResponse<string>(“value”); }
    4.     else { return Request.CreateErrorResponse(HttpStatusCode.NotFound, “invalid id”); }
    5. }

    El método CreateResponse espera un objeto y opcionalmente un código HTTP. Si no lo colocamos manda el 200, pero podemos indicar el que queramos. Por otro lado el CreateErrorResponse, espera un código HTTP y un mensaje de error a mandar en el cuerpo.

    No tenemos que usar excepciones para casos controlados pero ahora pagamos un precio: leyendo la firma no tenemos nada claro que devuelve esta acción, de hecho podría devolver cualquier cosa (incluso cosas distintas en distintos returns, lo que según cuando puede ser otra ventaja). Pero… ¿como podemos documentar que este método devuelve una cadena?

    He habilitado Swagger y el resultado para este controlador es el siguiente:

    image

    El primer es un método que devuelve un IEnumerable y el segundo un método que devuelte un HttpResponseMessage. Se puede ver como en el primer caso Swagger es capaz de inferir el tipo de respuesta, pero en el segundo caso no. Tiene lógica. No es tan malo como parece, desde la propia página de Swagger podemos probar los métodos y ver realmente qué nos devuelven. Pero… ¿hay alguna manera de indicar que el segundo método devuelve una cadena?

    Pues sí, podemos usar el atributo ResponseType:

    1. [ResponseType(typeof(string)]
    2. public HttpResponseMessage Get(int id)
    3. {
    4.     if (id > 1) { return Request.CreateResponse<string>(“value”); }
    5.     else { return Request.CreateErrorResponse(HttpStatusCode.NotFound, “invalid id”); }
    6. }

    ResponseType añade metadatos que indican que la acción devuelve objetos de un determinado tipo. Entonces Swagger (u otra herramienta) puede usar esos metadatos para dar más información al generar la documentación.

    Por supuesto es responsabilidad nuestra hacer que la acción devuelva realmente un objeto del tipo indicado en ResponseType, aquí el compilador no puede ayudarnos.

    Personalmente pienso que devolver un HttpResponseMessage (o una IHttpActionResult) es mucho más elegante que devolver un objeto. Gracias al uso de ResponseType las APIs pueden estar igualmente documentadas, y a cambio tenemos mucho más control sobre los códigos HTTP que usamos y no debemos usar excepciones.

    Pero… ¿Y tú, qué opinas?

    Si quieres, puedes invitarme a un café xD

    eiximenis
    ESCRITO POR
    eiximenis
    Compulsive Developer