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!

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

  1. Estupendo post, Eduard!

    Como solución, para mi gusto bastante más limpia que la anterior ;-), y una buena prueba de la flexibilidad del framework.

    Sobre el último punto, el poder recuperar como parámetro de la acción la cultura actual, efectivamente, cuando lo añadimos a la información de ruta ya es tarde para el binding.

    Pero si esto fuera un requisito indispensable, se podría hacer, por ejemplo utilizando la información que llega en el filterContext, que, entre otras cosas, nos da acceso a la lista de parámetros de la acción, y que podemos manipular a nuestro antojo.

    Por ejemplo, podríamos añadir al método OnActionExecuting() un código, más o menos, como este:

    if (filterContext.ActionParameters.ContainsKey(“culture”))
    filterContext.ActionParameters[“culture”]=culture;

    O en otras palabras, si el método de acción define un parámetro “culture” (por ejemplo), le inyectamos el valor desde aquí.

    Saludos!

  2. Buenas!!! 😉

    Pues a mi, el “feeling” que me da esa solución también me hace sentir mucho mejor: creo también que es más limpia y que “cada cosa se usa para lo que toca”…

    Y sobre lo que dices de inyectar el parámetro a la acción, me parece una aproximación realmente interesante! No había caído en ella!! 😛

    Muchas gracias!!!!!

Deja un comentario

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