WebApi–¿objeto o HttpResonseMessage?

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<string> 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?

6 comentarios en “WebApi–¿objeto o HttpResonseMessage?”

  1. Hace poco me vi en el mismo dilema. ¿Devolver un objeto o un HTTPResponseMessage? Al final me decanté por este último. Como comentas claramente en tu ejemplo, un null en una respuesta HTTP200 no es nada claro, sea restful o no. Es mucho más limpio usar los códigos que para eso están. Y las excepciones para cuando realmente sucedan. Da algo más de trabajo añadiendo el metedato, pero tienes más control, es más claro y además el día de mañana la documentación que generes de tu API muy probablemente la tengas que ampliar y detallar.
    Swagger no lo conocía me lo apunto.
    Gran post!

  2. El problema es que alteras el método condicionado por la tecnología, en este caso WebApi y pierdes visibilidad en los tipos devueltos, quizás lo mejor seria introducir una capa intermedia para retornar un IHttpActionResult que te facilita el testeo, aunque desde luego lo mas natural seria devolver directamente el objeto, pero no queda otra.

    Buen post!!!

    1. Buenas Juan!
      “alteras el método condicionado por la tecnología”
      Pero es que en este caso el método ES DE la tecnología. Es un controlador de WebApi, no una clase de negocio o de alguna librería.

      Sería más natural devolver el objeto… pero en REST puedes devolver (de hecho es recomendable) con varios códigos 2xx. Así que “solo un return” se nos queda corto. Por supuesto, tendríamos otras opciones, como una propiedad StatusCode en el controlador, que nos permitese establecer un código de retorno. Pero esto solo también tiene sus problemas (puedes establecer la propiedad en un lugar y el return estar en otro, lo que complica el seguimiento del código)…

      Gracias por comentar! 😀

  3. Buenas, estoy leyendo muy atento tus post ya que estoy haciendo pruebas de conceptos sobre .NetCore y me preguntaba que pasaria al implementar con .NetCore una WebApi, porque por lo que pude ver ResponseType proviene de la libreria System.Web.Http y esta hasta donde se no es compatible con DNX Core 5.0, como podría implementar esto con DNX Core 5.0?

    Desde ya muchas gracias, excelente post!

Deja un comentario

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