Artikel

August 2002 | Artikel

Keine Krümel

(Link zum Artikel: http://www.it-republik.de/dotnet/artikel/0212)

Session-Management mit ASP.NET

Text: von Christian Wenz und Tobias Hauser
  • Teilen
  • kommentieren
  • empfehlen
  • Bookmark and Share
HTTP ist ein statusloses Protokoll, es können also keine Informationen zwischengespeichert werden. Bei eCommerce-Anwendungen ist dies jedoch unerlässlich, muss die Webapplikation doch die einzelnen Nutzer unterscheiden, um den Inhalt ihrer virtuellen Warenkörbe speichern zu können. Es gibt hier zwei Ansätze, die wir in dieser und in der nächsten Ausgabe behandeln werden. Wir beginnen mit den ASP.NET-Sessions.

Unter dem Begriff Session versteht man weitgehend eine Sitzung. Der Anglizismus wird mittlerweile in den verschiedensten Zusammenhängen verwendet. Musiker berichten von einer Albumsproduktion, die in mehreren Sessions durchgeführt worden ist, die Vortragsprogramme bei Konferenzen werden ebenfalls in einzelne Sessions aufgeteilt. Wenn im Webbereich von Sessions die Rede ist, ist damit der Besuch eines Benutzers auf einer Website gemeint. So ein Besuch besteht ja bei Interesse des Benutzers aus mehreren aufeinander folgenden Seitenabrufen. Bei verschiedensten Anwendungen besteht Interesse daran, Informationen während einer solchen Sitzung zwischenzuspeichern. Dies kann eine Liste der Waren sein, die der Benutzer bestellen will oder einfach nur ein anonymer eindeutiger Identifikator, um seinen Weg verfolgen und auswerten zu können.
Unter ASP war das Session-Management noch nicht wirklich konkurrenzfähig; insbesondere der Zwang, Cookies zu verwenden (die der Benutzer im Browser abschalten konnte), verbot den Einsatz bei professionellen Anwendungen. Bei ASP.NET hat sich hier einiges getan, wie Sie in diesem Artikel sehen werden. Wir zeigen Ihnen zunächst ganz allgemein, wie Sie Sessions mit ASP.NET verwenden können und welche erweiterten Optionen Sie haben; dann erstellen wir eine praxisnahe Anwendung, die Sessions verwendet.

Sessions lesen und schreiben
Sessions verwaltet ASP.NET im Session-Objekt. Eine Session besteht aus mehreren Session-Variablen. Jede dieser Variablen kann einen Wert annehmen. Die Session wird gestartet, sobald Sie die erste Session-Variable definieren. Extra Befehle zum Starten der Session sind also nicht notwendig.
In C# erzeugen Sie eine Session-Variable mit dem Session-Objekt und hängen in eckigen Klammern und Apostrophen den Variablennamen an. Im folgenden Beispiel eröffnen wir eine Session und definieren zwei Variablen, denen wir jeweils einen String als Wert übergeben:
  1. <script runat="server">
  2. void Page_Load() {
  3. Session["svar1"] = "Wert1";
  4. Session["svar2"] = "Wert2";
  5. }
  6. </script>
Sollten Sie in VB.NET programmieren, verwenden Sie statt der eckigen runde Klammern. Das Beispiel sähe hier so aus:
  1. <script runat="server"><br></br> Sub Page_Load()<br></br> Session("Variable1") = "Wert1"<br></br> Session("Variable2") = "Wert2"<br></br> End Sub<br></br></script>
Der Zugriff auf die Session-Variablen ist genauso einfach. Mit dem Session-Objekt und danach dem Namen der Session-Variable in eckigen Klammern mit Apostrophen (VB.NET: runde Klammern) erhalten Sie den Wert. Folgender Code liest für die Session nur die zweite Variable aus. Mit ToString wird der Wert der Variablen in einen String umgewandelt:
  1. <%@ Page Language="c#" %>
  2. <script runat="server">
  3. void Page_Load() {
  4. String var2 = Session["svar2"].ToString();
  5. ausgeben.Text = var2;
  6. }
  7. </script>
  8. <html>
  9. <head>
  10. <title>Session ausgeben</title>
  11. </head>
  12. <body>
  13. <asp:label id="ausgeben" runat="server" />
  14. </body>
  15. </html>
Die Session-ID, die beim Definieren der ersten Variablen automatisch erstellt wurde, können Sie jederzeit auslesen. Sie ist in der Eigenschaft SessionID des Session-Objekts gespeichert. Mit
  1. String id = Session.SessionID;
speichern Sie die aktuelle Session-ID in der Variablen id.
Eine weitere Funktion, die Sie in der Praxis häufig benötigen werden, ist, alle Variablen einer Session auszulesen. Dafür dient die Eigenschaft Contents des Session-Objekts. Diese Eigenschaft ist eigentlich ein Array und enthält die Namen aller Session-Variablen. Sie können auf die Variablen übrigens nicht nur über den Namen, sondern auch über den Index des Arrays, beginnend bei 0, zugreifen.
Mit einer einfachen foreach-Schleife können wir alle Variablen der Session anzeigen:
  1. <%@ Page Language="c#" %>
  2. <script runat="server">
  3. void Page_Load() {
  4. foreach (String name in Session.Contents)
  5. ausgeben.Text += Session[name] + "<br />";
  6. }
  7. }
  8. </script>
  9. <html>
  10. <head><title>Session ausgeben</title></head>
  11. <body><asp:label id="ausgeben" runat="server" /></body>
  12. </html>
