miércoles, 26 de mayo de 2010

Desacopla tus datos XML del formato…

Leyendo este post de Gisela sobre la serialización XML me he decidido escribir este… es lo que tiene la realimentación en los blogs :)

El uso de atributos que menciona Gis en su post es realmente genial. A mi me encanta: me permite definir mis clases en un momento y es muy útil cuando leemos datos xml de una fuente externa. Pero hay un detalle que puede ser un problema: El esquema XML está totalmente acoplado de la clase que tiene los datos. Si estamos leyendo de dos fuentes externas que tienen esquemas XML distintos pero tienen los mismos datos, debemos duplicar las clases que serializan esos datos, ya que los atributos a usar serán distintos.

P.ej. supon que tenemos dos fuentes externas, que nos devuelven los mismos datos, pero de forma diferente:

<info>
<usuarios>
<usuario>
<nombre>eiximenis</nombre>
<nombrereal>Edu</nombrereal>
</usuario>
</usuarios>
</info>






<information>
<twitter.users>
<twitter.user name="eiximenis" realname="Edu" />
</twitter.users>
</information>





La información es exactamente la misma, pero el esquema es distinto. Si queremos usar atributos para leer estos dos archivos xml debemos implementar dos conjuntos de clases:




// Clases para leer el 1er formato
[XmlRoot("info")]
public class Info
{
private readonly List<UserInfo> _users;

public Info()
{
_users = new List<UserInfo>();
}

[XmlArray("usuarios")]
[XmlArrayItem("usuario", typeof(UserInfo))]
public List<UserInfo> Usuarios { get { return _users; } }
}

public class UserInfo
{
[XmlElement("nombre")]
public string Nombre { get; set; }

[XmlElement("nombrereal")]
public string NombreReal { get; set; }
}






// Clases para leer el segundo formato
[XmlRoot("information")]
public class TweeterInfo
{
private readonly List<TweeterUserInfo> _users;

public TweeterInfo()
{
_users = new List<TweeterUserInfo>();
}

[XmlArray("twitter.users")]
[XmlArrayItem("twitter.user", typeof(TweeterUserInfo))]
public List<TweeterUserInfo> Users { get { return _users; } }
}

public class TweeterUserInfo
{
[XmlAttribute("name")]
public string Name { get; set; }

[XmlAttribute("realname")]
public string RealName { get; set; }
}





El problema no es que tengamos que realizar el doble de trabajo… es que tenemos dos conjuntos de clases totalmente distintos que no tienen ninguna relación entre ellos. Si desarrollamos un método que trabaje con objetos de la clase Info, dicho método no trabajará con objetos de la clase TweeterInfo aún cuando ambas clases representan la misma información.



La solución pasa, obviamente, por usar interfaces: Ambas clases deberían implementar una misma inferfaz que nos permitiese acceder a los datos:




// Interfaces

public interface ITwitterInfo
{
IEnumerable<ITwitterUser> Users { get; }
}
public interface ITwitterUser
{
string Name { get; }
string RealName { get; }
}





Las interfaces se limitan a definir los datos que vamos a consultar desde nuestra aplicación. En este caso sólo hay getters porque se supone que dichos datos son de lectura sólamente.



El siguiente paso es implementar dichas interfaces en nuestras clases. Lo mejor es usar una implementación explícita. La razón es evitar tipos de retorno distintos entre propiedades que pueden tener el mismo nombre (p.ej. la propiedad Users de TweeterInfo devuelve una List<TweeterUserInfo> mientras que la propiedad de la interfaz ITwitterInfo devuelve un IEnumerable<ITwitterUser> y eso no compilaría:




[XmlRoot("information")]
public class TweeterInfo : ITwitterInfo
{
private readonly List<TweeterUserInfo> _users;

public TweeterInfo()
{
_users = new List<TweeterUserInfo>();
}

[XmlArray("twitter.users")]
[XmlArrayItem("twitter.user", typeof(TweeterUserInfo))]
public List<TweeterUserInfo> Users { get { return _users; } }
}

// error CS0738: 'XmlDesacoplado.Formato2.TweeterInfo' does not implement interface member
// 'XmlDesacoplado.Interfaz.ITwitterInfo.Users'. 'XmlDesacoplado.Formato2.TweeterInfo.Users' cannot
// implement 'XmlDesacoplado.Interfaz.ITwitterInfo.Users' because it does not have the matching return type
// of 'System.Collections.Generic.IEnumerable<XmlDesacoplado.Interfaz.ITwitterUser>'





Como os digo, para que funcione basta con una implementación explícita de la interfaz. Es decir basta con añadir:




// Implementación explícita
IEnumerable<ITwitterUser> ITwitterInfo.Users { get { return Users; } }





En el resto de clases debemos hacer lo mismo (implementar las interfaces).



Ahora todo nuestro código puede trabajar simplemente con objetos ITwitterInfo.



Vale… esto está muy bien, pero como puedo cambiar dinámicamente el “formato” del archivo a leer?



Una posible solución es realizar una clase “lectora” de XMLs, algo tal que así:




class LectorXmls
{
public ITwitterInfo LeerFormato<TSer>(string file) where TSer : class, ITwitterInfo
{
TSer data = default(TSer);
using (FileStream fs = new FileStream(file, FileMode.Open))
{
XmlSerializer ser = new XmlSerializer(typeof(TSer));
data = ser.Deserialize(fs) as TSer;
}
return data;
}
}





Y luego en vuestro código podéis escoger que clase serializadora usar:




ITwitterInfo f1 = new LectorXmls().LeerFormato<Info>("Formato1.xml");





Por supuesto, si quieres que el tipo de la clase serializadora esté en un archivo .config y así soportar “futuros formatos” de serialización no podrás usar un método genérico, pero en este caso te basta con pasar un Type como parámetro:




public ITwitterInfo LeerFormato(string file, Type serType)
{
ITwitterInfo data = null;
using (FileStream fs = new FileStream(file, FileMode.Open))
{
XmlSerializer ser = new XmlSerializer(serType);
data = ser.Deserialize(fs) as ITwitterInfo;
}
return data;
}





Y el valor del Type lo puedes obtener a partir de un fichero de .config.



Un saludo!!!



PD: Esto es un crosspost desde mi blog en geeks.ms!

viernes, 21 de mayo de 2010

Redefiniendo GetHashCode

Hola a todos! Un post para comentar paranoias varias sobre algo que parece tan simple como redefinir GetHashCode()…

Primero las dos normas básicas que supongo que la mayoría ya conoceréis:

  1. Si se redefine el método Equals() de una clase debería redefinirse también el método GetHashCode(), para que pueda cumplirse la segunda norma que es…
  2. Si la llamada a Equals para dos objetos devuelve true, entonces GetHashCode() debe devolver el mismo valor para ambos objetos.

Una forma fácil y rápida de implementar GetHashCode() y que cumpla ambas normas es algo así:

public class Foo
{
public int Bar { get; set;}
public int Baz { get; set;}

public override bool Equals(object obj)
{
return obj is Foo && ((Foo)obj).Bar == Bar && ((Foo)obj).Baz == Baz;
}

public override int GetHashCode()
{
return string.Format("{0},{1}", Bar, Baz).GetHashCode();
}
}





Simplemente creamos una representación en cadena del objeto y llamamos a GetHashCode de dicha cadena. ¿Algún problema? Bueno… pues la tercera norma de GetHashCode que no siempre es conocida:





Si a alguien le parece que esta tercera norma entra en contradicción con la segunda, en el caso de objetos mutables… bienvenido al club! ;-)



