ValueProviders en ASP.NET MVC

Voy a comentarles en esta oportunidad sobre una característica de ASP.NET MVC 2 que son los ValueProviders. La documentación en MSDN es bastante pobre en este caso, pero los dejo el link para quienes quieran investigar un poco más.

Antes de comenzar recomiendo el post de Eduard Tomàs i Avellana del cual me inspiré para escribir este post.

Para hacer más fácil la explicación vamos a trabajar con un ejemplo, para ello vamos a crear una aplicación web ASP.NET MVC 2 y en primer lugar vamos a definir la clase Cliente de la siguiente manera:

public class Cliente
{
    public int Id { get; set; }
    public string Nombre { get; set; }
    public string Email { get; set; }
    public int Saldo { get; set; }
}

Seguido a esto vamos a crear un controlador, en este ejemplo lo llamaremos ClienteController y vamos a definir la acción Create (podemos utilizar la que nos crea por default Visual Studio):

[HttpPost]
public ActionResult Create(Cliente cliente)
{
    return View();
}

Por último vamos a definir la vista de la siguiente forma:

<h2>Create</h2>
<% using (Html.BeginForm()) {%>
    Nombre: <%= Html.TextBoxFor(model => model.Nombre) %>
    E-mail: <%= Html.TextBoxFor(model => model.Email)%>
    Saldo: <%= Html.TextBoxFor(model => model.Saldo) %>
    <input type="submit" value="Create" />
<% } %>

Ahora detengámonos un poco en este punto, y analicemos lo que pasa cuando hacemos un submit del formulario. Al clickear sobre el botón Create, MVC envía el formulario en una request generando los siguientes datos:

Datos de la request

Datos de la request

Podemos ver claramente que los datos que cargamos del cliente en el formulario viajan como parámetros POST, y los mismos son identificados a partir del atributo name de los controles que los contienen.  Ahora es ASP.NET MVC el responsable de hacer el binding de esos campos POST con las propiedades de nuestra clase Cliente, y de esta manera instanciar un nuevo cliente que será enviado a nuestro controlador. Para ser más precisos, quien se encarga de generar el cliente a partir de los valores de la petición es el ModelBinder (en otro post hablaremos de él) quien a su vez hace uso de los Value Providers.

Los ValueProviders son los encargados de  inspeccionar y obtener los valores de la petición y “guardarlos” hasta que sean solicitados por el ModelBinder.

Pero vayamos por parte, veamos una gráfica para que se entienda un poco mejor este proceso:

ModelBinder

ModelBinder

A grandes rasgos el proceso es el siguiente, el ModelBinder es el encargado de hacer el binding entre los valores de la request al objeto que corresponda – en nuestro ejemplo un Cliente – para luego enviárselo al controlador. Estos valores los deberá obtener de una lista de ValueProviders los cuales tienen guardarlos la información de los campos recuperados en la petición (el acceso a estos valores no es de forma directa, sino que cada ValueProvider tendrá una factoría que será la encargada de proporcionarlos al MoldelBinder, veremos esto con más detalle más adelante).

Vale aclarar que no existe un único ValueProvider, sino que pueden existir diferentes tipos, por ejemplo algunos que inspecciones los valores de los parametros POST, otros los valores de los parámetros de la URL, otros los valores de las cooquies… Además es importante tener en cuenta que el ModelBinder irá buscando los valores de cada campo que necesite por orden, y a medida que los vaya encontrando, dejará de buscarlos en los siguiente value providers (por ejemplo, si el campo Saldo esta presente en los values provider que inspeccionan los campos POST y también en el value provider que inspecciona los parámetros de la URL, el ModelBinder solamente se quedará con uno de ellos, y este valor será del primer value provider que inspeccionó).

