ASP.NET MVC: Binding de datos de sesión a controladores

Muy buenas! Que tal el fin de año? Empachados con turrones, polvorones y demás? En fin, vamos a inaugurar el 2011 y que mejor manera que hacerlo que con un post! 😉

En realidad hubiese querido que este post fuese el último del año anterior, pero no puede publicarlo antes por problemas logísticos. La idea del post surge de un tweet que publicó Luis Ruiz Pavón. Su pregunta era que tal acceder a la sesión desde un Model Binder para poner datos a disposición de los controladores. Mi respuesta fue que yo usaría un value provider, y así llegamos a este post.

Para conseguir binding de los datos de la sesión a los parámetros de un controlador no es necesario crear ningún Model Binder. En MVC2 se introdujo un concepto nuevo (del que ya he hablado varias veces por aquí) que se llama ValueProvider y que es el encargado de acceder donde están los datos y ponerlos a disposición de los Model Binders. Si ignoramos los value providers y hacemos un model binder que acceda a la sesión, entonces realmente nuestro model binder hace dos cosas:

  1. Ir donde están los datos (la sesión) y recogerlos
  2. Enlazar los datos con los parámetros de los controladores

Según la arquitectura de ASP.NET MVC los model binders sólo se encargan de lo segundo, y son los value providers quienes se encargan de lo primero. Así, pues, tened presente la regla:

  1. Si lo que queréis canviar es cómo se enlazan los datos (vengan de donde vengan) a los controladores: cread un model binder
  2. Si lo que queréis es modificar de dónde se obtienen los datos que se enlazan a los controladores: usad un value provider.

En nuestro caso, tal y como se enlazan los valores a los controladores ya nos va bien (el DefaultModelBinder es realmente bueno en su tarea), sólo que queremos que si un dato está en la sesión se coja de allí: necesitamos un value provider nuevo.

Factoría de value providers

En ASP.NET MVC los value providers se crean siempre mediante una factoría y lo que realmente registramos en el runtime son esas factorías. En cada petición ASP.NET MVC le pide a las distintas factorías que creen los value providers necesarios.

Así pues lo primero va a ser crear nuestra factoría, que devolverá objetos de nuestro value provider vinculado a sesión:

class SessionValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
return new SessionValueProvider(controllerContext.HttpContext.Session);
}
}

Simplemente debemos derivar de ValueProviderFactory y en el método GetValueProvider devolver una instancia de nuestro value provider. En mi caso devuelvo una instancia del SessionValueProvider y le paso la sesión en el constructor.

Debemos registrar esa factoría de value providers en el runtime de ASP.NET MVC. Para ello en el Global.asax basta con meter la siguiente línea (usualmente en el Application_Start):

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

El value provider

Crear un value provider es “tan sencillo” como implementar la interfaz IValueProvider. De todos modos debemos conocer un poco como funcionan los model binders a los que debemos proporcionar los valores.

Los value providers vienen a ser como un “diccionario enorme” que los model binders consultan cuando quieren obtener un dato. Debemos saber cómo (“con que clave”) nos va a pedir el model binder los datos y como debemos dárselos. En un post mío de hace tiempo ya comenté como funciona el DefaultModelBinder, y allí cuento también la interacción entre el DefaultModelBiner y los value providers.

En fin, que todo ese rollete es para comentaros que muchas veces en lugar de implementar IValueProvider desde cero, es mucho mejor derivar de alguna de las clases que ya hay hechas, y hay una en concreto que nos viene al pelo: DictionaryValueProvider<TValue>. Esta clase implementa un value provider cuya fuente de datos es un diccionario cuyos valores son de tipo TValue. Y que es la sesión en el fondo sinó un gran diccionario cuyos valores son de tipo object?

Así pues creamos nuestra clase que derive de DictionaryValueProvider y lo único que tenemos que hacer es pasarle a nuestra clase base el diccionario que debe usar, que construimos a partir de la sesión:

class SessionValueProvider : DictionaryValueProvider<object>
{
public SessionValueProvider(HttpSessionStateBase session)
: base(CreateDictionary(session), CultureInfo.CurrentCulture)
{

}

private static IDictionary<string, object> CreateDictionary(HttpSessionStateBase session)
{
var entries = session.Keys.Cast<string>().ToDictionary(key => key, key => session[key]);
return entries;
}
}

Trivial no? El método CreateDictionary simplemente crea un IDictionary a partir de los datos de la sesión.

Y listos! Hemos terminado, No necesitamos hacer nada más para que el binding de objetos de sesión funcione. El requisito para que el binding se efectúe es el de siempre: que el nombre del parámetro en la acción del controlador tenga el mismo nombre que la clave de sesión:

public class SessionController : Controller
{
public ActionResult Put()
{
Session["ufo"] = "String en sessión";
Session["complex"] = new Foo()
{
Cadena = "Una cadena en objeto complejo",
Entero = 100,
Lista = new List<int>() {1, 1, 3, 5, 8, 13}
};
return View();
}
public ActionResult Get(string ufo)
{
ViewData["data"] = ufo;
return View();
}

public ActionResult GetClass(Foo complex)
{
return View(complex);
}
}

En este controlador la acción Put coloca dos datos en la sesión: una cadena con clave “ufo” y un objeto de una clase llamada Foo, con clave “complex”. Las dos acciones restantes (Get y GetClass) usan el binding y obtienen los datos de la sesión.

Ventajas de usar el binding para obtener los valores

La ventaja principal de usar el binding para obtener los valores en lugar de acceder a la sesión directamente es que desacopla el código de las acciones de la sesión de HTTP. En definitiva, si quiero probar si la acción Get funciona correctamente, p.ej. usando un unit test, me basta con pasarle una cadena cualquiera. Si en el código de la acción accediese a la sesión debería tener acceso a la sesión (desde la prueba) y rellenarla.

Espero que os sea útil!

Un saludo!

PD: Teneis un proyecto en VS2010 con MVC2 con el código de dicho post en mi skydrive: http://cid-6521c259e9b1bec6.office.live.com/self.aspx/BurbujasNet/ZipsPosts/MvcSessionBinding.zip

Deja un comentario

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