Si no cumplimos esta tercera norma… no podemos objetos de nuestra clase como claves de un diccionario: P.ej. el siguiente test unitario realizado sobre la clase Foo, falla:




[TestClass]
public class FooTests
{
[TestMethod()]
public void FooUsedAsKey()
{
var dict = new Dictionary<Foo, int>();
Foo foo1 = new Foo() { Bar = 10, Baz = 20 };
Foo foo2 = new Foo() { Bar = 10, Baz = 30 };
dict.Add(foo1, 1);
dict.Add(foo2, 2);
foo2.Baz = 20;
int value = dict[foo2];
Assert.AreEqual(2, value); // Assert.AreEqual failed. Expected:<2>. Actual:<1>.
}
}





Esperaríamos que la llamada a dict[foo2] nos devolviese 2, ya que este es el valor asociado con foo2… pero como foo2 ha mutado y ahora devuelve el mismo hashcode que foo1, esa es la entrada que nos devuelve el diccionario… y por eso el Assert falla.




Nota: Si alguien piensa que usando structs en lugar de clases se soluciona el problema… falso: Usando structs ocurre exactamente lo mismo.




Ahora… varias dudas filosóficas:




  1. Alguien entiende que el test unitario está mal? Es decir que el assert debería ser AreEqual(1, value) puesto que si foo2 es igual a foo1, debemos encontrar el valor asociado con foo1, aunque usemos otra referencia (en este caso foo2).


  2. Planteando lo mismo de otro modo: Debemos entender que el diccionario indexa por valor (basándose en equals) o por referencia (basándose en ==)? El caso es entendamos lo que entendamos, la clase Dictionary usa Equals y no ==.


  3. El meollo de todo el asunto ¿Tiene sentido usar objetos mutables como claves en un diccionario?



Yo entiendo que no tiene sentido usar objetos mutables como claves, ya que entonces nos encontramos con todas esas paranoias… y no se vosotros pero yo soy incapaz de escribir un método GetHashCode() para la clase Foo que he expuesto y que se cumpla la tercera condición.



Si aceptamos que usar objetos mutables como claves de un diccionario no tiene sentido, ahora me viene otra pregunta: Dado que es muy normal querer redefinir Equals para objetos mutables, porque se supone que siempre debemos redefinir también GetHashCode? No hubiese sido mucho mejor definir una interfaz IHashable y que el diccionario sólo aceptase claves que implementasen IHashable?



No se… intuyo que la respuesta puede tener que ver con el hecho de que genéricos no aparecieron hasta la versión 2 del lenguaje (en lo que a mi me parece uno de los errores más grandes que Microsoft ha cometido al respecto de .NET), pero quien sabe…



… las mentes de Redmond son inescrutables.



Un saludo!



PD: Para variar esto es un crosspost desde mi blog en geeks.ms! Pásate mejor por allí que somos más!

miércoles, 12 de mayo de 2010

ASP.NET MVC: Custom Model Binders

Seguimos esa serie donde intentamos bucear un poco por algunas interioridades de ASP.NET MVC, intentando ver como funcionan por dentro algunas de las características de ese framework tan apasionante como és ASP.NET MVC. Si en el primer post de la serie vimos lo que eran los value providers y en el segundo post vimos como funcionaba el DefaultModelBinder en el post de hoy veremos como podemos crear Model Binders propios (lo que a su vez, nos ayudará a entender todavía más como funciona el DefaultModelBinder).

Bueno, para empezar dejemos claro un punto fundamental:

  • En ASP.NET MVC no estamos limitados a un solo Model Binder, podemos tener muchos model binders, de hecho uno por cada tipo de modelo.

La clase estática ModelBinders es la que mantiene el conjunto de model binders que estén registrados en el sistema. Para registrar un Model Binder propio simplemente llamamos al método Add() de la colección Binders expuesta por dicha clase:

ModelBinders.Binders.Add(typeof(FooClass), new FooBinder());





El método Add() espera el tipo de modelo y el binder a usar para objetos de dicho tipo. Si para un tipo de modelo no existe model binder definido, se usará el model binder predeterminado, cuyo valor por defecto es el DefaultModelBinder pero que también podemos cambiar:




ModelBinders.Binders.DefaultBinder = new CustomModelBinder();





1. Un ejemplo sencillo… un Model Binder para objetos simples



Vamos a crear un Model Binder propio tan sencillo como (probablemente) inútil: un model binder propio para objetos de tipo string, que simplementa convierta los valores en mayúsculas.



El trato del framework de ASP.NET MVC con los model binders es muy simple: la interfaz IModelBinder que deben implementar todos los model binders define un sólo método: BindModel. Como se las apañe el model binder internamente le da igual al framework. Por suerte para nosotros la clase DefaultModelBinder es muy extensible, de forma que cuando implementamos un model binder propio, lo más normal (aunque no es obligatorio) es derivar de dicha clase. Así que antes vamos a ver que es lo que hace el DefaultModelBinder cuando debe enlazar un modelo:



 Elace de un modelo con el DefaultModelBinder



Nota: No estan todas las funciones que usa el CustomModelBinder (faltan las relacionadas con la validación del modelo, pero no quiero hablar hoy de validaciones).



Todas estas funciones son virtuales y por lo tanto pueden ser redefinidas en clases derivadas. Aquí tienes una breve descripción de cada método:




  • BindModel: El único método de IModelBinder, su responsabilidad es devolver el modelo creado y enlazado.


  • CreateModel: Crea una instancia del modelo


  • GetTypeDescriptor: Obtiene la información del tipo del modelo


  • GetModelProperties: Obtiene información sobre las propiedades del modelo


  • BindProperty: Método que enlaza una propiedad concreta del modelo


  • GetPropertyValue: Vista en el post anterior, obtiene el valor de una propiedad. Para ello usará el model binder asociado al tipo de la propiedad y llamará a su BindModel.


  • SetProperty: Vista en el post anterior, establece el valor de una propiedad



Volviendo a nuestro caso (un model binder que convierta las cadenas en mayúsculas) vamos a derivar de DefaultModelBinder y vamos a redefinir el método BindModel. Nos basta con este, puesto que un objeto string no tiene propiedades que se puedan enlazar, así que no se llamaría nunca a BindProperty de nuestro model binder: en BindModel vamos a hacer todo el trabajo:




public class StringBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object o = base.BindModel(controllerContext, bindingContext);
return o as string != null ? ((string)o).ToUpper() : o;
}
}





El código es trivial: llamamos a la implementación de BindModel del DefaultModelBinder (de esa manera todo el trabajo va a realizarlo el CustomModelBinder) y luego simplemente convertimos el resultado a mayúsculas.



Sólo nos queda registrar nuestro model binder, usando la clase ModelBinders. Esto suele hacerse en Global.asax en el Application_Start:




ModelBinders.Binders.Add(typeof(string), new StringBinder());





Y listos… ahora cualquier cadena que se enlace será convertida a mayúsculas, pero atención! No es necesario que el controlador reciba una cadena: si la acción del controlador recibe cualquier clase que tenga una propieda de tipo string, dicha propiedad se enlazará usando nuestro model binder, por lo que dicha propiedad será convertida a mayúsculas.



2. Otro ejemplo, colecciones a partir de una sola cadena



Vamos ahora a crear un model binder para una clase de modelo específica. La clase es tal como sigue:




public class Pedido
{
public string Nombre {get; set;}
public IEnumerable<string> Bebidas {get; set;}
}





Como ya sabemos, podemos enlazar colecciones con N campos en la petición que tengan el mismo nombre, o con N campos que tengan nombre[0], nombre[1],…, nombre[N-1]. Pero en este caso, vamos a crear una vista que tenga un solo campo que se llame Bebidas:




<form action="/Pedido/Nuevo" method="post">
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
<label for="Nombre">Nombre</label>
</div>
<div class="editor-field">
<input id="Nombre" name="Nombre" type="text" value="" />
</div>
<div class="editor-label">
<label for="Bebidas">Bebidas</label>
</div>
<div class="editor-field">
<input id="Bebidas" name="Bebidas" type="text" value="" />
</div>
</fieldset>
<input type="submit" value="Pedir" />
</form>





Fijaos que sólo tenemos un <input type=”text” name=”Bebidas”>. Cuando hacemos submit del formulario los datos de la petición POST quedan de la siguiente forma:



Content-Type: application/x-www-form-urlencoded

Content-Length: 51

Nombre=eiximenis&Bebidas=coca-cola%2C+fanta%2C+agua



Fijaos que existe un solo campo POST llamado Bebidas, cuyo valor es la cadena “coca-cola, fanta, agua”. Si usáis del DefaultModelBinder para enlazar este modelo, el controlador recibirá un objeto Pedido cuyo nombre será correcto, y la propiedad Bebidas será un IEnumerable<string> con un solo elemento (con valor coca-cola, fanta, agua):



