Casi todas las aplicaciones ASP.NET necesitan realizar un seguimiento de los datos de la sesión de un usuario. ASP.NET proporciona la clase HttpSessionState para almacenar valores de estado de sesión. Se puede acceder a una instancia de la clase HttpSessionState para cada solicitud HTTP en toda la aplicación mediante la propiedad estática HttpContext.Current.Session. El acceso a la misma instancia se simplifica en cada página y control de usuario utilizando la propiedad Sesión de la página o control de usuario.
La clase HttpSessionState proporciona una colección de pares clave/valor, donde las claves son de tipo Cadena y los valores son de tipo Objeto. Esto significa que Session es extremadamente flexible y puede almacenar casi cualquier tipo de datos en Session.
Pero (siempre hay un pero) esta flexibilidad tiene un coste. El costo es la facilidad con la que se pueden introducir errores en su aplicación. Muchos de los errores que se pueden introducir no se encontrarán mediante pruebas unitarias y probablemente tampoco mediante ninguna forma de prueba estructurada. Estos errores a menudo solo aparecen cuando la aplicación se ha implementado en el entorno de producción. Cuando aparecen, a menudo es muy difícil, si no imposible, determinar cómo ocurrieron y poder reproducir el error. Esto significa que su reparación es muy costosa.
Este artículo presenta una estrategia para ayudar a prevenir este tipo de error. Utiliza un patrón de diseño llamado Fachada, en el sentido de que envuelve la interfaz muy gratuita proporcionada por la clase HttpSessionState (que puede cumplir con los requisitos de cualquier aplicación) con una interfaz bien diseñada y controlada que está diseñada específicamente para una aplicación específica. Si no está familiarizado con los patrones de diseño o el patrón de fachada, una búsqueda rápida en Internet de "patrón de diseño de fachada" le proporcionará muchos antecedentes. Sin embargo, no es necesario comprender los patrones de diseño para comprender este artículo.
El código de ejemplo que se muestra en este artículo está escrito en C#, pero los conceptos son aplicables a cualquier lenguaje .NET.
¿Cuál es el problema?
En esta sección del artículo describiré los problemas con el acceso directo a la clase HttpSessionState, sin fachada. Describiré los tipos de errores que se pueden introducir.
A continuación se muestra el código típico escrito para acceder a variables de estado de sesión.
//Guardar una variable de sesión
Sesión["alguna cadena"] = cualquierOldObject;
// Leer una variable de sesión
FechaHora fechaInicio = (FechaHora)Sesión["FechaInicio"];
Los problemas surgen de la interfaz flexible proporcionada por HttpSessionState: las claves son solo cadenas y los valores no están fuertemente tipados.
Usar literales de cadena como claves
Si se utilizan literales de cadena como claves, el compilador no verifica el valor de cadena de la clave. Es fácil crear nuevos valores de sesión mediante simples errores tipográficos.
Sesión["recibida"] = 27;...
Sesión["recibida"] = 32;
En el código anterior, se guardaron dos valores de sesión separados.
La mayoría de errores como este se identificarán mediante pruebas unitarias, pero no siempre. Puede que no siempre sea evidente que el valor no ha cambiado como se esperaba.
Podemos evitar este tipo de error usando constantes:
privado const cadena recibida = "recibida";...
Sesión[recibida] = 27;...
Sesión[recibida] = 32;
Sin verificación de tipo
No hay verificación de tipo de los valores que se almacenan en las variables de sesión. El compilador no puede comprobar la exactitud de lo que se está almacenando.
Considere el siguiente código:
Session["maxValue"] = 27;...
int maxValue = (int)Sesión["maxValue"];
En otros lugares se utiliza el siguiente código para actualizar el valor.
Sesión["maxValue"] = 56,7;
Si el código para leer la variable de sesión "maxValue" en la variable int maxValue se ejecuta nuevamente, se generará una excepción InvalidCastException.
La mayoría de errores como este se identificarán mediante pruebas unitarias, pero no siempre.
Reutilizar una clave sin querer
Incluso cuando definimos constantes en cada página para las claves de sesión, es posible utilizar involuntariamente la misma clave en todas las páginas. Considere el siguiente ejemplo:
Código en una página:
cadena const privada editar = "editar";...
Sesión[editar] = verdadero;
Código en una segunda página, que se muestra después de la primera página:
private const string edit = "edit";...
if ((bool)Sesión[editar])
{
...
}
Código en una tercera página no relacionada:
private const string edit = "edit";...
Sesión[editar] = falso;
Si por algún motivo se muestra la tercera página antes de que se muestre la segunda página, es posible que el valor no sea el esperado. El código probablemente seguirá ejecutándose, pero los resultados serán incorrectos.
Por lo general, este error NO se detectará durante las pruebas. Sólo cuando un usuario realiza alguna combinación particular de navegación por la página (o abre una nueva ventana del navegador) se manifiesta el error.
En el peor de los casos, nadie se da cuenta de que el error se ha manifestado y es posible que terminemos modificando los datos a un valor no deseado.
Reutilizar una clave sin querer - otra vez
En el ejemplo anterior, se almacenó el mismo tipo de datos en la variable de sesión. Debido a que no existe una verificación de tipos de lo que se almacena, también puede ocurrir el problema de tipos de datos incompatibles.
Código en una página:
Session["FollowUp"] = "true";
Código en una segunda página:
Session["FollowUp"] = 1;
Código en una tercera página:
Session["FollowUp"] = true;
Cuando el error se manifieste, se lanzará una excepción InvalidCastException.
Por lo general, este error NO se detectará durante las pruebas. Sólo cuando un usuario realiza alguna combinación particular de navegación por la página (o abre una nueva ventana del navegador) se manifiesta el error.
¿Qué podemos hacer?
La primera solución rápida
Lo primero y más sencillo que podemos hacer es asegurarnos de no utilizar nunca cadenas literales para las claves de sesión. Utilice siempre constantes y así evite simples errores tipográficos.
límite de cadena constante privada = "límite";...
Sesión[límite] = 27;...
Sesión[límite] = 32;
Sin embargo, cuando las constantes se definen localmente (por ejemplo, a nivel de página), es posible que aún reutilicemos la misma clave sin querer.
Una mejor solución rápida
En lugar de definir constantes en cada página, agrupe todas las constantes clave de sesión en una única ubicación y proporcione la documentación que aparecerá en Intellisense. La documentación debe indicar claramente para qué se utiliza la variable de sesión. Por ejemplo, defina una clase solo para las claves de sesión:
clase pública estática SessionKeys
{
/// <resumen>
/// El máximo...
/// </summary>
cadena constante pública Límite = "límite";
}
...
Sesión[SessionKeys.Limit] = 27;
Cuando necesite una nueva variable de sesión, si elige un nombre que ya se ha utilizado, lo sabrá cuando agregue la constante a la clase SessionKeys. Puede ver cómo se está utilizando actualmente y determinar si debería utilizar una clave diferente.
Sin embargo, todavía no garantizamos la coherencia del tipo de datos.
Una manera mucho mejor: usar una fachada
Acceda únicamente a HttpSessionState desde una única clase estática en su aplicación: la fachada. No debe haber acceso directo a la propiedad Session desde el código de las páginas o controles, ni acceso directo a HttpContext.Current.Session que no sea desde la fachada
. Todas las variables de sesión se expondrán como propiedades de la clase de fachada.
Esto tiene las mismas ventajas que usar una sola clase para todas las claves de sesión, además de las siguientes ventajas:
Escritura sólida de lo que se coloca en las variables de sesión.
No es necesario convertir código donde se utilizan variables de sesión.
Todos los beneficios de los establecedores de propiedades para validar lo que se coloca en las variables de sesión (más que solo escribir).
Todos los beneficios de los captadores de propiedades al acceder a variables de sesión. Por ejemplo, inicializar una variable la primera vez que se accede a ella.
Una clase de fachada de sesión de ejemplo
A continuación se muestra una clase de ejemplo para implementar la fachada de sesión para una aplicación llamada MyApplication.
Colapsar
/// <resumen>
/// MyApplicationSession proporciona una fachada para el objeto de sesión ASP.NET.
/// Todo acceso a las variables de sesión debe realizarse a través de esta clase.
/// </summary>
clase estática pública MyApplicationSession
{
# región Constantes privadas
//------------------------------------------------ ---------------------
cadena const privada userAuthorisation = "UserAuthorisation";
cadena constante privada teamManagementState = "TeamManagementState";
cadena constante privada fecha de inicio = "Fecha de inicio";
cadena constante privada fecha final = "Fecha final";
//------------------------------------------------ ---------------------
# endregion
# región Propiedades públicas
//------------------------------------------------ ---------------------
/// <resumen>
/// El nombre de usuario es el nombre de dominio y el nombre de usuario del usuario actual.
/// </summary>
cadena estática pública Nombre de usuario
{
get { return HttpContext.Current.User.Identity.Name; }
}
/// <resumen>
/// UserAuthorisation contiene la información de autorización para
/// el usuario actual.
/// </summary>
Autorización de usuario estática pública Autorización de usuario
{
conseguir
{
Autorización de usuario userAuth
= (Autorización de usuario)HttpContext.Current.Session[Autorización de usuario];
// Comprobar si la autorización de usuario ha caducado
si (
autenticación de usuario == nulo ||
(userAuth.Created.AddMinutes(
MyApplication.Settings.Caching.AuthorisationCache.CacheExpiryMinutes))
< FechaHora.Ahora
)
{
userAuth = UserAuthorisation.GetUserAuthorisation(Nombre de usuario);
Autorización de usuario = autenticación de usuario;
}
devolver autenticación de usuario;
}
conjunto privado
{
HttpContext.Current.Session[userAuthorisation] = valor;
}
}
/// <resumen>
/// TeamManagementState se utiliza para almacenar el estado actual del
/// Página TeamManagement.aspx.
/// </summary>
público estático TeamManagementState TeamManagementState
{
conseguir
{
return (TeamManagementState)HttpContext.Current.Session[teamManagementState];
}
colocar
{
HttpContext.Current.Session[teamManagementState] = valor;
}
}
/// <resumen>
/// StartDate es la fecha más antigua utilizada para filtrar registros.
/// </summary>
Fecha y hora estática pública Fecha de inicio
{
conseguir
{
si (HttpContext.Current.Session[fecha de inicio] == nulo)
devolver FechaHora.MinValue;
demás
return (FechaHora)HttpContext.Current.Session[fechainicio];
}
colocar
{
HttpContext.Current.Session[fecha de inicio] = valor;
}
}
/// <resumen>
/// EndDate es la última fecha utilizada para filtrar registros.
/// </summary>
Fecha y hora estática pública Fecha de finalización
{
conseguir
{
si (HttpContext.Current.Session[fecha final] == nulo)
devolver FechaHora.MaxValue;
demás
return (FechaHora)HttpContext.Current.Session[fechafinal];
}
colocar
{
HttpContext.Current.Session[endDate] = valor;
}
}
//------------------------------------------------ ---------------------
# región final
}
La clase demuestra el uso de captadores de propiedades que pueden proporcionar valores predeterminados si un valor no se ha almacenado explícitamente. Por ejemplo, la propiedad StartDate proporciona DateTime.MinValue de forma predeterminada.
El captador de propiedad para la propiedad UserAuthorisation proporciona un caché simple de la instancia de la clase UserAuthorisation, asegurando que la instancia en las variables de sesión se mantenga actualizada. Esta propiedad también muestra el uso de un configurador privado, de modo que el valor en la variable de sesión solo se puede establecer bajo el control de la clase de fachada.
La propiedad Nombre de usuario demuestra un valor que alguna vez pudo haberse almacenado como una variable de sesión pero que ya no se almacena de esta manera.
El siguiente código muestra cómo se puede acceder a una variable de sesión a través de la fachada. Tenga en cuenta que no es necesario realizar ninguna conversión en este código.
//Guardar una variable de sesión
MyApplicationSession.StartDate = DateTime.Today.AddDays(-1);
// Leer una variable de sesión
DateTime startDate = MyApplicationSession.StartDate;
Beneficios adicionales
Un beneficio adicional del patrón de diseño de fachada es que oculta la implementación interna del resto de la aplicación. Quizás en el futuro decida utilizar otro mecanismo para implementar el estado de sesión, distinto de la clase integrada ASP.NET HttpSessionState. Sólo necesita cambiar el interior de la fachada; no necesita cambiar nada más en el resto de la aplicación.
Resumen
El uso de una fachada para HttpSessionState proporciona una forma mucho más sólida de acceder a las variables de sesión. Esta es una técnica muy sencilla de implementar, pero con un gran beneficio.