Ahora vamos a lo interesante, crear nuestro propio value provider!. Para ello solo debemos crear y extender nuestra clase DefaultValueProvider de IValueProvider. Este tendrá como finalidad obtener un valor default para el parámetro Saldo leyéndolo desde el archivo de configuración web.config utilizando nuestra propia clase personalizada para poder accederlos:

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

    public DefaultValueProvider()
    {
        values = new Dictionary<string, ValueProviderResult>();

        var configuracion = (ValoresDefaultConfigurationSection)ConfigurationManager.GetSection("valoresDefault");
        int saldo = configuracion.Saldo;

        values.Add("Saldo", new ValueProviderResult(saldo, saldo.ToString(), 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;
    }
}

Lo primero que podemos observar es que de IValueProvider vamos a implementar los siguientes  métodos:

ConstainsPrefix: el cual nos permite saber si existe algun valor para el campo pedido.
GetValue: retorna el valor solicitado para una clave.

En el constructor de la clase, vamos a leer el valor del saldo que tenemos definido en nuestro web.config y una vez obtenido lo agregaremos a nuestro diccionario de valores, poniéndole como clave el nombre del campo y como valor un objeto del tipo ValueProviderResult el cual tiene como parámetros: el valor en sí, el valor expresado como string y por último el tipo de cultura (este objeto es el que le retornamos a ModelBinder cuando solicita el valor de un campo, como verán no es un simple valor).

Finalizado nuestro value provider deberemos crear su factory, ya que como dijimos anteriormente, el ModelBinder no accede directamente a los valores, sino que lo hace por medio de sus factories (esto entre otras cosas permite hacer más extensible nuestro código).

Las factories tendrán la responsabilidad de devolver los valores en cada petición:

public class DefaultValueProviderFactory : ValueProviderFactory
{
    public override IValueProvider GetValueProvider(ControllerContext controllerContext)
    {
        return new DefaultValueProvider();
    }
}

Es bastante simple su construcción, deberemos heredar de ValueProviderFactory y sobreescribir el método GetValueProvider, que el que vamos a utilizar para retornar nuestro DefaultValueProvider. Es interensante aclarar que desde la factory tenemos acceso al ControllerContext, por lo tanto podríamos pasarselo al constructor del ValueProvider para obterner datos del contexto actual (por ejemplo valores de la request, de las cooquies…).

Finalmente le avisamos a ASP.NET MVC que tenga presente nuestra factory, y para eso en el archivo Global.asax registramos la misma:

protected void Application_Start()
{
    ...
    ValueProviderFactories.Factories.Add(new DefaultValueProviderFactory());
 }

Ahora, solo resta eliminar del formulario el campo Saldo, para que no sea recuperado por las factories anteriores a la nuestra (es decir, las que obtienen los valores de los parámetros POST en este caso). Ahora al hacer el submit del formulario veremos que el valor de Saldo del cliente será el que hayamos definido en nuestro archivo de configuración:

Campo saldo recuperado por nuestro Value Provider

Campo saldo recuperado por nuestro Value Provider

Próximamente vamos a ver un poco más en profundidad el ModelBinder.

Configuraciones personalizadas en .NET

Estuve leyendo el post de Cristian Prieto y me parecio más que interesante comentarles sobre las configuraciones en .NET y como personalizarlas. Lo primero que debemos hacer es crear nuestra clase de “configuración” y que herede de ConfigurationSection, esto nos va a permitir tener un acceso directo a secciones en particular de nuestro archivo de configuración por medio de clases.

Veamos la definición de que nos proporciona MSDN sobre ConfigurationSection

La clase ConfigurationSection se utiliza para implementar un tipo de sección personalizado. Extienda la clase ConfigurationSection para proporcionar control personalizado y acceso mediante programación a las secciones de configuración personalizadas.

Ahora veamos un ejemplo simple, supongamos que tenemos en nuestro archivo de configuración una sección en particular para las configuraciones generales del sitio, por lo tanto vamos a crear nuestra clase GeneralConfigurationSection que nos permitirá tener acceso a dichas configuraciones:

public class GeneralConfigurationSection : ConfigurationSection
{
    [ConfigurationProperty("titulo")]
    public string Titulo
    {
        get { return this["titulo"].ToString(); }
        set { this["titulo"] = value; }
    }

    [ConfigurationProperty("cantidadMaximoUsuarios")]
    public int CantidadMaximoUsuarios
    {
        get { return (int)this["cantidadMaximoUsuarios"]; }
        set { this["cantidadMaximoUsuarios"] = value; }
    }
}

En el post de Cristian también explica que puede decorarse las ConfigurationProperty con otros atributos, así que les recomiendo que le peguen una mirada.

Lo siguiente será configurar dichas secciones en nuestro web.config, cada una de las secciones registran su tipo de control con una entrada en configSections:

<configSections>
    <section name="general" type="GeneralConfigurationSection, Sebis.UI.Config" />
</configSections>
<general titulo="Mi sitio web" cantidadMaximoUsuarios="100"/>

Ahora simplemente en nuestro código podremos acceder a las mismas de la siguiente manera:

var configuracion = (GeneralConfigurationSection)ConfigurationManager.GetSection("general");
string titulo = configuracion.Titulo;

Sencillo, no?