image



Nota: Fíjate como el nombre se ha convertido a mayúsculas, ya que el DefaultModelBinder ha usado el StringBinder que hicimos antes para enlazar la propiedad Nombre que es de tipo String!



Bueno… esperar que el DefaultModelBinder entienda que un campo separado por comas es realmente una lista de cadenas es esperar demasiado, así que vamos a hacer un CustomBinder que haga esto:




public class PedidoBinder : DefaultModelBinder
{
protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
{
object value = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
object retVal = value;
if (propertyDescriptor.Name == "Bebidas" && value as IEnumerable<string> != null)
{
retVal = ((IEnumerable<string>)value).First().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
return retVal;
}
}





El código es muy simple… A diferencia del caso anterior, ahora redefinimos el método GetPropertyValue (dado que la clase Pedido si que tiene propiedades). Simplemente miramos si estamos obteniendo la propiedad “Bebidas” y si es el caso:




  1. Cogemos el valor que ha obtenido el DefaultModelBinder


  2. Lo convertimos a IEnumerable<string>, ya que sabemos que se trata de un IEnumerable<string> con una sola cadena


  3. Sobre esa cadena, devolvemos el resultado de hacer el Split (lo que devuelve un array, que a su vez es otro IEnumerable<string>).



Y listos, ahora sí que el enlace se realiza correctamente:



image



Bien, vamos a ver ahora otra manera en como podríamos codificar ese mismo model binder: vamos a implementar directamente la interfaz IModelBinder (no es lo mejor en este caso, pero vamos a aprender algunas cosillas más haciendolo).



3. Implementando IModelBinder



Insisto: En muchas ocasiones es mejor derivar de DefaultModelBinder en lugar de implementar directamente la interfaz IModelBinder (para aprovechar parte de lo que el DefaultModelBinder ya hace por nosotros).



Aquí tenemos una posible implementación:




public class PedidoBinderInterfaz : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
Pedido pedido = new Pedido();

pedido.Nombre = bindingContext.ValueProvider.GetValue("Nombre").AttemptedValue as string;
IEnumerable<string> bebidas = bindingContext.ValueProvider.GetValue("Bebidas").RawValue as IEnumerable<string>;
if (bebidas != null)
{
pedido.Bebidas = ((IEnumerable<string>)bebidas).First().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}

return pedido;
}
}





Fijate que dado que sabemos que el modelo es de tipo Pedido (puesto que este model binder sólo se registra para objetos de tipo Pedido) podemos crear directamente un Pedido y enlazar sus dos propiedades (Nombre y Bebidas).



Y ahora lo que quería que vierais: Para preguntar a los value providers por el valor de un campo de la petición, se usa el método GetValue de la propiedad ValueProvider del bindingContext. La propiedad ValueProvider es un objeto de la clase ValueProviderCollection pero nosotros lo recibimos como un IValueProvider. Eso es muy interesante: realmente el objeto es una colección de value providers, pero nosotros la vemos como un solo value provider y simplemente llamamos al método GetValue(). La propia clase se encarga de iterar por los value providers que contiene y encontrar el primero que nos pueda devolver el valor indicado.



Igual viendo el código pensáis que tampoco hay para tanto, que implementar la interfaz IModelBinder tampoco es tan complicado… bueno, fijaos en que lo que no hace este Model Binder y que si que hace el DefaultModelBinder:




  1. No estamos validando el modelo… Siempre podemos meter el código de validación dentro de BindModel y tampoco seria muy costoso hacerlo, cierto… pero perdemos la capacidad de usar DataAnnotations p.ej. (Si queremos soportar DataAnnotations entonces si que debemos empezar a tirar código)


  2. No estamos usando los model binders concretos para las propiedades… me explico: si enlazáis un modelo Pedido con este model binder, la propiedad Nombre la estamos enlazando nosotros directamente, sin usar el StringBinder que teníamos registrado… efectivamente, el valor aparece en minúsculas:



image



Bueno… espero que os esté gustando esta serie de posts sobre temas “internos” de ASP.NET MVC… aún nos quedan varias cosas por destripar!!!



Un saludo!!



PD: El código de ejemplo lo podéis descargar aquí (link a mi skydrive).



PD2: Como siempre… esto es un crosspost desde mi blog de geeks.ms… Pásate mejor por allí!!!

lunes, 10 de mayo de 2010

ASP.NET MVC: El DefaultModelBinder

En el post anterior vimos que eran los Value Providers de ASP.NET MVC. En éste, lo que vamos a ver es el DefaultModelBinder y algunas de sus “interioridades”…

Disclaimer: Al igual que el post anterior, este asume conocimientos básicos de ASP.NET MVC, así como de http en general.

Antes que nada el repaso rápido: Cuando un controlador recibe en una acción un objeto del modelo, ASP.NET MVC es capaz de realizar el binding entre los datos contenidos en la request y el objeto que espera el controlador. Por un lado los value providers se encargan de leer los datos de la request y guardarlos “en una estructura común” y por otro los model binders crean el objeto del modelo a partir de dicha estructura común.

Hoy vamos a diseccionar el DefaultModelBinder, el model binder que trae por defecto ASP.NET MVC.

Colecciones

Vamos a suponer la siguiente clase del modelo:

public class Persona
{
public IEnumerable<string> Telefonos { get; set; }
public string Nombre { get; set; }
public int Edad { get; set; }
}