Auszeit und Schlusspfiff
Eine Session belegt Platz im Arbeitsspeicher des Webservers. Dort soll sie natürlich nicht ewig bleiben, da das Performance kostet und früher oder später den Server zum Absturz bringt. Deswegen muss eine Session beendet werden können. Die eleganteste Lösung ist der Zeitpunkt, wenn ein Nutzer sich ausloggt. In diesem Fall beendet die Methode Abandon des Session-Objekts die aktive Session.
Viele Webmail-Dienste meckern, wenn sich der Nutzer beim letzten Mal nicht ausgeloggt hat. Musste die letzte Session dann bis zum erneuten Einloggen auf dem Server vorgehalten werden? Natürlich nicht, denn für eine Session wird ein so genannter Timeout gesetzt. Er legt fest, nach welchem Zeitraum, in dem der Nutzer inaktiv war, die Session abläuft. In ASP.NET können Sie dies mit Session.Timeout für jede Session einzeln festlegen. Die Angabe erfolgt dabei in Minuten. Das folgende Beispiel beendet die Sitzung also, wenn der Nutzer zwei Minuten lang inaktiv war:
  1. <%@ Page Language="c#" %>
  2. <script runat="server">
  3. void Page_Load() {
  4. Session["svar1"] = "Wert1";
  5. Session.Timeout = 2;
  6. }
  7. </script>
Zwei Minuten sind in den meisten Fällen für eine Sitzung nicht lang genug. Eine gute Webanwendung sollte dem Nutzer Zeit genug lassen, auf die Toilette zu gehen oder zu telefonieren.
Wem es zu umständlich ist, das Ablaufen einer Session von Hand anzugeben, der kann dies auch in einer Konfigurationsdatei für das gesamte Webprojekt tun. In ASP.NET heißt diese Datei web.config und muss im Ordner des Webprojekts liegen. Beachten Sie aber, dass der Ordner mit dem Webprojekt im IIS eine Anwendung sein muss.
Sie können diese Konfigurationsdatei mit einem beliebigen Texteditor erzeugen. Das Attribut timeout im sessionState-Tag legt die Ablaufzeit aller Sessions des Projekts in Minuten fest:
  1. <configuration>
  2. <system.web>
  3. <sessionState timeout="12" />
  4. </system.web>
  5. </configuration>
