Presque toutes les applications ASP.NET doivent conserver une trace des données de la session d'un utilisateur. ASP.NET fournit la classe HttpSessionState pour stocker les valeurs d'état de session. Une instance de la classe HttpSessionState pour chaque requête HTTP est accessible dans toute votre application à l'aide de la propriété statique HttpContext.Current.Session. L'accès à la même instance est simplifié sur chaque Page et UserControl à l'aide de la propriété Session de Page ou UserControl.
La classe HttpSessionState fournit une collection de paires clé/valeur, où les clés sont de type String et les valeurs sont de type Object. Cela signifie que Session est extrêmement flexible et que vous pouvez stocker à peu près n'importe quel type de données dans Session.
Mais (il y a toujours un mais) cette flexibilité n’est pas sans coût. Le coût est la facilité avec laquelle les bogues peuvent être introduits dans votre application. La plupart des bogues pouvant être introduits ne seront pas détectés par les tests unitaires, et probablement pas par aucune forme de test structuré. Ces bugs n'apparaissent souvent que lorsque l'application a été déployée dans l'environnement de production. Lorsqu’ils font surface, il est souvent très difficile, voire impossible, de déterminer comment ils se sont produits et de pouvoir reproduire le bug. Cela signifie qu’ils coûtent très cher à réparer.
Cet article présente une stratégie pour aider à prévenir ce type de bug. Il utilise un modèle de conception appelé Facade, dans le sens où il enveloppe l'interface très gratuite fournie par la classe HttpSessionState (qui peut répondre aux exigences de n'importe quelle application) avec une interface bien conçue et contrôlée, spécialement conçue pour une application spécifique. Si vous n'êtes pas familier avec les modèles de conception ou le modèle de façade, une recherche rapide sur Internet de « modèle de conception de façade » vous fournira de nombreuses informations. Cependant, vous n’avez pas besoin de comprendre les modèles de conception pour comprendre cet article.
L'exemple de code présenté dans cet article est écrit en C#, mais les concepts sont applicables à n'importe quel langage .NET.
Quel est le problème ?
Dans cette section de l'article, je décrirai les problèmes d'accès direct à la classe HttpSessionState, sans façade. Je décrirai les types de bugs qui peuvent être introduits.
Ce qui suit montre le code typique écrit pour accéder aux variables d'état de session.
// Enregistre une variable de session
Session["une chaîne"] = anyOldObject;
// Lire une variable de session
DateHeure startDate = (DateTime)Session["StartDate"];
Les problèmes proviennent de l'interface flexible fournie par HttpSessionState : les clés ne sont que des chaînes et les valeurs ne sont pas fortement typées.
Utiliser des littéraux de chaîne comme clés
Si des chaînes littérales sont utilisées comme clés, la valeur de chaîne de la clé n'est pas vérifiée par le compilateur. Il est facile de créer de nouvelles valeurs de session par de simples erreurs de frappe.
Session["reçu"] = 27;...
Session["reçu"] = 32 ;
Dans le code ci-dessus, deux valeurs de session distinctes ont été enregistrées.
La plupart des bugs comme celui-ci seront identifiés par des tests unitaires – mais pas toujours. Il n’est pas toujours évident que la valeur n’a pas changé comme prévu.
On peut éviter ce genre de bug en utilisant des constantes :
private const string reçu = "reçu";...
Session[reçue] = 27 ;...
Session[reçue] = 32 ;
Aucune vérification de type
Il n'y a pas de vérification de type des valeurs stockées dans les variables de session. Le compilateur ne peut pas vérifier l'exactitude de ce qui est stocké.
Considérez le code suivant :
Session["maxValue"] = 27;...
int maxValue = (int)Session["maxValue"];
Ailleurs, le code suivant est utilisé pour mettre à jour la valeur.
Session["valeurmax"] = 56,7 ;
Si le code permettant de lire la variable de session "maxValue" dans la variable maxValue int est à nouveau exécuté, une InvalidCastException sera levée.
La plupart des bugs comme celui-ci seront identifiés par des tests unitaires – mais pas toujours.
Réutiliser une clé involontairement
Même lorsque nous définissons des constantes sur chaque page pour les clés de session, il est possible d'utiliser involontairement la même clé sur plusieurs pages. Prenons l'exemple suivant :
Code sur une page :
private const string edit = "edit";...
Session[modifier] = vrai ;
Code sur une deuxième page, affiché après la première page :
private const string edit = "edit";...
si ((bool)Session[modifier])
{
...
}
Code sur une troisième page sans rapport :
private const string edit = "edit";...
Session[modifier] = faux ;
Si la troisième page s'affiche pour une raison quelconque avant l'affichage de la deuxième page, la valeur peut ne pas être celle attendue. Le code continuera probablement à s'exécuter, mais les résultats seront erronés.
Habituellement, ce bug ne sera PAS détecté lors des tests. Ce n'est que lorsqu'un utilisateur effectue une combinaison particulière de navigation sur une page (ou ouvre une nouvelle fenêtre de navigateur) que le bug se manifeste.
Dans le pire des cas, personne n'est conscient que le bug s'est manifesté, nous pourrions simplement finir par modifier les données à une valeur involontaire.
Réutiliser une clé involontairement - encore une fois
Dans l'exemple ci-dessus, le même type de données a été stocké dans la variable de session. Puisqu’il n’y a pas de vérification de type de ce qui est stocké, le problème des types de données incompatibles peut également survenir.
Code sur une page :
Session["FollowUp"] = "true" ;
Code sur une deuxième page :
Session["FollowUp"] = 1;
Code sur une troisième page :
Session["FollowUp"] = true;
Lorsque le bogue se manifeste, une InvalidCastException sera levée.
Habituellement, ce bug ne sera PAS détecté lors des tests. Ce n'est que lorsqu'un utilisateur effectue une combinaison particulière de navigation sur une page (ou ouvre une nouvelle fenêtre de navigateur) que le bug se manifeste.
Que pouvons-nous faire ?
La première solution rapide
La première et la plus simple chose que nous pouvons faire est de nous assurer que nous n'utilisons jamais de chaînes littérales pour les clés de session. Utilisez toujours des constantes et évitez ainsi les simples erreurs de frappe.
limite de chaîne privée const = "limite";...
Session[limite] = 27 ;...
Session[limite] = 32 ;
Cependant, lorsque les constantes sont définies localement (par exemple au niveau de la page), nous pouvons toujours réutiliser la même clé involontairement.
Une meilleure solution rapide
Plutôt que de définir des constantes sur chaque page, regroupez toutes les constantes clés de session en un seul emplacement et fournissez la documentation qui apparaîtra dans Intellisense. La documentation doit clairement indiquer à quoi sert la variable de session. Par exemple, définissez une classe uniquement pour les clés de session :
classe statique publique SessionKeys
{
/// <résumé>
/// Le maximum...
/// </summary>
public const string Limit = "limite";
}
...
Session[SessionKeys.Limit] = 27 ;
Lorsque vous avez besoin d'une nouvelle variable de session, si vous choisissez un nom qui a déjà été utilisé, vous le saurez lorsque vous ajouterez la constante à la classe SessionKeys. Vous pouvez voir comment il est actuellement utilisé et déterminer si vous devez utiliser une clé différente.
Cependant, nous ne garantissons toujours pas la cohérence du type de données.
Une bien meilleure façon : utiliser une façade
Accédez uniquement à HttpSessionState à partir d’une seule classe statique de votre application : la façade. Il ne doit y avoir aucun accès direct à la propriété Session depuis le code des pages ou des contrôles, et aucun accès direct à HttpContext.Current.Session autre que depuis la façade.
Toutes les variables de session seront exposées en tant que propriétés de la classe de façade.
Cela présente les mêmes avantages que l'utilisation d'une seule classe pour toutes les clés de session, plus les avantages suivants :
Typage fort de ce qui est mis dans les variables de session.
Pas besoin de transtyper du code où les variables de session sont utilisées.
Tous les avantages des définisseurs de propriétés pour valider ce qui est mis dans les variables de session (plus qu'un simple type).
Tous les avantages des getters de propriétés lors de l'accès aux variables de session. Par exemple, initialiser une variable lors du premier accès à celle-ci.
Un exemple de classe de façade de session
Voici un exemple de classe pour implémenter la façade Session pour une application appelée MyApplication.
Effondrement
/// <résumé>
/// MyApplicationSession fournit une façade à l'objet Session ASP.NET.
/// Tous les accès aux variables de session doivent se faire via cette classe.
/// </summary>
classe statique publique MyApplicationSession
{
# région Constantes privées
//------------------------------------------------ ---------------------
chaîne const privée userAuthorisation = "UserAuthorisation";
chaîne const privée teamManagementState = "TeamManagementState";
chaîne const privée startDate = "StartDate" ;
chaîne const privée endDate = "EndDate" ;
//------------------------------------------------ ---------------------
#endregion
#region Propriétés publiques
//------------------------------------------------ ---------------------
/// <résumé>
/// Le nom d'utilisateur est le nom de domaine et le nom d'utilisateur de l'utilisateur actuel.
/// </summary>
chaîne statique publique
{
get { return HttpContext.Current.User.Identity.Name; }
}
/// <résumé>
/// UserAuthorisation contient les informations d'autorisation pour
/// l'utilisateur actuel.
/// </summary>
public static UserAuthorisation UserAuthorisation
{
obtenir
{
UserAuthorisation userAuth
= (UserAuthorisation)HttpContext.Current.Session[userAuthorisation];
// Vérifiez si l'autorisation utilisateur a expiré
si (
utilisateurAuth == null ||
(userAuth.Created.AddMinutes(
MyApplication.Settings.Caching.AuthorisationCache.CacheExpiryMinutes))
< DateHeure.Maintenant
)
{
userAuth = UserAuthorisation.GetUserAuthorisation(Username);
UserAuthorisation = userAuth;
}
return userAuth ;
}
ensemble privé
{
HttpContext.Current.Session[userAuthorisation] = valeur ;
}
}
/// <résumé>
/// TeamManagementState est utilisé pour stocker l'état actuel du
/// Page TeamManagement.aspx.
/// </summary>
public statique TeamManagementState TeamManagementState
{
obtenir
{
return (TeamManagementState)HttpContext.Current.Session[teamManagementState];
}
ensemble
{
HttpContext.Current.Session[teamManagementState] = valeur ;
}
}
/// <résumé>
/// StartDate est la première date utilisée pour filtrer les enregistrements.
/// </summary>
public statique DateTime StartDate
{
obtenir
{
si (HttpContext.Current.Session[startDate] == null)
renvoyer DateTime.MinValue ;
autre
return (DateTime)HttpContext.Current.Session[startDate];
}
ensemble
{
HttpContext.Current.Session[startDate] = valeur ;
}
}
/// <résumé>
/// EndDate est la dernière date utilisée pour filtrer les enregistrements.
/// </summary>
public statique DateTime EndDate
{
obtenir
{
si (HttpContext.Current.Session[endDate] == null)
renvoyer DateTime.MaxValue ;
autre
return (DateTime)HttpContext.Current.Session[endDate];
}
ensemble
{
HttpContext.Current.Session[endDate] = valeur ;
}
}
//------------------------------------------------ ---------------------
# région finale
}
La classe démontre l'utilisation de getters de propriétés qui peuvent fournir des valeurs par défaut si une valeur n'a pas été explicitement stockée. Par exemple, la propriété StartDate fournit DateTime.MinValue par défaut.
Le getter de propriété pour la propriété UserAuthorisation fournit un cache simple de l'instance de la classe UserAuthorisation, garantissant que l'instance dans les variables de session est maintenue à jour. Cette propriété montre également l'utilisation d'un setter privé, de sorte que la valeur dans la variable de session ne peut être définie que sous le contrôle de la classe de façade.
La propriété Username montre une valeur qui a peut-être été stockée autrefois en tant que variable de session, mais qui n'est plus stockée de cette façon.
Le code suivant montre comment accéder à une variable de session via la façade. Notez qu’il n’est pas nécessaire d’effectuer un casting dans ce code.
// Enregistre une variable de session
MyApplicationSession.StartDate = DateTime.Today.AddDays(-1);
// Lire une variable de session
DateTime startDate = MyApplicationSession.StartDate ;
Avantages supplémentaires
Un avantage supplémentaire du modèle de conception de façade est qu'il masque l'implémentation interne du reste de l'application. Peut-être qu'à l'avenir, vous déciderez d'utiliser un autre mécanisme d'implémentation de l'état de session, autre que la classe ASP.NET HttpSessionState intégrée. Il vous suffit de modifier les éléments internes de la façade - vous n'avez rien d'autre à modifier dans le reste de l'application.
Résumé
L'utilisation d'une façade pour HttpSessionState fournit un moyen beaucoup plus robuste d'accéder aux variables de session. Il s’agit d’une technique très simple à mettre en œuvre, mais très bénéfique.