Asumid que tenemos un controlador Personas con una acción Crear que recibe un objeto Persona. Vamos a ver como el DefaultModelBinder es capaz de mapear las propiedades, incluída la colección Telefonos. Para ello tengo simplemente la siguiente vista:




<form action="/Personas/Crear" method="post" >
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
<label for="Nombre">Nombre</label>
<input type="text" name="Nombre" />
</div>
<div class="editor-label">
<label for="Edad">Edad</label>
<input type="text" name="Edad" />
</div>
<div class="editor-label">
<label for="Telefonos">Telf 1</label>
<input type="text" name="Telefonos" />
</div>
<div class="editor-label">
<label for="Telefonos">Telf 2</label>
<input type="text" name="Telefonos" />
</div>
<div class="editor-label">
<label for="Telefonos">Telf 3</label>
<input type="text" name="Telefonos" />
</div>
<input type="submit" />
</form>





Fijaos que hay 3 <input type=”text” name=”Telefono”>. Esto no es incorrecto, el parámetro name de un <input> puede estar repetido para indicar campos con más de un valor… Si introduzco datos y envío el formulario los campos POST de la request son:



Content-Type: application/x-www-form-urlencoded

Content-Length: 90

Nombre=Nombre&Edad=12&Telefonos=%2B34+555222&Telefonos=%2B34+666112&Telefonos=%2B34+777114



Honestamente… el navegador no es que haga gran cosa: simplemente tenemos 5 campos POST: Nombre, Edad y 3 campos Telefonos. Veamos ahora lo que ocurre en el lado del DefaultModelBinder.



El método dentro del DefaultModelBinder que obtiene el valor de una propiedad se llama GetPropertyValue y tiene la siguiente firma:




protected virtual object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)





De todos los parámetros que recibe este método nos interesan básicamente dos:




  1. bindingContext: Un objeto de la clase ModelBindingContext que contiene la información necesaria para poder realizar el binding: Proporciona acceso a los value providers, al ModelState (donde se guarda información relativa a la validación del modelo) y a los metadatos del modelo.


  2. propertyDescriptor: Un objeto de la clase PropertyDescriptor con información sobre la propiedad del modelo de la cual queremos obtener el valor (básicamente el tipo de la propiedad).



A partir de la información del bindingContext el DefaultModelBinder puede preguntar a los value providers que le den el valor del campo que contiene el valor de la propiedad (por defecto si estamos rellenando la propiedad Telefonos el DefaultModelBinder preguntará a los value providers por el valor de Telefonos). El DefaultModelBinder usará la información del propertyDescriptor para convertir el valor devuelto por los value providers al tipo del que sea la propiedad.



Luego el framework llama al método SetPropertyValue del propio model binder, que tiene esta firma:




protected virtual void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)





Los parámetros son más o menos los mismos que GetPropertyValue, con la salvedad de que recibimos también el objeto devuelto por GetPropertyValue. En este método “simplemente” asignamos el valor a la propiedad (podemos acceder al modelo a través del la propiedad Model del parámetro bindingContext).



Bueno, en el caso de la petición POST que nos ocupa esto es lo que devuelve el método GetPropertyValue de la propiedad Teléfonos:



image



El DefaultModelBinder, a partir de la información de los value providers, crea un array de cadenas con el valor de los tres teléfonos (recordad que la propiedad estava declarada como IEnumerable<string> en el modelo).



Hemos visto como a partir de una petición POST con varios campos “Telefonos” el DefaultModelBinder era capaz de enlazar esto a una variable IEnumerable<string> del modelo… Sigamos jugando un poco a ver que pasa… :)



Imaginad que hago la siguiente modificación en la vista:




<div class="editor-label">
<label for="Telefonos">Telf 1</label>
<input type="text" name="Telefonos[0]" />
</div>
<div class="editor-label">
<label for="Telefonos">Telf 2</label>
<input type="text" name="Telefonos[1]" />
</div>
<div class="editor-label">
<label for="Telefonos">Telf 3</label>
<input type="text" name="Telefonos[2]" />
</div>





Es decir, en lugar de tener tres campos cuyo name es Telefonos, tengo tres campos cuyo name es Telefonos[0], Telefonos[1] y Telefonos[2]. Si relleno los datos y envio la petición, ahora los datos POST tienen la forma:



Content-Type: application/x-www-form-urlencoded

Content-Length: 105

Nombre=eiximenis&Edad=20&Telefonos%5B0%5D=%2B34+111&Telefonos%5B1%5D=%2B34+222&Telefonos%5B2%5D=%2B34+333



Ahora en los datos POST tenemos (además de Nombre y Edad) tres campos más, llamados Telefonos%5B0%5D, Telefonos%5B1%5D, y Telefonos%5B2%5D (%5B es el código para [ y %5D es el código para ]).



Pues bien: eso funciona correctamente… el DefaultModelBinder en el GetPropertyValue para la propiedad Telefonos hace lo siguiente (es una simplificación de lo que ocurre realmente, pero es suficiente para entender el concepto):




  1. Pregunta a los value providers para el valor de Telefonos y el resultado es null (no hay ningún campo “Telefonos”)


  2. Como el DefaultModelBinder sabe que está enlazando una colección, pregunta a los value providers por el valor de Telefonos[0] y si existe lo añade a la colección de la propiedad Telefonos y pregunta por Telefonos[1]… y así sucesivamente.



No me puteéis el pobre DefaultModelBinder, que es bastante inteligente pero tampoco es dios… eso no funciona:




