ASP.NET MVC Q&A: Cómo usar la sesión?

Hola a todos! Este es el primer post de la serie que “nace” a raíz de las preguntas que se me realizaron durante el Webcast de ASP.NET MVC que realizé el pasado 28 de Junio.

Una de las preguntas fue precisamente si se podia usar la sesión. La respuesta corta que di en el Webcast fue “sí: la sesión funciona exactamente igual que en Webforms y en mi opinión el sitio donde usarla es en los controladores”. Ahora viene la respuesta larga… 🙂

Acceder a la sesión desde un controlador es trivial:

public class HomeController : Controller
{
public ActionResult Index()
{
Session["data"] = "Datos en sesión";
return View();
}
}

Como se puede ver el controlador tiene acceso directo a la sesión a través de la propiedad Session declarada en la clase Controller.

Desde las vistas también tenemos acceso a la sesión:

<div>
Datos en sesión: <%: this.Session["data"] %>
</div>

1. NO accedas a la sesión desde las vistas

ASP.NET MVC expone un modelo extremadamente sencillo y potente: toda petición es enrutada a un controlador y el controlador hace lo que tenga que hacer para terminar pasándole un conjunto de datos a la vista (usando lo que se llama un ViewModel que no es nada más que una clase propia que encapsula los datos que la vista requiere). La vista accede a su ViewModel a través de la propiedad Model.

La vista debe ser agnóstica sobre donde proceden los datos: le basta saber que los tiene disponibles en su ViewModel. Deja que sea el controlador si es preciso quien acceda a la sesión, cree un ViewModel con los datos y se los pasa a la vista.

Otro sitio donde uno podría usar Session es en el Modelo, es decir dejar la lógica de si un dato debe almacenarse en sesión o no en el propio Modelo. De esta aproximación lo que mi no me convence es que termina atando nuestro modelo al objeto HttpSessionStateBase. Yo personalmente prefiero que todo acceso a sesión esté en los controladores, dados que es más natural que estos estén atados a “objetos típicos de http”.

2. ¿Para qué quieres la sesión?

Asegúrate que usas la sesión para lo que pertoca. En la sesión se guardan datos relativos a un usuario para un periodo de tiempo relativamente largo, generalmente hasta que el usuario se desconecta de nuestra aplicación. No hagas nunca esto:

public class HomeController : Controller
{
public ActionResult Index()
{
Session["data"] = "Datos en sesión";
return RedirectToAction("OtraAccion", "OtroControlador");
}
}
public class OtroControladorController : Controller
{
public ActionResult OtraAccion()
{
var datos = Session["data"] as string;
// hacemos algo con los datos
Session.Remove("data");
return View();
}
}

En este código el controlador Home pone datos en la sesión y luego redirige a la acción OtraAccion del controlador OtroControlador que recoge los datos de la sesión, hace algo con ellos (p.ej. crear un ViewModel), los elimina de la sesión y llama a su vista.

Aquí se está usando la sesión para compartir datos temporales entre controladores. Dado que este es un escenario relativamente habitual ASP.NET MVC proporciona un mecanismo propio para ello: el TempData.

El código anterior quedaría mejor con:

public class HomeController : Controller
{
public ActionResult Index()
{
TempData["data"] = "Datos temporales";
return RedirectToAction("OtraAccion", "OtroControlador");
}
}
public class OtroControladorController : Controller
{
public ActionResult OtraAccion()
{
var datos = TempData["data"] as string;
// hacemos algo con los datos
return View();
}
}