Wenn Sie weder in der Konfigurationsdatei noch direkt im Skript ein Timeout vergeben, werden die Sessions automatisch nach 20 Minuten abgebrochen. Wollen Sie das Timeout nicht nur für ein Webprojekt, sondern für alle festlegen, müssen Sie die obigen Zeilen in die Datei machine.config einfügen. Dies ist die globale Konfigurationsdatei für ASP.NET. Sie finden sie normalerweise im Ordner WINDOWS\Microsoft.NET\Framework. Dann folgt als Unterordner die Versionsnummer des Frameworks (beispielsweise v1.0.3705) und anschließend der Ordner CONFIG mit den globalen Konfigurationsdateien. Sie können die machine.config in einem normalen Texteditor bearbeiten, sollten aber immer suchen, ob es bereits einen Eintrag mit dem sessionState-Tag gibt.
Weitere Methoden
Das Session-Objekt bietet neben den bereits bekannten Protagonisten noch einige weitere Methoden. Sie werden sie nicht so oft benötigen, sollten allerdings wissen, dass es sie gibt:
  • Session.Clear löscht alle Session-Variablen inklusive Wert.
  • Session.Remove["name"] entfernt eine Session-Variable.
  • Session.Add["name", "Wert"] fügt eine Session-Variable hinzu. Zur oben gezeigten Alternative gibt es keinen funktionalen Unterschied.
Sessions Cookie-los
In ASP war die Verwendung von Sessions immer mit der Verwendung von Cookies verbunden. Auf Cookies können sich viele Website-Betreiber aber nicht einlassen, da der Nutzer sie einfach im Browser deaktivieren kann. Dann funktioniert auf einmal die Nutzerverwaltung mit Warenkorb nicht mehr und der Ärger ist groß.
In ASP.NET wurde dieses Problem mit dem Cookie-losen Session-Management gelöst. Ein Eintrag in der Konfigurationsdatei web.config aktiviert es für das gesamte Webprojekt:
  1. <configuration>
  2. <system.web>
  3. <sessionState cookieless="true" />
  4. </system.web>
  5. </configuration>
Sollten Sie in allen Projekten auf Cookie-loses Session-Management setzen, reicht es, obigen Eintrag in der globalen Konfigurationsdatei machine.config vorzunehmen.
Wenn Sie auf Cookie-loses Session-Management setzen, sind Sie zwar das Cookie-Problem los, haben aber auch neue. Bei Sessions ohne Cookies wird die Session-ID über die URL übertragen (siehe Abb. 1). ASP.NET koppelt die Session-ID automatisch für alle Links auf den Seiten mit der URL. Dies funktioniert aber unter Umständen nicht bei absoluten Links. Ein zweites Problem ohne Cookies ist, dass alles ein wenig langsamer geht. Mit Cookies ist die Performance des Session-Managements ein wenig höher.
Wenn Sie wissen möchten, ob eine Session Cookie-los ist oder nicht, verwenden Sie die Eigenschaft IsCookieLess. Mit ihr erhalten Sie einen Wahrheitswert, der angibt, ob Cookies deaktiviert (true) oder aktiviert (false) sind. Mit folgender Zeile lesen Sie den Status in eine Variable ein:
  1. Boolean cookies = HttpSessionState.IsCookieLess;
Konfiguration für Fortgeschrittene
Normalerweise werden die Sessions im Arbeitsspeicher des Webservers abgelegt. Das ist bei kleineren Websites auch kein Problem. Schwieriger wird es, wenn die Last des Servers kontinuierlich steigt. ASP.NET löst dieses Problem recht elegant, indem es eine Auslagerung der Sessions per Konfiguration erlaubt. Die Konfigurationseinträge können Sie wiederum entweder nur für ein Projekt in einer web.config-Datei oder global in der machine.config anlegen.
ASP.NET hält insgesamt vier Modi bereit, Sessions auszulagern: Die Standardeinstellung ist, Cookies im Arbeitsspeicher des Webservers zu sichern. Dieser Modus heißt mode="InProc". Die Konfigurationsdatei sieht so aus:
  1. <configuration>
  2. <system.web>
  3. <sessionState mode="InProc"
  4. cookieless="true" />
  5. </system.web>
  6. </configuration>
Der zweite Modus speichert die Sessions in einer MS SQL Server-Datenbank. Dieser Modus bietet keine hohe Performance, da in die Datenbank geschrieben und aus ihr gelesen werden muss. Dafür können die Sessions auch dauerhaft gesichert werden. Der Modus wird mit mode="SqlServer" angegeben. Zusätzlich müssen Sie mit dem Attribut sqlConnectionString die Verbindungsdaten zur Datenbank festlegen. Dies sind vor allem IP-Adresse, User-ID und Passwort:
  1. <configuration>
  2. <system.web>
  3. <sessionState mode="SqlServer" sqlConnectionString="data source=127.0.0.1;database=state;user id=sa;password="
  4. cookieless="true" />
  5. </system.web>
  6. </configuration>
Im dritten Modus lagern Sie die Sessions in den Arbeitsspeicher eines eigenen Servers aus, der dann die Verwaltung und Sicherung übernimmt. Auch hier müssen Sie die IP-Adresse und zusätzlich den Port angeben:
  1. <configuration>
  2. <system.web>
  3. <sessionState mode="StateServer"
  4. stateConnectionString="tcpip=127.0.0.1:42424"
  5. cookieless="true" />
  6. </system.web>
  7. </configuration>
Der letzte Modus (mode="Off") ist eigentlich kein Modus für das Session-Management, sondern deaktiviert es komplett. Wenn Sie dies in der Konfigurationsdatei eines Webprojekts angeben, können Sie zwar nicht mit Sessions arbeiten, dafür gewinnen Sie allerdings ein wenig Performance, da ansonsten standardmäßig Platz im Arbeitsspeicher für Sessions reserviert wäre:
  1. <configuration>
  2. <system.web>
  3. <sessionState mode="Off" />
  4. </system.web>
  5. </configuration>
Beispielapplikation
Personalisierte Portale sind momentan in. In Zeiten, in denen die Werbeumsätze stagnieren oder gar geringer werden, versuchen immer mehr Seitenbetreiber, möglichst genaue Profildaten über ihre Benutzer zu gewinnen und als Gegenleistung Mehrwert zu bieten: Zugriffe auf spezielle Inhalte. Bevor ein Benutzer aber diese Inhalte erhalten darf, muss er sich erst anmelden. Eine Authentifizierung über .NET Passport ist teuer, weswegen einer selbst gestrickten Lösung in vielen Fällen der Vorzug gegeben wird.
Wir werden bei dieser Applikation natürlich Sessions einsetzen. In einer Session-Variablen wird der Benutzername des gerade eingeloggten Nutzers abgelegt. Zunächst müssen wir sicherstellen, dass bei Seiten mit personalisiertem Inhalt lediglich authentifizierte Nutzer Zugriff erhalten. Da diese Aufgabenstellung häufig vorkommt - nämlich auf allen personalisierten Seiten - bietet sich eine Code Behind-Klasse geradezu an (Alternative: Ein User Control). Alle personalisierten Seiten erben dann von der Klasse:
  1. <%@ Page Language="c#"
  2. Inherits="SessionCodeBehind"
  3. Src="SessionCodeBehind.cs" %>
  4. <html>
  5. <head>
  6. <title>Personalisierter Bereich</title>
  7. </head>
  8. <body>
  9. <h1>Willkommen in Ihrem personalisierten Bereich!</h1>
  10. <p>Benutzer: <asp:Label id="Benutzer" runat="server" /></p>
  11. <p><a href="logout.aspx">Logout</a></p>
  12. </body>
  13. </html>