<div class="editor-label">
<label for="Telefonos">Telf 1</label>
<input type="text" name="Telefonos[1]" />
</div>
<div class="editor-label">
<label for="Telefonos">Telf 2</label>
<input type="text" name="Telefonos[2]" />
</div>





En este caso NO tenemos ni campo “Telefonos”, ni campo “Telefonos[0]" en la request, así que el DefaultModelBinder entenderá que la propiedad Telefonos no ha sido informado y la pondrá a null.



Objetos compuestos



Imaginad ahora que tenemos nuestro modelo definido del siguiente modo:




public class Persona
{
public IEnumerable<string> Telefonos { get; set; }
public string Nombre { get; set; }
public int Edad { get; set; }

public Direccion Direccion { get; set; }
}

public class Direccion
{
public string Calle { get; set; }
public string Ciudad { get; set; }
}





Como se las apaña el DefaultModelBinder para enlazar la propiedad Direccion que es un objeto completo??



Para ver como se las puede apañar el DefaultModelBinder, vamos a dejar que el propio framework nos ayude :) Par ello añado las siguientes líneas dentro del <form> de la vista:




<div class="editor-label">
<%: Html.LabelFor(x=>x.Direccion) %>
<%: Html.EditorFor(x=>x.Direccion) %>
</div>





Recordad que Html.LabelFor lo que hace es crear una <label> vinculada a la propiedad del modelo que se le pasa vía la expresión lambda. Por otro lado Html.EditorFor lo que hace es crear un “editor” para la propiedad del modelo que se le indica… El framework usa los metadatos del modelo para saber que tipo de editor es mejor (además de que nosotros podemos definir el tipo editor que queramos, pero eso es otra historia). Cuál creeis que es el “editor por defecto” para la propiedad Direccion del modelo, que es un objeto de la clase Direccion?



Si habéis respondido “dos textboxes, uno para la propiedad Calle y el otro para la propiedad Ciudad” habeis dado en el clavo… este es el código HTML que me genera la llamada a Html.EditorFor:




<div class="editor-field">
<input class="text-box single-line" id="Direccion_Calle" name="Direccion.Calle" type="text" value="" />
</div>
<div class="editor-label">
<label for="Direccion_Ciudad">Ciudad</label>
</div>
<div class="editor-field">
<input class="text-box single-line" id="Direccion_Ciudad" name="Direccion.Ciudad" type="text" value="" />
</div>





Exacto, dos <input type=”text”> uno para la propiedad Calle y el otro para la propiedad Ciudad… pero os habéis fijado en los name?




  • Direccion.Calle


  • Direccion.Ciudad



No vamos a entrar dentro del ciclo de vida entero del DefaultModelBinder (porque daría para otro post entero) y pienso que nos basta con esta idea: El DefaultModelBinder es capaz de procesar modelos complejos siempre y cuando los value providers tengan valores cuyo nombre sea propiedad.propiedad.propiedad…



Validación del modelo



El DefaultModelBinder no sólo crea objetos del modelo y los enlaza… también es el responsable de validar que el modelo cumpla con las restricciones que tiene. Las restricciones forman parte de los metadatos del modelo y por lo tanto están accesibles a través del parámetro bindingContext (propiedad ModelMetaData). El DefaultModelBinder comprueba que los valores de las propiedades satisfacen las restricciones que indican los metadatos. Si no es el caso, utiliza el ModelState (accesible a través del bindingContext) y añade un error (llamando al método AddModelError) para indicar que la propiedad determinada no cumple la restricción indicada.



El proveedor de metadatos para el modelo que usa ASP.NET MVC por defecto es DataAnnotations, pero nosotros podríamos crearnos nuestros propios proveedores de metadatos (sí, sí… ASP.NET MVC es sumamente extensible). Pero eso también ya es una historia para otro post… :)



Resumiendo…



El Model Binder es el encargado de crear un objeto del tipo correspondiente al que espera la acción de destino del controlador y de rellenarlo con los valores que provienen de la petición, aunque para ello no consulta los datos de la petición, sinó que consulta los datos de los value providers (que son quienes previamente han consultado la petición). Eso independiza al Model Binder de la forma en cómo los datos estén codificados en la petición. Finalmente el Model Binder también valida que el modelo cumpla las restricciones indicadas en los metadatos asociados.



Hemos visto por encima como funciona el DefaultModelBinder, el Model Binder que viene por defecto en ASP.NET MVC… Como (casi) todo en ASP.NET MVC podemos cambiar el DefaultModelBinder por otro a nuestro antojo, o lo que suele ser más normal, asociar un Model Binder específico a un tipo de modelo… Pero eso lo veremos en otro post… :)



Un saludo!



PD: Esto es un crosspost desde mi blog en geeks.ms… Pásate por allí mejor! :)

viernes, 7 de mayo de 2010

ASP.NET MVC: ValueProviders

Hola! Hoy quiero comentar un aspecto de ASP.NET MVC2 que no sé hasta que punto es conocido, y son los llamados Value Providers.

Disclaimer: Este post será largo y puede ser un poco denso y asumo conocimientos básicos de ASP.NET MVC. Tampoco tengas reparos en leerte este post en más de un dia si quieres… había pensado dividirlo en dos posts, pero al final he preferido meterlo todo en uno.

Si habéis trabajado un poco con ASP.NET MVC, sabréis que si tenéis un controlador con esta acción:

[HttpPost()]
public ActionResult Create(Producto p)
{
// Hacer algo con producto
return View();
}





ASP.NET MVC es capaz de hacer binding entre los parámetros de la Request y las propiedades de la clase Producto para instanciar un objeto Producto para tí y pasarlo al controlador.



Es decir, si la clase producto está definida como:




public class Producto
{
public int Codigo { get; set; }
public string Nombre { get; set; }
public int Precio { get; set; }
}





Y la Request tiene estos campos, ASP.NET MVC hace el binding por nosotros… P.ej. si usamos un formulario como el siguiente para enviar los datos:




<form action="/Productos/Create" method="post">
<fieldset>
<input id="Codigo" name="Codigo" type="text" value="" />
<input id="Nombre" name="Nombre" type="text" value="" />
<input id="Precio" name="Precio" type="text" value="" />
<input type="submit" value="Create" />
</fieldset>
</form>






La Request tiene los campos “Codigo”, “Nombre” y “Precios” (los name de los input). Al enviarse el formulario genera una Request con los siguientes datos POST (se pueden ver fácilmente usando firebug):



Content-Type: application/x-www-form-urlencoded

Content-Length: 31

Codigo=1&Nombre=Silla&Precio=12



La última línea es la que tiene los campos con los valores, que son usados por ASP.NET MVC para realizar el binding con la clase Producto.



El responsable de recoger los valores y realizar el binding con el modelo es el ModelBinder pero no es reponsabilidad del ModelBinder saber donde están los valores. P.ej. haced una prueba… Si al formulario anterior le quitáis uno de los campos (p.ej. el campo Precio) y modificáis el action del <form> para que quede <form action=”/Productos/Create?Precio=10” method=”post> y realizáis la petición veréis que sigue funcionando.



En este caso si miramos con firebug la petición, no tiene el parámetro post “Precio”:



Content-Type: application/x-www-form-urlencoded

Content-Length: 21

Codigo=1&Nombre=Silla



Pero si que dicho parámetro está en la URL, que ahora es /Productos/Create?Precio=10. Pero esto no afecta al ModelBinder que es capaz de recoger el parámeteo con independencia de dónde se encuentre dentro de la Request. ¿Y cómo es posible? Pues gracias a los Value Providers.



Pero antes sigamos jugando un poco, para tener todas las piezas encima de la mesa… que ocurre si sin modificar el atributo action del tag <form> volvemos a añadir el <input name=”Precio”> al formulario? Es decir tenemos un formulario con los tres campos (Codigo, Nombre y Precio) pero además tenemos el campo Precio otra vez en la url (?Precio=100). Pueden ocurrir tres cosas:




  1. Que ASP.NET MVC de error, porque el campo “Precio” está repetido (una vez en la URL y otra en los parámetros POST) de la petición.


  2. Que tenga prioridad el valor del parámetro en la URL


  3. Que tenga prioriodad el valor del parámetro POST



Bien, lo que ocurre es que tiene prioridad el valor del parámetro POST. Es decir el valor de la propiedad Precio del objeto Producto que reciba el controlador será el que haya entrado el usuario y no el que se indica en la URL. Pero… ¿por que?



Cuando una petición es tratada por el framework, primero se pasa por una serie de value providers que inspeccionan cada uno dicha petición y guardan los valores que cada uno de ellos entiende. P.ej. existe un value provider para inspeccionar los valores POST y otro distinto para inspeccionar los valores de la URL. ASP.NET MVC mantiene una colección de value providers y pasa la request por todos ellos.



P.ej. asumamos en nuestro caso que tenemos una colección con dos value providers (luego veremos que esto no es exactamente así, pero me vale esa simplificación por ahora). Llamemosle PostValueProvider al primero y UrlValueProvider al segundo. Supongamos que nos llega la petición que hemos comentado:




  • Parámetros en la URL: ?Precio=100


  • Parámetros POST: Codigo=1&Nombre=Silla&Precio=12



La request pasa por el primer value provider (supongamos que es el PostValueProvider) que la inspecciona, ve que hay tres parámetros llamados Código, Nombre y Precio y se los guarda con sus respectivos valores. Luego la request pasa por el siguiente value provider, el UrlValueProvider que inspecciona la request y ve que hay un parámetro llamado Precio y se lo guarda junto con su valor.



Ahora es el turno del ModelBinder: el ModelBinder detecta que debe crear un objeto Producto que tiene tres propiedades: Código, Nombre y Precio, así que pregunta a los value providers por los valores de estos campos. Esta frase es la clave: pregunta a los value providers, no a un value provider en particular. Pongamos que cuando el ModelBinder necesita el valor del campo “Precio” se limita a preguntar simplemente el valor de dicho campo, y el ModelBinder espera que haya un sólo valor para dicho campo. Si como es el caso dos value providers han guardado el valor para dicho campo, sólo uno responde… cual? Pues el que esté primero en la lista de value providers que ASP.NET MVC mantiene. Ni más ni menos :)



Creación de un Value Provider propio



Como ya sabréis ASP.NET MVC es muy extensible, así que obviamente uno se puede crear sus propios Value Providers, para permitir tratar peticiones que tengan datos codificados de forma extraña o en otros sitios donde pueden estar los datos (p.ej. en cookies). Crear un value provider es muy simple, basta crear una clase que implemente la interfaz IValueProvider. Este interfaz define dos métodos:




  1. ContainsPrefix –> No se como explicar fácilmente lo que significa exactamente sin entrar en demasiados detalles sobre como funciona el ModelBinder, así que permitidme una simplificación. Este método devuelve si el value provider tiene valor para un campo en concreto. Es decir si el value provider tiene un valor para el campo “Precio” entonces ContainsPrefix(“Precio”) debe devolver true. Insisto: No es tan simple, pero para entender el concepto del post nos basta así.


  2. GetValue –> Devuelve el valor que se le pide. Es decir GetValue(“Precio”) devuelve el valor correspondiente al campo “Precio” que tenga este Value Provider. Este método sólo se llama si ContainsPrefix devuelve true.