TempData funciona de un método algo raro: Cuando una propiedad de TempData es leída se marca para eliminación y dicha eliminación sucede al fin de la request. Eso significa que a efectos prácticos los datos de TempData son datos para leer una sola vez. Una buena solución pues para pasar datos temporales entre controladores. Y sí: un dato almacenado en TempData que nunca es consultado permanece entre requests. Esta capacidad permite dar soporte a escenarios con varias redirecciones, pero debemos andarnos con cuidado: si ponemos un valor en TempData que jamás es consultado, ahí se queda ocupando espacio… ¿Y espacio de donde? Pues si esto de que el dato permanece entre requests te suena mucho a “sesión”, no vas desencaminado: internamente TempData se guarda dentro de Session. Para más información sobre TempData léete este brutal post de José M. Aguilar donde lo cuenta mejor que yo.

3. Considera no usar sesión

Otro tema importante a tener en cuenta es evitar el uso de la sesión. La sesión lleva consiguo ciertas implicaciones. Tenemos tres modos de sesión en ASP.NET (y por lo tanto en ASP.NET MVC):

  • InProc: La sesión se guarda en el propio servidor web. No se comparte entre servidores web de una misma web farm, por lo que si tenemos una web farm debemos usar sticky sessions lo que limita la esacabilidad.
  • StateServer: La sesión se guarda en un único servidor. Se comparte entre diversos los servidores de la web farm, pero añade un único punto de fallo: si se cae el servidor de sesión, se cae toda nuestra aplicación.
  • SQLServer: La sesión se guarda en base de datos. Se comparte entre los servidores de la web farm, a costa de perder rendimiento y de perder parte de escalabilidad (siempre es más difícil escalar un SQL Server que añadir un servidor web adicional).

Así pues, ten presente el precio que pagas por usar la sesión. Con eso no digo que la sesión esté prohibida (ni mucho menos), simplemente que consideres si la necesitas de verdad.

4. El punto (3) te echa para atrás pero necesitas usar sesión?

Quieres usar la sesión, pero no estás satisfecho con ninguna de las tres alternativas que te proporciona por defecto ASP.NET? Ningún problema, puedes crearte tu propio proveedor de sesión que use algún mecanismo que satisfaga tus necesidades… Ya, no es tarea fácil verdad? Por suerte (o por desgracia) Microsoft no para de sacar productos, APIs y cosas nuevas y hay una muy interesante: Velocity.

Velocity (incluída dentro de lo que llama Windows Server AppFabric) es una cache distribuída. Lo bueno es que viene con un proveedor de sesión para ASP.NET. Por lo tanto, puedes utilizar Velocity para almacenar tu sesión! Esto implica que la sesión, al estar en la cache distribuída, se comparte entre los servidores web del webfarm sin ser un único punto de fallo (como StateServer) ni penalizar tanto el rendimiento (como SQLServer). Así pues, es una muy buena opción para usar como proveedor de sesión (no solo para ASP.NET MVC, también para ASP.NET tradicional).

En este post Stephen Walther tienes más información sobre como usar Velocity en ASP.NET MVC.

5. Y sobre unit testing?

Una de las ventajas de MVC respecto Webforms es la capacidad de probar nuestras aplicaciones usando unit testing. Generalmente lo que más nos interesa pobar son los controladores y el modelo, dado que las vistas no contienen ninguna lógica (tan solo presentación).

Si nuestros controladores usan la sesión, necesitan un HttpContext para funcionar, pero durante tus pruebas untarias no vas a tenerlo. Imagina que tengo este controlador:

public class HomeController : Controller
{
public ActionResult Index()
{
Session["data"] = "eiximenis";
var data = Session["data"] as string;
return View();
}
}

Y un test unitario para probar algo sobre dicha acción:

[TestMethod]
public void TestMethod1()
{
HomeController hc = new HomeController();
var result = hc.Index() as ViewResult;
Assert.AreEqual(string.Empty, result.ViewName);
}

Al ejecutar este UnitTest… patapaaaam! Nos va a lanzar una NullReferenceException, porque como no estamos ejecutando los tests bajo el motor de ASP.NET no existe Session ni nada parecido (por lo que el método Index de HomeController se encuentra un null cuando accede a la propiedad Session). ¿Y entonces? Pues debemos, de alguna manera u otra conseguir crear Mocks de dichos objetos.

