Nahezu jede ASP.NET-Anwendung muss die Daten für die Sitzung eines Benutzers verfolgen. ASP.NET stellt die HttpSessionState-Klasse zum Speichern von Sitzungsstatuswerten bereit. Auf eine Instanz der HttpSessionState-Klasse für jede HTTP-Anfrage kann in Ihrer gesamten Anwendung mithilfe der statischen HttpContext.Current.Session-Eigenschaft zugegriffen werden. Der Zugriff auf dieselbe Instanz wird auf jeder Seite und jedem UserControl mithilfe der Session-Eigenschaft der Seite oder des UserControl vereinfacht.
Die HttpSessionState-Klasse stellt eine Sammlung von Schlüssel/Wert-Paaren bereit, wobei die Schlüssel vom Typ String und die Werte vom Typ Object sind. Dies bedeutet, dass Session äußerst flexibel ist und Sie nahezu jede Art von Daten in Session speichern können.
Aber (es gibt immer ein Aber) diese Flexibilität ist nicht ohne Kosten. Der Preis ergibt sich aus der Leichtigkeit, mit der Fehler in Ihre Anwendung eingeführt werden können. Viele der Fehler, die eingeführt werden können, werden durch Unit-Tests nicht gefunden, und wahrscheinlich auch nicht durch irgendeine Form von strukturiertem Testen. Diese Fehler treten häufig erst auf, wenn die Anwendung in der Produktionsumgebung bereitgestellt wurde. Wenn sie auftauchen, ist es oft sehr schwierig, wenn nicht unmöglich, festzustellen, wie sie entstanden sind, und den Fehler zu reproduzieren. Das bedeutet, dass die Reparatur sehr teuer ist.
In diesem Artikel wird eine Strategie zur Vermeidung dieser Art von Fehlern vorgestellt. Es verwendet ein Entwurfsmuster namens „Fassade“, indem es die sehr freie Schnittstelle der HttpSessionState-Klasse (die die Anforderungen jeder Anwendung erfüllen kann) mit einer gut gestalteten und kontrollierten Schnittstelle umschließt, die speziell für eine bestimmte Anwendung entwickelt wurde. Wenn Sie mit Design Patterns oder dem Fassadenmuster nicht vertraut sind, finden Sie bei einer schnellen Internetsuche nach „Fassadendesignmuster“ zahlreiche Hintergrundinformationen. Sie müssen sich jedoch nicht mit Designmustern auskennen, um diesen Artikel zu verstehen.
Der in diesem Artikel gezeigte Beispielcode ist in C# geschrieben, die Konzepte sind jedoch auf jede .NET-Sprache anwendbar.
Was ist das Problem?
In diesem Abschnitt des Artikels werde ich die Probleme beim direkten Zugriff auf die HttpSessionState-Klasse ohne Fassade beschreiben. Ich werde die Arten von Fehlern beschreiben, die eingeführt werden können.
Im Folgenden wird der typische Code gezeigt, der für den Zugriff auf Sitzungsstatusvariablen geschrieben wird.
// Eine Sitzungsvariable speichern
Session["some string"] = anyOldObject;
// Eine Sitzungsvariable lesen
DateTime startDate = (DateTime)Session["StartDate"];
Die Probleme ergeben sich aus der flexiblen Schnittstelle von HttpSessionState: Die Schlüssel sind nur Zeichenfolgen und die Werte sind nicht stark typisiert.
Verwenden von String-Literalen als Schlüssel
Werden String-Literale als Schlüssel verwendet, wird der String-Wert des Schlüssels vom Compiler nicht überprüft. Es ist leicht, durch einfache Tippfehler neue Sitzungswerte zu erstellen.
Session["received"] = 27;...
Session["recieved"] = 32;
Im obigen Code wurden zwei separate Sitzungswerte gespeichert.
Die meisten Fehler wie dieser werden durch Unit-Tests identifiziert – aber nicht immer. Es ist möglicherweise nicht immer offensichtlich, dass sich der Wert nicht wie erwartet geändert hat.
Wir können diese Art von Fehler vermeiden, indem wir Konstanten verwenden:
private const stringempfangen = "received";...
Sitzung[empfangen] = 27;...
Sitzung[empfangen] = 32;
Keine Typprüfung
Es gibt keine Typprüfung der in Sitzungsvariablen gespeicherten Werte. Der Compiler kann die Richtigkeit dessen, was gespeichert wird, nicht überprüfen.
Betrachten Sie den folgenden Code:
Session["maxValue"] = 27;...
int maxValue = (int)Session["maxValue"];
An anderer Stelle wird der folgende Code verwendet, um den Wert zu aktualisieren.
Sitzung["maxValue"] = 56,7;
Wenn der Code zum Einlesen der Sitzungsvariablen „maxValue“ in die int-Variable maxValue erneut ausgeführt wird, wird eine InvalidCastException ausgelöst.
Die meisten Fehler wie dieser werden durch Unit-Tests identifiziert – aber nicht immer.
Unbeabsichtigte Wiederverwendung eines Schlüssels
Selbst wenn wir auf jeder Seite Konstanten für die Sitzungsschlüssel definieren, ist es möglich, dass auf allen Seiten unbeabsichtigt derselbe Schlüssel verwendet wird. Betrachten Sie das folgende Beispiel:
Code auf einer Seite:
private const string edit = "edit";...
Sitzung[Bearbeiten] = true;
Code auf einer zweiten Seite, angezeigt nach der ersten Seite:
private const string edit = "edit";...
if ((bool)Session[edit])
{
...
}
Code auf einer dritten, nicht verwandten Seite:
private const string edit = "edit";...
Sitzung[Bearbeiten] = false;
Wenn aus irgendeinem Grund die dritte Seite angezeigt wird, bevor die zweite Seite angezeigt wird, entspricht der Wert möglicherweise nicht dem erwarteten Wert. Der Code wird wahrscheinlich weiterhin ausgeführt, aber die Ergebnisse werden falsch sein.
Normalerweise wird dieser Fehler beim Testen NICHT aufgedeckt. Erst wenn ein Benutzer eine bestimmte Kombination aus Seitennavigation ausführt (oder ein neues Browserfenster öffnet), tritt der Fehler auf.
Im schlimmsten Fall ist sich niemand bewusst, dass der Fehler aufgetreten ist, und es kann sein, dass wir Daten auf einen unbeabsichtigten Wert ändern.
Unbeabsichtigte Wiederverwendung eines Schlüssels – schon wieder
Im obigen Beispiel wurde derselbe Datentyp in der Sitzungsvariablen gespeichert. Da es keine Typprüfung der gespeicherten Daten gibt, kann auch das Problem inkompatibler Datentypen auftreten.
Code auf einer Seite:
Session["FollowUp"] = "true";
Code auf einer zweiten Seite:
Session["FollowUp"] = 1;
Code auf einer dritten Seite:
Session["FollowUp"] = true;
Wenn der Fehler auftritt, wird eine InvalidCastException ausgelöst.
Normalerweise wird dieser Fehler beim Testen NICHT aufgedeckt. Erst wenn ein Benutzer eine bestimmte Kombination aus Seitennavigation ausführt (oder ein neues Browserfenster öffnet), tritt der Fehler auf.
Was können wir tun?
Die erste schnelle Lösung
Das Erste und einfachste, was wir tun können, ist sicherzustellen, dass wir niemals String-Literale für Sitzungsschlüssel verwenden. Verwenden Sie immer Konstanten und vermeiden Sie so einfache Tippfehler.
private const string limit = "limit";...
Sitzung[Limit] = 27;...
Sitzung[Limit] = 32;
Wenn jedoch Konstanten lokal definiert werden (z. B. auf Seitenebene), kann es sein, dass wir denselben Schlüssel unbeabsichtigt wiederverwenden.
Eine bessere schnelle Lösung
Anstatt Konstanten auf jeder Seite zu definieren, gruppieren Sie alle Sitzungsschlüsselkonstanten an einem einzigen Ort und stellen Sie eine Dokumentation bereit, die in Intellisense angezeigt wird. Aus der Dokumentation sollte klar hervorgehen, wofür die Sitzungsvariable verwendet wird. Definieren Sie beispielsweise eine Klasse nur für die Sitzungsschlüssel:
öffentliche statische Klasse SessionKeys
{
/// <Zusammenfassung>
/// Das Maximum ...
/// </summary>
public const string Limit = "limit";
}
...
Session[SessionKeys.Limit] = 27;
Wenn Sie eine neue Sitzungsvariable benötigen und einen Namen wählen, der bereits verwendet wurde, erfahren Sie dies, wenn Sie die Konstante zur SessionKeys-Klasse hinzufügen. Sie können sehen, wie es aktuell verwendet wird, und können entscheiden, ob Sie einen anderen Schlüssel verwenden sollten.
Wir gewährleisten jedoch immer noch nicht die Konsistenz des Datentyps.
Ein viel besserer Weg – die Nutzung einer Fassade
Greifen Sie nur innerhalb einer einzigen statischen Klasse in Ihrer Anwendung auf den HttpSessionState zu – der Fassade. Es darf kein direkter Zugriff auf die Session-Eigenschaft innerhalb des Codes auf Seiten oder Steuerelementen und kein direkter Zugriff auf HttpContext.Current.Session außer innerhalb der Fassade
erfolgen. Alle Sitzungsvariablen werden als Eigenschaften der Fassadenklasse verfügbar gemacht.
Dies hat die gleichen Vorteile wie die Verwendung einer einzigen Klasse für alle Sitzungsschlüssel und zusätzlich die folgenden Vorteile:
Starke Typisierung dessen, was in Sitzungsvariablen eingefügt wird.
Bei der Verwendung von Sitzungsvariablen ist keine Codeumwandlung erforderlich.
Alle Vorteile von Eigenschaftssettern zur Validierung dessen, was in Sitzungsvariablen eingegeben wird (mehr als nur Typ).
Alle Vorteile von Property Gettern beim Zugriff auf Sitzungsvariablen. Beispielsweise wird eine Variable beim ersten Zugriff initialisiert.
Ein Beispiel für eine Sitzungsfassadenklasse
Hier ist eine Beispielklasse zum Implementieren der Sitzungsfassade für eine Anwendung namens MyApplication.
Zusammenbruch
/// <Zusammenfassung>
/// MyApplicationSession stellt eine Fassade für das ASP.NET-Sitzungsobjekt bereit.
/// Der gesamte Zugriff auf Sitzungsvariablen muss über diese Klasse erfolgen.
/// </summary>
öffentliche statische Klasse MyApplicationSession
{
# Region Private Konstanten
//------------------------------------------------ ---------------------
private const string userAuthorisation = "UserAuthorisation";
private const string teamManagementState = "TeamManagementState";
private const string startDate = "StartDate";
private const string endDate = "EndDate";
//------------------------------------------------ ---------------------
# endregion
# öffentliche Eigenschaften der Region
//------------------------------------------------ ---------------------
/// <Zusammenfassung>
/// Der Benutzername ist der Domänenname und Benutzername des aktuellen Benutzers.
/// </summary>
öffentliche statische Zeichenfolge Benutzername
{
get { return HttpContext.Current.User.Identity.Name; }
}
/// <Zusammenfassung>
/// UserAuthorization enthält die Autorisierungsinformationen für
/// der aktuelle Benutzer.
/// </summary>
öffentliche statische UserAuthorization UserAuthorization
{
erhalten
{
UserAuthorization userAuth
= (UserAuthorization)HttpContext.Current.Session[userAuthorization];
// Prüfen, ob die UserAuthorisation abgelaufen ist
Wenn (
userAuth == null ||
(userAuth.Created.AddMinutes(
MyApplication.Settings.Caching.AuthorisationCache.CacheExpiryMinutes))
< DateTime.Now
)
{
userAuth = UserAuthorisation.GetUserAuthorisation(Benutzername);
UserAuthorisation = userAuth;
}
return userAuth;
}
privater Satz
{
HttpContext.Current.Session[userAuthorisation] = value;
}
}
/// <Zusammenfassung>
/// TeamManagementState wird verwendet, um den aktuellen Status des zu speichern
/// TeamManagement.aspx-Seite.
/// </summary>
öffentlicher statischer TeamManagementState TeamManagementState
{
erhalten
{
return (TeamManagementState)HttpContext.Current.Session[teamManagementState];
}
Satz
{
HttpContext.Current.Session[teamManagementState] = value;
}
}
/// <Zusammenfassung>
/// StartDate ist das früheste Datum, das zum Filtern von Datensätzen verwendet wird.
/// </summary>
öffentliches statisches DateTime StartDate
{
erhalten
{
if (HttpContext.Current.Session[startDate] == null)
return DateTime.MinValue;
anders
return (DateTime)HttpContext.Current.Session[startDate];
}
Satz
{
HttpContext.Current.Session[startDate] = value;
}
}
/// <Zusammenfassung>
/// EndDate ist das späteste Datum, das zum Filtern von Datensätzen verwendet wird.
/// </summary>
öffentliches statisches DateTime EndDate
{
erhalten
{
if (HttpContext.Current.Session[endDate] == null)
return DateTime.MaxValue;
anders
return (DateTime)HttpContext.Current.Session[endDate];
}
Satz
{
HttpContext.Current.Session[endDate] = value;
}
}
//------------------------------------------------ ---------------------
# Endregion
}
Die Klasse demonstriert die Verwendung von Eigenschafts-Gettern, die Standardwerte bereitstellen können, wenn ein Wert nicht explizit gespeichert wurde. Die StartDate-Eigenschaft stellt beispielsweise DateTime.MinValue als Standard bereit.
Der Property-Getter für die UserAuthorization-Eigenschaft stellt einen einfachen Cache der UserAuthorization-Klasseninstanz bereit und stellt so sicher, dass die Instanz in den Sitzungsvariablen auf dem neuesten Stand gehalten wird. Diese Eigenschaft zeigt auch die Verwendung eines privaten Setters, sodass der Wert in der Sitzungsvariablen nur unter der Kontrolle der Fassadenklasse festgelegt werden kann.
Die Eigenschaft „Benutzername“ stellt einen Wert dar, der möglicherweise einmal als Sitzungsvariable gespeichert wurde, aber nicht mehr auf diese Weise gespeichert wird.
Der folgende Code zeigt, wie über die Fassade auf eine Sitzungsvariable zugegriffen werden kann. Beachten Sie, dass in diesem Code kein Casting erforderlich ist.
// Eine Sitzungsvariable speichern
MyApplicationSession.StartDate = DateTime.Today.AddDays(-1);
// Eine Sitzungsvariable lesen
DateTime startDate = MyApplicationSession.StartDate;
Zusätzliche Vorteile
Ein zusätzlicher Vorteil des Fassadendesignmusters besteht darin, dass es die interne Implementierung vor dem Rest der Anwendung verbirgt. Vielleicht entscheiden Sie sich in Zukunft für die Verwendung eines anderen Mechanismus zur Implementierung des Sitzungsstatus als der integrierten ASP.NET-Klasse HttpSessionState. Sie müssen nur die Innenteile der Fassade ändern, sonst müssen Sie an der restlichen Anwendung nichts ändern.
Zusammenfassung
Die Verwendung einer Fassade für HttpSessionState bietet eine wesentlich robustere Möglichkeit, auf Sitzungsvariablen zuzugreifen. Dies ist eine sehr einfach zu implementierende Technik, die jedoch große Vorteile bietet.