miércoles, 19 de enero de 2011

ASP.NET MVC: Como recuperar un dato de una cookie para cada petición… Una alternativa ¿igual?

Muy buenas! Hace algunos días escribí el post ASP.NET MVC: Como recuperar datos de una cookie en cada petición, donde mostraba el uso de un route handler propio para recuperar los datos de una cookie y colocarlos en el Route Data. En el ejemplo era una cookie de cultura de la aplicación, pero se puede aplicar a lo que queráis.

Lo que más me gusta de ASP.NET MVC es que muy expandible, que muchas cosas pueden hacerse de más de una forma. Pues bien, una de las novedades más interesantes de MVC3 (al margen de Razor) son los action filters globales.

En este post os propongo una solución alternativa (aunque ya veremos que tiene una ligerísima diferencia) al mismo problema. La diferencia es que no se debe alterar la tabla de rutas para nada. Y dicha solución pasa por usar un action filter global.

Una de las cosas que en MVC nos debe quedar claro es que cuando repitamos muchas veces un mismo código de un controlador debemos considerar de ponerlo en un Action Filter. El “problema” está que los action filters deben aplicarse controlador a controlador (o acción a acción). Si tenemos un filtro que debe aplicarse a todos los controladores podemos considerar crear una clase base que lo tenga y heredar todos los controladores de ella…

… o al menos eso era así antes de MVC3.

Con MVC3 y los filtros globales podemos aplicar un filtro a todas las acciones de todos los controladores. Y todo ello con una sola línea en global.asax. Es brutal!

El filtro global…

Lo bueno es que los filtros globales se implementan igual que los filtros no globales clásicos que teníamos en MVC2. En este caso la implementación es super sencilla:

public class CookieCultureFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var cultureCookieVal = GetCultureFromCookie(filterContext.HttpContext.Request.Cookies);
var culture = new CultureInfo(cultureCookieVal);
filterContext.RouteData.Values.Add("culture", cultureCookieVal);
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
}

private string GetCultureFromCookie(HttpCookieCollection cookies)
{
var retValue = "ca-ES";
if (cookies.AllKeys.Contains("userculture"))
{
retValue = cookies["userculture"].Value;
}
return retValue;
}
}





El código es trivial: derivo de ActionFilterAttribute (clase que ya existía) y redefino el método OnActionExecuting que se ejecuta antes de ejecutar la acción del controlador. En este método tengo el código para leer la cookie de cultura (exactamente lo mismo que tenía antes en el Route Handler).



Activar el filtro global



Os dije que era una sóla línea en global.asax, verdad? Pues concretamente esta:




GlobalFilters.Filters.Add(new CookieCultureFilterAttribute());





Otra opción es colocar esa línea dentro de la función RegisterGlobalFilters que crea el VS2010, aunque entonces no es necesario usar la clase GlobalFilters (usad en su lugar el parámetro filters):




filters.Add(new CookieCultureFilterAttribute());





Y ya está. Nada más. Antes de cada acción de cualquier controlador se ejecutará el código de nuestro filtro global. No es necesario modificar la tabla de rutas para añadir nuestro route handler.



¿Puedo usar el filtro en una aplicación MVC2?



Si, si que puedes, pero entonces debes:




  1. Aplicarlo en cada controlador (usando [CookieCultureFilter] antes de cada acción o cada controlador que quieras que use la cookie).


  2. Derivar todos tus controladores de un controlador base que tenga [CookieCultureFilter] aplicado.



Y lo más importante… ¿Ambas soluciones son equivalentes?



Pues NO. Ambas soluciones no son equivalentes… En el caso del post anterior, si recordáis, si declaraba un parámetro culture en una acción, recibía el valor de la cookie, ya que el route handler me añadía este parámetro en el RouteData. Pues bien, eso dejará de funcionar. Es decir, en este caso la acción:




public ActionResult Foo(string culture)
{
}





que en el post anterior recibía el valor de la cookie en culture, con esa nueva aproximación siempre recibirá null.



¿Y por que si mi filtro también añade en el RouteData el valor de la cookie? Pues muy sencillo: el Model Binder (que se encarga de hacer binding a los parámetros de las acciones) se ejecuta antes que el filtro. Simplificando, el flujo de ejecuciones sería:




  1. Route Handler


  2. Model Binder


  3. Action Filters


  4. Acción del controlador



En este caso, estamos añadiendo un valor en el RouteData después de que el Model Binder haya actuado. Por eso el parámetro no tendrá el valor. Eso no quita que desde el controlador lo podáis consultar (tenéis acceso al RouteData).



Y con esto termino… espero que el post os haya ayudado un poco más a entender como funciona MVC y ver distintas alternativas de cómo hacer las cosas!



PD: Eso es otroooooooooooooooooo crosspost desde mi blog de geeks.ms

No hay comentarios: