Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 4 de febrero de 2014
ASP.NET MVCEn ASP.NET MVC, TempData es un mecanismo de lo más socorrido cuando se pretende hacer persistir información entre distintas peticiones de forma sencilla.
Hace ya bastante tiempo hablamos por aquí de cómo funciona, aunque resumidamente podríamos decir que se trata de un almacén temporal en el que podemos guardar datos desde una acción y consultarlos desde otra, aunque sea en peticiones distintas; la información se quedará almacenada hasta que sea obtenida por primera vez, momento en el que será eliminada del repositorio utilizado.

El principal problema que encontramos al utilizarlo en proyectos reales es que la información se guarda en variables de sesión, lo que en determinados escenarios puede ser muy costoso o simplemente imposible, y esto hace que descartemos su uso y que tengamos que inventar alternativas para solucionar problemas que usando TempData ya tendríamos resueltos.

Sin embargo, el hecho de que se utilicen variables de sesión, como casi todo en el framework, es simplemente un comportamiento por defecto que puede ser reemplazado por otro. Podríamos guardar la información de TempData en una base de datos, archivos de texto o cualquier otro lugar donde nos interese hacerlo :-)

Y lo que vamos a ver en este post es una implementación muy sencilla para hacer persistir esta información en cookies. Así, cuando desde el servidor establezcamos un valor en el TempData, lo que haremos es enviárselo en una cookie al cliente, donde persistirá hasta que sea leído. Obviamente no se puede usar para guardar datos confidenciales ni nada parecido, ni tampoco es interesante para gran cantidad de datos puesto que éstos viajarán entre cliente y servidor hasta ser eliminados, pero puede ser útil en escenarios simples y creo que ilustra bastante bien el funcionamiento interno de esta interesante característica de ASP.NET MVC.

TempData Providers

Todos los controladores tienen una propiedad llamada TempDataProvider, que es la encargada de suministrar el componente que se encargará de guardar y recuperar información. En su interior se encuentra un objeto que implementa el interfaz ITempDataProvider, cuya definición vemos a continuación:
public interface ITempDataProvider
{
   IDictionary<string, object> LoadTempData(ControllerContext controllerContext);
   void SaveTempData(ControllerContext controllerContext, 
                     IDictionary<string, object> values);
  }
Cuando el framework carga el controlador, antes de invocar la acción correspondiente llamará al método TempDataProvider.LoadTempData() para obtener la información salvada desde el almacén correspondiente y cargarla en el diccionario TempData.

De la misma forma, al finalizar el proceso de la acción, ASP.NET MVC ejecutará el método TempDataProvider.SaveTempData() para volcar la información almacenada en el diccionario  TempData al sistema de persistencia elegido.

Decíamos antes que por defecto se utilizan variables de sesión, ¿verdad? Pues esto se debe a que internamente en TempDataProvider encontraremos inicialmente una instancia de SessionStateTempDataProvider, cuya implementación lo único que hace es utilizar variables de sesión para almacenar y obtener la información.

Para modificar este comportamiento lo único que tenemos que hacer es crear una clase que implemente ese sencillo interfaz, e introducir un objeto de este nuevo tipo en la propiedad TempDataProvider de los controladores. Fácil, ¿eh?

Un TempDataProvider para cookies

El siguiente código muestra la implementación de nuestro flamante CookieTempDataProvider. Observad que aprovechamos el método SaveTempData() para serializar en JSON el contenido del diccionario TempData, y el método LoadTempData() para realizar la operación inversa:
public class CookieTempDataProvider : ITempDataProvider
{
    private const string CookieName = "_TempData_";

    public IDictionary<string, object> LoadTempData(
                     ControllerContext controllerContext)
    {
        var cookie = controllerContext.HttpContext.Request.Cookies.Get(CookieName);
        if (cookie != null)
        {
            return Newtonsoft.Json.JsonConvert
                    .DeserializeObject<Dictionary<string, object>>(cookie.Value);
        }
        return null;
    }

    public void SaveTempData(ControllerContext controllerContext, 
                             IDictionary<string, object> values)
    {
        if (values != null && values.Any())
        {
            var serializedData = Newtonsoft.Json.JsonConvert.SerializeObject(values);
            var cookie = new HttpCookie(CookieName, serializedData);
            controllerContext.HttpContext.Response.Cookies.Add(cookie);
        }
        else
        {
            var cookie = controllerContext.HttpContext.Request.Cookies[CookieName];
            if (cookie != null)
            {
                cookie.Expires = DateTime.Now.AddDays(-1);
                controllerContext.HttpContext.Response.Cookies.Set(cookie);
            }
        }
    }
}
El código de SaveTempData() es un poco más extenso porque estamos incluyendo la lógica para eliminar la cookie cuando ya no sea necesaria, es decir, cuando no haya información almacenada en TempData.

Por supuesto, podríamos habernos parado un poco más en la creación de la cookie y definirla más detalladamente, por ejemplo estableciéndole la caducidad, visibilidad, etc., pero no es ese el objetivo en este momento…

Modificar el proveedor de TempData por defecto

Ya lo único que nos queda es introducir este nuevo proveedor en el pipeline de ASP.NET MVC. Esto tenemos varias formas de hacerlo, vamos a ver dos de ellas.

Una forma muy sencilla es establecer el TempDataProvider desde los propios controladores, o bien desde una de sus clases base para no tener que repetir demasiado ese código. En este caso, podemos aprovechar que para instanciar estos proveedores el framework invoca al método virtual CreateTempData() del controlador; basta con sobrescribirlo y tenemos ya todo el trabajo hecho:
public class BaseController: Controller
{
    protected override ITempDataProvider CreateTempDataProvider()
    {
        return new CookieTempDataProvider();
    }
}

public class HomeController : BaseController
{
   // We are now using the CookieTempDataProvider
   ...
}
Aún más fácil resulta si estamos utilizando un contenedor de inversión de control como Unity o Ninject. En este caso, bastará con asociar el interfaz ITempDataProvider a nuestra clase y todo saldrá funcionando como por arte de magia, puesto que el Dependency Resolver es utilizado de forma interna para crear el proveedor.

El siguiente código sería el registro usando Unity:
container.RegisterType<ITempDataProvider, CookieTempDataProvider>();
Con cualquiera de estas dos técnicas ASP.NET MVC pasaría a utilizar cookies para persistir la información del TempData. Si en lugar de cookies preferís otro sitio simplemente habría que crear otro proveedor e implementar los métodos LoadTempData() y SaveTempData() para tenerlo resuelto.

¿Sencillo, eh?

Publicado en: Variable not found.

6 Comentarios:

Sergio León dijo...

Hola Jose,

Muy bueno, un punto de extensibilidad más en MVC, estoy encantando! :-)

Por si sirve, cuando después quieras recuperar un objeto desde TempData (esto es si no has guardado un tipo simple) tendrás que hacer algo como ((JObject)miObjeto).ToObject();

Un saludo.

josé M. Aguilar dijo...

Muchas gracias por el apunte, Sergio :)

Unknown dijo...

Hola José, excelente artículo, es importante conocer el funcionamiento interno del FrameWork para lograr este tipo de cosas y verle una utilidad, me parece demasiado útil poder personalizar la persistencia del TempData para temas de performance.

Gracias y saludos!

Unknown dijo...

Estimados, no me funciona :(

https://onedrive.live.com/redir?resid=4E598B0C22FAEB34!792&authkey=!AGBy0i98WZEMkgk&v=3&ithint=photo%2c.JPG

Sergio León dijo...

Hola Jose,

Llevo algún tiempo utilizando tu solución y me encontrado con un problema, el típico que te das cuenta al rato :)

Resulta que (al menos en Chrome) si guardo caracteres no ASCII estándar (p. ej: Operación) me guarda en vez de eso Operación, puedes verlo mejor aquí http://stackoverflow.com/a/1969339

La solución en mi caso ha pasado por codificar en Base64 y luego llamar a HttpUtility.UrlEncode (por el tema de que Base64 puede acabar en == a veces). Lógicamente de vuelta la operación inversa.

Ahora sí puedo guardar lo que quiera en TempData basado en cookies :)

Un saludo.

josé M. Aguilar dijo...

Hola, Sergio!

Muy bueno, muchas gracias por compartirlo :)

Saludos!