Model binding en ASP.NET MVC y WebApi

Una de las confusiones más habituales con la gente que viene de MVC y pasa a WebApi es que el funcionamiento del model binding (es decir rellenar los parámetros de los controladores a partir de los datos de la request) es distinto entre ambos frameworks. La confusión viene porque a primera vista todo parece que funcione igual pero realmente hay diferencias muy profundas entre ambos frameworks.

Veamos, pues, algunas de las diferencias que hay entre ambos frameworks

ASP.NET MVC Value Providers primero y Model Binders después

En ASP.NET MVC el proceso de model binding está compuesto de dos pasos:

  1. Primero existe una serie de clases, llamadas value providers que se encargan de leer la petición HTTP y colocar los datos en un sitio común (como un diccionario). Así no importa si un dato (p. ej. Id) está en querystring o en formdata). Simplemente, se obtendrá ese Id y se colocará en el ese sitio común. Hay varios value providers porque cada value provider puede analizar una parte de la petición. Así hay un value provider que analiza la querystring, un par que analizan formdata (uno si el content-type es application/x-www-form-urlencoded y otro si es application/json) y así sucesivamente.
  2. Una vez los datos de la petición están en este lugar común entra en acción el model binder. El model binder recoge los datos de ese sitio común y los utiliza para crear los objetos que son pasados como parámetros en la acción del controlador. Cada parámetro es enlazado por un model binder distinto dependiendo del tipo del parámetro (aunque por defecto casi todos los tipos se enlazan usando el DefaultModelBinder, nosotros podemos crear model binders propios y vincularlos a tipos de parámetros).

Supongamos dos clases como las siguientes:

  1. public class Customer
  2. {
  3.    public int Id { get; set; }
  4.    public string Name {  get; set; }
  5.    public string Address{ get; set; }
  6. }
  7.  
  8. public class Product
  9. {
  10.    public int Id { get; set; }
  11.    public string Name { get; set; }
  12. }

Y una vista que envíe los datos:

  1. <form>
  2.     <input type="text" name="Id" /><br />
  3.     <input type="text" name="Name" /><br />
  4.     <input type="text" name="Address" /><br />
  5.     <input type="submit" value="send"/>
  6. </form>

Ahora si tenemos un controlador que tiene una acción con dos parámetros (Customer y Product) ¿qué es lo que recibimos? Pues lo siguiente:

image

Es decir el valor de las propiedades Id y Name se ha enlazado a ambos parámetros. Es lógico: los value providers han recogido 3 valores de la petición resultado de enviar el formulario (Id, Name y Address) y los han colocado en el “sitio común”. Luego cuando el model binder del parámetro customer debe crear el objeto usa los valores de dicho sitio común para enlazar las propiedades… lo mismo que el model binder del parámetro product. De ahí que las propiedades que se llamen igual tengan el mismo valor.

Por supuesto esta posibilidad está contemplada en ASP.NET MVC, de forma que el model binder entiende de “prefijos”. Así si modifico uno de los campos de texto para que sea:

  1. <input type="text" name="customer.Name" />

Ahora el valor del campo de la petición llamado “customer.Name” se enlazará solo a la propiedad Name del parámetro customer (product.Name será null).

El modelo de value providers + model binders es muy versátil y potente.

WebApi – Básicamente MediaTypeFormatters (básicamente)

En WebApi la aproximación es radicalmente distinta. Primero hay una distinción clara, clarísima, sobre si el parámetro de la acción se enlaza desde el cuerpo de la petición o desde cualquier otro sitio (p. ej. querystring).

En MVC el model binder no sabe de donde vienen los datos que usa para enlazar los parámetros del controlador, ya que los saca siempre del “sitio común” (donde lo dejan los value providers). WebApi usa una orientación distinta pero la regla de oro fundamental es: El cuerpo de la petición puede ser leído una sola vez.

En efecto en WebApi el cuerpo de la petición HTTP es un stream forward-only, lo que significa que puede ser leído una única vez (en ASP.NET MVC el cuerpo está cacheado ya que distintos value providers pueden leerlo). Eso está hecho por temas de rendimiento.

Así en WebApi se usa un paradigma basado básicamente en el content-type. Aparecen unos entes nuevos (los media type formatters) encargados de leer el cuerpo de la petición. Pero SOLO UN media type formatter puede leer el cuerpo (recuerda: puede ser leído una sola vez). Y como sabe WebApi qué media type formatter procesa el cuerpo de la petición? Pues basándose en el content-type.

El media type formatter lee pues el cuerpo de la petición y rellena un objeto de .NET (del tipo indicado) en base a dichos datos. Efectivamente: el media type formatter lee de la petición y enlaza las propiedades. Eso implica una restricción importante: tan solo un parámetro de la acción puede ser obtenido a partir de los datos del cuerpo de una petición.

Veamos la diferencia: he creado un controlador WebApi con un método análogo al anterior que recibe (via POST) un Customer y un Product:

  1. public class DataController : ApiController
  2. {
  3.     public bool Post(Product product, Customer customer)
  4.     {
  5.         return true;
  6.     }
  7. }

Y he modificado la vista para que haga post del formulario a dicho controlador. Existe un media type formatter que se encarga de leer los datos cuando el content-type es application/x-www-form-urlencoded (de hecho, realmente hay dos pero tampoco es necesario entrar en más detalles). ¿Y cual es el resultado? Pues un error. Concretamente una System.InvalidOperationException con el mensaje: Can’t bind multiple parameters (‘product’ and ‘customer’) to the request’s content.

La razón es que tenemos dos parámetros a rellenar basándonos en el cuerpo de la petición. Pero dicho cuerpo puede ser leído una sola vez por un media type formatter. Y un media type formatter tan solo puede enlazar un objeto.

Es ahí donde entra el atributo FromUri. Dicho atributo se aplica a parámetros de la accion para indicar que esos parámetros se deben enlazar a partir de datos encontrados en la URL (querystring):

  1. public bool Post(Product product, [FromUri] Customer customer)
  2. {
  3.     return true;
  4. }

Si ahora ejecutamos de nuevo podemos ver que recibe el controlador son:

image

Podemos ver como customer no tiene datos (a pesar de haber un campo Address en el cuerpo de la petición) porque customer se enlaza a partir de los datos de la URL (querystring).

Por defecto WebApi usa siempre un media type formatter cuando el parámetro es un tipo complejo (una clase) a no ser que haya el atributo FromUri aplicado. Si el parámetro es un tipo simple (p. ej. int o un string) intenta enlazarlo a partir de la URL a no ser que haya aplicado el atributo contrario [FromBody]. Eso sí recuerda que solo un parámetro puede ser enlazado desde el cuerpo de la petición.

WebApi – ModelBinders y Value Providers

Cuando se enlaza un parámetro que no viene del cuerpo de la petición (es decir que viene de la URL) WebApi usa entonces el mismo modelo que MVC. Es decir primero los value providers recogen los datos de la petición (excepto el cuerpo) y los colocan en un sitio común y luego los model binders usan los datos de este sitio cómún para enlazar los parámetros).

P. ej. modifico el controlador de WebApi para que acepte GET y enlace ambos parámetros desde la URL:

  1. public bool Get([FromUri]Product product, [FromUri] Customer customer)
  2. {
  3.     return true;
  4. }

Ahora si realizo una llamada GET a /api/Data/10?name=test lo que obtengo es:

image

Los value providers recogen los datos de la URL (hay dos, uno para los route values (como /10) y otro para la querystring) y los dejan en el sitio común. Luego los model binders usan esos datos para enlazar tanto product como customer y es por eso que obtengo los datos duplicados (es el caso análogo al caso inicial de ASP.NET MVC).

En resumen: hemos visto cuatro pinceladas de como ASP.NET MVC y WebApi enlazan los parámetros a los controladores haciendo especial énfasis en las diferencias. Dejamos para un post posterior el ver como funciona este tema en vNext pues recuerda que en vNext WebApi y MVC son un solo framework 🙂

Saludos!

5 comentarios sobre “Model binding en ASP.NET MVC y WebApi”

  1. Hombre,

    Menos mal que ya llega la cordura, sobre todo por lo que citas al final que no es otra cosa que en vNext WepApi y MVC son un solo framework, será un solo binding?

    O seguirán haciendo elucubraciones fantásticas de como se hace un framework a trozos. Creo que en este post de «binding la historia interminable»ya demandaba un poco de cordura 🙂

    http://geeks.ms/blogs/phurtado/archive/2014/05/09/binding-la-historia-interminable.aspx

  2. Dadas las diferencias entre los dos entiendo que en vNext no va a quedar más remedio que articularse en uno sólo, del mismo modo que se ha anudado la parte más backend de las apps W8.1/WP8.1 de modo que puedes tener todo en un sólo núcleo (salvo las partes específicas de diseño de cada plataforma).

    Si han sido capaces de hacerlo en una plataforma deberían ser capaces de llevarlo a cabo en la otra. Y de este modo tirar hacia un .Net de sólo una plataforma y no de mil enanos diferentes.

    El divide y vencerás es para los problemas, no para las soluciones, aunque ellos piensen que para las soluciones también.

  3. En vNext hay un solo modelo de binding… Que mezcla cosas de MVC y de WebApi, al menos por lo que he visto hasta el momento 😛

    Tampoco podía ser de otro modo: en vNext ya no tiene sentido hablar de WebApi en tanto que este se ha integrado completamente dentro de MVC6. Ya no existe ApiController ni tenemos toda la duplicidad de clases entre System.Web.Http y System.Web.Mvc

    Estoy cocinando un post al respecto!!

  4. No podría ser de otro modo:P

    Yo creo que sí. Si hace dos años hubiesen roto con
    System.Web.dll

    Pero entonces a los que hablamos de Node.js nos tomaban por locos

    Yo a estás alturas me pregunto desde hace 2 años en Expres/Connect que
    es lo que se ha copiado, no ha cambiado el mildelware, creo que era

    algo asi function (req,res,next) y sigue siendo

    Lo que pido es sencillo no me trates como lo que no soy que yo me encargo
    del Request y del Response. Es decir que nos quieren deslumbrar volviendo
    a utilizar asp del clásico. Lo proximo codigo spaghetti. Ya lo verás 😛

  5. Por «No podía ser de otro modo» me refería a que en vNext había un solo modelo de binding 😛 No si la evolución de ASP.NET podía ir en un sentido o en otro.

    Es obvio que vNext toma muchas «ideas» de nodejs (y otros) pero eso NO es malo. A mi la idea y el enfoque general de vNext me gusta mucho. Mi principal duda es si el ecosistema de desarrolladores de MS está preparado para él o no 🙂

    Código spaghetti?? Tampoco te pases!!! ^_^ 😉

Responder a pedrohurtado Cancelar respuesta

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