In der Code Behind-Klasse müssen dann einige Aufgaben programmiertechnisch erfüllt werden. Zunächst einmal: Wenn die Session-Variable mit dem Benutzernamen nicht existiert, muss die Seite verlassen werden. In diesem Fall wird eine Umleitung auf die Seite login.aspx (dazu kommen wir später) durchgeführt. Als URL-Parameter wird die URL der aktuellen Seite (Umgebungsvariable SCRIPT_NAME) übergeben, damit das Login-Skript auch weiß, auf welche Seite der Benutzer ursprünglich wollte:
  1. public void Page_Load(Object o, EventArgs e) {
  2. if (Session["login"] == null ||
  3. Session["login"] == "") {
  4. String referer = Request.ServerVariables["SCRIPT_NAME"];
  5. Response.Redirect("login.aspx?ref=" + referer);
  6. }
  7. }
Ist dagegen die Session-Variable gesetzt, wird der Name des Benutzers im Label-Control ausgegeben:
  1. Benutzer.Text = HttpUtility.HtmlEncode(Session["login"].ToString());
Den kompletten Code für die Code Behind-Datei (SessionCodeBehind.cs) finden Sie in Listing 1.

Listing 1
  1. using System;
  2. using System.Web;
  3. using System.Web.UI;
  4. using System.Web.UI.WebControls;
  5. public class SessionCodeBehind : Page {
  6. public Label Benutzer;
  7. public void Page_Load(Object o, EventArgs e) {
  8. if (Session["login"] == null ||
  9. Session["login"] == "") {
  10. String referer = Request.ServerVariables["SCRIPT_NAME"];
  11. Response.Redirect("login.aspx?ref=" + referer);
  12. } else {
  13. Benutzer.Text = HttpUtility.HtmlEncode(Session["login"].ToString());
  14. }
  15. }
  16. }
Auf der Seite login.aspx wird dem Benutzer die Möglichkeit geboten, seinen Namen und Passwort anzugeben und sich damit bei der Applikation anzumelden. Als Datenbasis verwenden wir eine einfache Access-Datenbank, die wir unter c:\inetpub\db\sessions.mdb abgelegt haben. Sie enthält eine einzige Tabelle, genannt Benutzer, mit den folgenden Feldern:
  • id - AutoWert (ID des Eintrags)
  • login - Text (Benutzername)
  • passwort - Text (zugehöriges Passwort)
Doch zurück zur Login-Seite. Das Formular zum Einloggen sieht wie folgt aus:
  1. <form runat="server">
  2. <p><asp:Label id="Ausgabe" runat="server" /></p>
  3. <p>Benutzer: <asp:TextBox id="Benutzer" runat="server" /></p>
  4. <p>Passwort: <asp:TextBox TextMode="Password" id="Passwort" runat="server" /></p>
  5. <p><asp:Button Text="Login" OnClick="Login" runat="server" />
  6. </form>