Hay varias maneras de hacer esto, pero una de las más sencillas es usar el TestHelper de MvcContrib. Veamos como podemos testear este método usando MvcContrib. Para ello nos descargamos MvcContrib (para la versión de ASP.NET MVC que estés usando) y:

  1. Agregamos una referencia a MvcContrib.TestHelper.dll
  2. Agregamos otra referencia a Rhino.Mocks.dll. Nota: Técnicamente esta referencia no es necesaria que la agreguemos en nuestro proyecto. El requisito a cumplir es que Rhino.Mocks.dll esté disponible en tiempo de ejecución del test (MvcContrib.TestHelper.dll depende de ella). P. ej. otra forma de conseguir esto es agregar Rhino.Mocls.dll a la solución y usar [DeploymentItem].

Ahora el código del test queda así:

[TestMethod]
public void TestMethod1()
{
TestControllerBuilder tb = new TestControllerBuilder();
HomeController hc = tb.CreateController<HomeController>();
var result = hc.Index() as ViewResult;
Assert.AreEqual(string.Empty, result.ViewName);
}

Usamos la clase TestControllerBuilder que es capaz de crear controladores con todos los “objetos http” inicializados (con Mocks).

Los Mocks de MvcContrib no solo hacen que el código del controlador no de un error, también simulan la funcionalidad de la clase real. Por lo tanto podemos probar que el método Index guarda algo en la sesión:

[TestMethod]
public void TestMethod1()
{
TestControllerBuilder tb = new TestControllerBuilder();
HomeController hc = tb.CreateController<HomeController>();
hc.Index();
Assert.AreEqual("pepe", hc.HttpContext.Session["data"]);
}

¿Que os parece? MvcContrib es una manera elegante y fácil de poder probar nuestros controladores que tienen dependencias contra “objetos http” como la sesión.

Un saludo a todos!!! 😉

11 comentarios sobre “ASP.NET MVC Q&A: Cómo usar la sesión?”

  1. Genial Post, Eduard!

    Totalmente de acuerdo con lo de usar sesiones sólo cuando sea estrictamente necesario: es un recurso «caro».

    Y por supuesto, no hacerlo desde la Vista. Es una dependencia fácil de evitar y muy propensa a generar errores.

    En cuanto al Modelo, tampoco me gusta que acceda a la sesión o, en general, a información asociada al contexto Http. Cuando realmente hace falta algo así (por ejemplo, el típico escenario donde se requiere la obtención del usuario activo desde la lógica de negocio), siempre será mejor inyectar en el modelo un objeto desde el que éste pueda obtener los datos.

    Enhorabuena de nuevo por el post, y gracias por la referencia!

  2. Gracias por lo del TempData, no sabía donde lo guardaba!!! Yo lo uso para hacer una especie de multiview, como en mvc no existe creo mis propios step y en los tempdata guardo los datos del modelo q no voy a usar en el step actual, pero si para finalizar todos los step… medio loco, pero funciona!!!
    En mi caso lo estoy poniendo en la vista, y después lo recojo en el controler, es una buena práctica? O seria el mismo caso que esta al principio:
    Datos en sesión: < %: this.Session["data"] %>
    Gracias por todo!!!

  3. Hola Pablo,
    Yo no soy partidario de usar TempData para compartir datos entre vistas y controladores. En mi opinión TempData es bueno para compartir datos entre peticiones de controladores.
    Para pasar datos de una vista a un controlador tienes dos mecanismos ya construdídos dentro de http:
    1) Querystring: ya sabes los ?param=valor&param2=valor…
    2) Datos POST en el cuerpo de la petición

    En ambos casos el model binder de ASP.NET MVC es capaz de traducirte eso a parámetros en la acción del controlador.

    Un saludo!

Responder a etomas Cancelar respuesta

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