Vamos a ver como podemos implementar un ValueProvider propio… P.ej. vamos a implementar un Value Provider que lea valores de los <appSettings> del web.config (de acuerdo, quizá no es brutalmente útil, pero como ejemplo servirá).



El código podría ser el siguiente:




public class AppSettingsValueProvider : IValueProvider
{
private Dictionary<string, ValueProviderResult> values;


public AppSettingsValueProvider()
{
values = new Dictionary<string, ValueProviderResult>();
foreach (string key in ConfigurationManager.AppSettings.AllKeys)
{
string appSetting = ConfigurationManager.AppSettings[key];
values.Add(key, new ValueProviderResult(appSetting, appSetting, CultureInfo.InvariantCulture));
}
}

public bool ContainsPrefix(string prefix)
{
return values.ContainsKey(prefix);
}

public ValueProviderResult GetValue(string key)
{
ValueProviderResult value;
values.TryGetValue(key, out value);
return value;
}
}





En el constructor se inicializa el diccionario values con los valores leídos de los <appSettings> del web.config. El método GetValue de IValueProvider, no devuelve un object con el valor directo del campo pedido, sinó que devuelve un ValueProviderResult una clase que tiene tres campos (rawValue, attemptedValue y culture).  El campo rawValue es lo que realmente se ha leído de la petición, mientras que el campo attemptedValue es el valor de rawValue convertido a una string. En mi caso dado que <appSettings> contiene ya string, tanto rawValue como attemptedValue tienen el mismo valor.



Bien! Ya tenemos un value provider… ahora ha llegado el momento de decirle a ASP.NET MVC que lo use… y aquí es donde debemos explicar la simplificación que hice antes :)



Factorías de Value Providers



Os acordáis cuando dije que ASP.NET MVC mantenía una colección de value providers y que pasaba la request a cada uno de ellos para que pudiesen procesarla y guardarse los campos necesarios? Dije que esto no era exactamente así, sinó una simplificación… Pues bien, la verdad es que ASP.NET MVC no mantiene una colección de value providers, sinó una colección de factorías de value providers.



Si te preguntas porque una factoría en lugar de guardar directamente los value providers… es para darte más control sobre como se crean los value providers: un value provider actúa sobre los datos de la petición actúal, por lo que por cada petición deben crearse todos los value providers de nuevo… La interfaz IValueProvider no define ningún mecanismo para pasarle la Request al value provider. Tampoco tenemos acceso a ningún tipo de contexto ni nada parecido… Piensa, si ASP.NET MVC debe crear los value providers a cada petición, cómo le pasa los datos de la request?



La solución pasa por usar factorías de value providers es decir, clases cuya única responsabilidad es crear los value providers a cada petición.



Veamos como sería nuestra factoría que cree objetos de nuestro AppSettingsValueProvider:




public class AppSettingsValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{

return new AppSettingsValueProvider();
}
}





Basta con derivar de ValueProviderFactory y redefinir el método GetValueProvider. En este méotdo tenemos acceso al ControllerContext que a su vez nos da acceso a la request http. Ahora si queréis pasáis la request http (o lo que queráis) al value provider.



Una vez tenemos la factoría debemos decirle a ASP.NET MVC que la use. Eso se hace registrando nuestra factoría usando la clase estática ValueProviderFactories:




ValueProviderFactories.Factories.Add(new AppSettingsValueProviderFactory());





Usualmente esto se coloca en el Application_Start de Global.asax.



Fijaos en un detalle: La factoría se crea sólo una vez (y la creamos nosotros, no el framework) y luego para cada petición se llama al método GetValueProvider de la factoría… método que también hemos hecho nosotros y donde se devuelve el value provider. De esta manera controlamos la creación de los value providers (lo que nos permitiría usar, si quisiéramos, mecanismos de inyección de dependencias).



Y listos! Ya estamos. Si modificamos la vista de datos para que no tenga el campo “Precio” (ni como POST ni como GET) y añadimos en el web.config:




<appSettings>
<add key="Precio" value="999"/>
</appSettings>





Veréis que el controlador recibe un Producto con el nombre y código que hayáis indicado y un Precio de 999. Dado que nuestra factoría se ha añadido la última de la lista, si volvéis a poner el Precio en el formulario, tendrá prioridad el que haya entrado el usuario (ya que la factoría que crea el value provider con los datos POST está antes de nuestra factoría).



Os dejo un proyecto de demo. La vista inicial muestra tres enlaces: Formulario con los tres campos, formulario con dos campos pero precio en la url y formulario con dos campos. Al hacer submit del formulario se nos muestran los detalles del producto entrado. Veréis como en el último caso (formulario con dos campos) aparece el valor de precio 999 que es el que se ha sacado del web.config (en el primer caso tiene preferencia el valor entrado por el usuario y en el segundo el valor de la url).



Os podeis descargar el proyecto desde aquí (enlace a mi skydrive).



Espero que te haya quedado más o menos clara la idea de los value providers… en un post próximo hablaré de los ModelBinders para que podamos tener la foto completa!



Saludos!



PD: Esto es un crosspost desde mi blog de geeks.ms!