Nach Klick auf die Versende-Schaltfläche wird die serverseitige Funktion Login() ausgeführt. In dieser wird zunächst überprüft, ob in der Datenbank ein Eintrag für die Kombination aus Benutzername und Passwort existiert. Dazu wird eine SQL-SELECT-Abfrage an die Datenbank geschickt:
  1. public void Login(Object o, EventArgs e) {
  2. OleDbConnection conn = new OleDbConnection(
  3. "Provider=Microsoft.Jet.OleDb.4.0; Data Source=c:\\inetpub\\db\\sessions.mdb");
  4. conn.Open();
  5. OleDbCommand comm = new OleDbCommand("SELECT * FROM benutzer WHERE login LIKE '" +
  6. Benutzer.Text.Replace("'", "''") +
  7. "' AND passwort = '" +
  8. Passwort.Text.Replace("'", "''") +
  9. "'", conn);
  10. OleDbDataReader reader = comm.ExecuteReader();
Nun geht es ans Eingemachte: Existiert ein Datensatz, werden die Session-Variable angelegt und der Benutzer auf die Hauptseite des Applikation weitergeleitet. Sollte allerdings der URL-Parameter ref übergeben worden sein, leitet das Skript den Browser zur angegebenen Adresse um:
  1. if (reader.Read()) {
  2. Session["login"] = Benutzer.Text;
  3. conn.Close();
  4. if (Request.QueryString["ref"] != null) {
  5. Response.Redirect(Request.QueryString["ref"]);
  6. } else {
  7. Response.Redirect("./");
  8. }
  9. }
Andernfalls - die Authentifizierung ist also fehlgeschlagen - wird eine Fehlermeldung im Label-Control ausgegeben:
  1. else {
  2. Ausgabe.Text = "Login gescheitert";
  3. conn.Close();
  4. }
Den kompletten Code können Sie Listing 2 entnehmen. In Abpictureung 2 sehen Sie das Loginformular, in Abpictureung 3 die personalisierte Seite (nach erfolgtem Login). Wie Sie anhand der URL erkennen können, wird dabei ein Cookie-loses Session-Management verwendet, durch einen entsprechenden Eintrag in der web.config wie zuvor beschrieben.

Listing 2
  1. <%@ Page Language="c#" %>
  2. <%@ Import Namespace="System.Data" %>
  3. <%@ Import Namespace="System.Data.OleDb" %>
  4. <script runat="server">
  5. public void Login(Object o, EventArgs e) {
  6. OleDbConnection conn = new OleDbConnection(
  7. "Provider=Microsoft.Jet.OleDb.4.0; Data Source=c:\\inetpub\\db\\sessions.mdb");
  8. conn.Open();
  9. OleDbCommand comm = new OleDbCommand("SELECT * FROM benutzer WHERE login LIKE '" +
  10. Benutzer.Text.Replace("'", "''") +
  11. "' AND passwort = '" +
  12. Passwort.Text.Replace("'", "''") +
  13. "'", conn);
  14. OleDbDataReader reader = comm.ExecuteReader();
  15. if (reader.Read()) {
  16. Session["login"] = Benutzer.Text;
  17. conn.Close();
  18. if (Request.QueryString["ref"] != null) {
  19. Response.Redirect(Request.QueryString["ref"]);
  20. } else {
  21. Response.Redirect("./");
  22. }
  23. } else {
  24. Ausgabe.Text = "Login gescheitert";
  25. conn.Close();
  26. }
  27. }
  28. </script>
  29. <html>
  30. <head>
  31. <title>Personalisierter Bereich</title>
  32. </head>
  33. <body>
  34. <h1>Login</h1>
  35. <form runat="server">
  36. <p><asp:Label id="Ausgabe" runat="server" /></p>
  37. <p>Benutzer: <asp:TextBox id="Benutzer" runat="server" /></p>
  38. <p>Passwort: <asp:TextBox TextMode="Password" id="Passwort" runat="server" /></p>
  39. <p><asp:Button Text="Login" OnClick="Login" runat="server" />
  40. </form>
  41. </body>
  42. </html>
Zu guter Letzt muss noch eine Logout-Seite (logout.aspx) erstellt werden, welche den Benutzer wieder abmeldet. Dazu wird einfach die zuvor angelegte Session-Variable gelöscht:
  1. <%@ Page Language="c#"%>
  2. <script runat="server">
  3. public void Page_Load(Object o, EventArgs e) {
  4. Session["login"] = "";
  5. }
  6. </script>
  7. <html>
  8. <head>
  9. <title>Personalisierter Bereich</title>
  10. </head>
  11. <body>
  12. <h1>Sie haben den personalisierten Bereich verlassen!</h1>
  13. <p><a href="login.aspx">Login</a></p>
  14. </body>
  15. </html>
Fazit
Diese kleine, aber feine Applikation hat Ihnen gezeigt, wie Sie mit ASP.NET und dem eingebauten Session-Management den Grundstein für eine Anwendung legen können, die unbedingt ein Session-Management benötigt. Personalisierung ist nur der erste Schritt.
Im nächsten Heft gehen wir auf die zweite Möglichkeit für eine Webapplikation ein, Informationen zu behalten. Die Rede ist natürlich von Cookies, den kleinen, ungeliebten Datenkrümeln.


Anzeige

Kommentare

zurück zum Seitenanfang