MSDN Magazin > Home > Ausgaben > 2006 > December >  Cutting Edge: Der Client-Aspekt von ASP.NET-Sei...
Cutting Edge
Der Client-Aspekt von ASP.NET-Seiten
Dino Esposito

Codedownload verfügbar unter: CuttingEdge2006_12.exe (153 KB)
Browse the Code Online
Es gibt einen Trend in der Softwarebranche, einen Großteil der Last des Codeschreibens in die Infrastruktur der zugrunde liegenden Plattform zu verschieben. In verschiedenen Entwicklungsumgebungen werden Entwickler aufgefordert, eine grobe Beschreibung der benötigten Informationen in einer relativ formlosen Syntax bereitzustellen, anstatt jedes einzelne Byte gemäß einem streng festgelegten Satz von Syntaxregeln hartzucodieren. Es ist jetzt unter Entwicklern üblich, das gewünschte Ergebnis in einem XML-Dialekt zu beschreiben und die Inhalte von einem Compiler oder einem Laufzeitmodul analysieren und in herkömmlichen und ausführbaren Code verarbeiten zu lassen.
Windows® Presentation Foundation z. B., eine der Säulen von .NET Framework 3.0, verwendet XAML als XML-basierte Präsentationssprache, um die Benutzeroberfläche des Formulars zu beschreiben. Die AJAX-Bibliothek von Microsoft (ein Teil des Systems, das früher den Codenamen ASP.NET „Atlas“ trug) wendet mit seiner XML-Skript-Metasprache dasselbe Prinzip auf reichhaltige Webseiten an (obwohl XML-Skript technisch gesehen nicht Bestandteil der Kernversion, sondern als inoffizielle Beispieltechnologie freigegeben ist). Als deklarative Layoutsprache bindet XML-Skript HTML-Elemente und Skript zusammen und bildet virtuelle, clientseitige Steuerelemente. Am Ende führt XML-Skript Logik und Funktionalität in Clientseiten ein.
Es gibt ein paar Vorteile bezüglich der Verwendung einer deklarativen Sprache zum Erstellen von Webseiten und -formularen. Auf diese Weise können Seiten und Formulare einfacher von serverseitigen Komponenten generiert werden, als wenn diese tatsächlich Code in Visual Basic®, C# oder JavaScript ausgeben müssten. Außerdem ist deklaratives Markup an sich für Erstellungstools wie Visual Studio® einfacher zu ersinnen und zu entwerfen. Vom architektonischen Standpunkt aus betrachtet, zeigen Sie durch die Verwendung eines deklarativen Ansatzes an, was die Seitenelemente tun – aber nicht, wie sie es tun. Auf diese Weise erstellen Sie eine zusätzliche Abstraktionsebene.
Die erste Programmierumgebung, die die Vorteile eines solchen Modells nutzte, war ASP.NET, und zwar ab Version 1.0. Wie die meisten Webentwickler mittlerweile sicher wissen, wird eine ASP.NET-Seite in der Regel in eine oder zwei Dateien geschrieben: eine ASPX-Markup-Datei und optional eine Code-Behind-Datei. Die Code-Behind-Datei enthält eine Klassendatei, die in einer beliebigen, unterstützten Programmiersprache geschrieben ist, in der Regel jedoch in Visual Basic oder C#. Die ASPX-Markup-Datei enthält HTML-Tags, Tags für ASP.NET-Steuerelemente und Literale, die die Struktur der Seite bilden (die ebenfalls Code enthalten kann). Dieser Text wird zur Laufzeit analysiert und in eine Seitenklasse umgewandelt. So eine Seitenklasse, kombiniert mit der Code-Behind-Klasse und ein wenig vom System generierten Code, beinhaltet den ausführbaren Code, der alle zurückgegebenen Daten verarbeitet, die Antwort generiert und an den Client zurücksendet.
Die große Mehrheit der ASP.NET-Entwickler kennt das Gesamtmodell, doch es gibt eine Reihe „schwarzer Löcher“, die nur von einer kleinen Gruppe von Entwicklern wirklich im Detail verstanden werden. In MSDN®, Büchern und Onlineartikeln werden einzelne Aspekte der Seitenmaschinerie erklärt, doch es mangelt immer noch an einer umfassenden und einheitlichen Berichterstattung über die Interna der Seiten. Wenn Sie sich den HTML-Quellcode einer ASP.NET-Seite ansehen, sehen Sie eine Reihe verborgener Felder und automatisch eingefügte Blöcke von JavaScript-Code, der wenig Sinn ergibt. Diese Felder und Blöcke tragen jedoch dazu bei, dass die Webseite funktioniert. In diesem Artikel werde ich den clientseitigen Quellcode analysieren, den ASP.NET-Seiten generieren. Ich werde verborgene Felder wie den bekannten Ansichtszustand behandeln, aber auch weniger bekannte wie den Steuerelementzustand, die Ereignisvalidierung, das Ereignisziel und von Argumenten und vom System bereitgestellten Skriptcode.
Viele der hier behandelten Implementierungsdetails sind für die aktuelle Version von ASP.NET spezifisch. Diese Details könnten sich in Zukunft ändern (sie haben sich in der Vergangenheit geändert), und Sie sollten keinen Produktionscode erstellen, der von nicht dokumentierten Details abhängig ist.

Analyse des ASPX-Codes
In Abbildung 1 wird eine minimale, aber funktionierende ASP.NET-Seite angezeigt. Trotz seiner extremen Einfachheit ist dies ein gutes Beispiel, da es typische Elemente einer realen ASP.NET-Seite beinhaltet: Felder für die Seiteneingabe, klickbare Postbackelemente und schreibgeschützte Elemente.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Test.aspx.cs"
    Inherits="Test" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html  >
<head runat="server"><title>Test page</title></head>
<body>
  <form id="form1" runat="server">
    <div>
      <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>  
      <asp:Button ID="Button1" runat="server" 
        OnClick="Button1_Click" Text="Click me" />
    </div>
    <hr />
    <h1><asp:Label ID="Msg" runat="server" Text=""></asp:Label></h1>
  </form>
</body>
</html>
Die ASPX-Seite enthält drei Serversteuerelemente: ein Textfeld zum Erfassen von Daten, eine Submit-Schaltfläche zum Starten eines POST-Vorgangs und eine Beschriftung zum Anzeigen von schreibgeschützten Daten. Oberhalb der ASPX-Datei werden einige globale Attribute für die individuelle Seite definiert. Sehen Sie sich die am häufigsten verwendeten Attribute der Page-Direktive an, wie die in Abbildung 1.
<%@ Page Language="C#" 
         AutoEventWireup="true" 
         CodeFile="Test.aspx.cs" 
         Inherits="Test" 
%>
Die meisten Attribute der Page-Direktive haben nur begrenzte Auswirkungen auf das Seitenmarkup, den HTML-Code, den der Browser mit der HTTP-Antwort empfängt. Vielmehr wirken sich die meisten Page-Attribute auf den Code der dynamisch generierten Seite aus, die vom System auf dem ASPX-Markup und der Code-Behind-Klasse erstellt wird. Mit dem Language-Attribut wird die Sprache zugeordnet, mit der Code-Behind in Visual Studio erstellt wird. Vom System wird dieselbe Sprache verwendet, um die dynamische Seitenklasse zu generieren, mit der die Browseranforderung für die ASPX-Ressource erfüllt wird. Mit dem Attribut CodeFile wird die Quelldatei angegeben, in der die Code-Behind-Klasse gespeichert ist. Mit dem Inherits-Attribut wird der Name der Code-Behind-Klasse in der Codedatei angegeben, die als übergeordnete Klasse der dynamisch generierten Seitenklasse verwendet werden sollte. Mit dem Attribut AutoEventWireup schließlich wird angegeben, ob eine standardmäßige Namenskonvention verwendet werden soll, um Behandlungscode Seitenereignissen zuzuordnen. Wenn AutoEventWireup auf True gesetzt ist, können Sie der Codedatei eine Page_Load-Methode hinzufügen, mit der das Load-Ereignis für die Seite verarbeitet wird. Das Attribut wird dann automatisch mit dem Load-Ereignis der Seite registriert. Die implizite Namenskonvention schreibt vor, dass der Ereignishandler die Form Page_XXX haben soll, wobei XXX der Name aller öffentlichen Ereignisse sein kann, die in der Seitenklasse definiert sind. Wenn AutoEventWireup auf False gesetzt ist, müssen Sie das Ereignis der Seitenklasse an seinen Handler binden. Dies können Sie in einem maßgefertigten Klassenkonstruktor erledigen:
public partial class Test : System.Web.UI.Page
{
  public Test()
  {
    this.Load += new EventHandler(Page_Load);
  }
  ...
}
Wenn der Webserver eine HTTP-Anforderung für eine bestimmte ASPX-Ressource empfängt, wird diese Anforderung an den ASP.NET-Workerprozess weitergeleitet. Der Prozess verwaltet die CLR, innerhalb der eine Laufzeitumgebung erstellt wird, um ASP.NET-Anforderungen zu verarbeiten. Das höchste Ziel der ASP.NET HTTP-Laufzeitumgebung ist die Erfüllung der Anforderung, also das Abrufen des Markup (HTML, WML, XHTML und was die Anwendung sonst noch zurückgeben soll), welches dann in die HTTP-Antwort eingebettet wird. Verantwortlich für die Rückgabe des Markup für die Anforderung ist eine besondere Systemkomponente, die HTTP-Handler genannt wird.
Der HTTP-Handler ist eine Instanz einer Klasse, die die IHttpHandler-Schnittstelle implementiert. Das ASP.NET-Framework wird mit einigen vordefinierten HTTP-Handlern geliefert, die in bestimmten Situationen dienlich sind oder die als Basisklasse für andere und spezifischere Anforderungen fungieren. Die Klasse System.Web.UI.Page ist einer der komplexesten und differenziertesten integrierten HTTP-Handler in ASP.NET.
Jede ASP.NET-Anforderung ist einem HTTP-Handler zugeordnet. Angenommen, ein Clientbrowser sendet eine Anforderung für eine Seite namens test.aspx. Die Anforderung wird an ASP.NET weitergegeben und von der HTTP-Laufzeit verarbeitet. Mit der Laufzeit wird die HTTP-Handlerklasse ermittelt, die die Anforderung durch eine Seitenhandlerfactory weiterreicht. Wenn der richtige Handler noch nicht in der AppDomain zur Verfügung steht, wird er dynamisch erstellt und im temporären ASP.NET-Ordner auf dem Webserver-Computer gespeichert. Für eine Seite namens test.aspx wird der HTTP-Handler als Klasse namens ASP.text_aspx erstellt.
Die dynamische Erstellung der HTTP-Handlerklasse für eine bestimmte Anforderung ist ein Prozess, der nur ein Mal pro Seite stattfindet: wenn die Seite während der Lebensdauer der Anwendung zum ersten Mal angefordert wird. (Wenn jedoch Batchkompilierung verwendet wird, kann der Handler bei der ersten Anforderung einer beliebigen Seite in der Anwendung generiert werden.) Die dynamisch erstellte Assembly wird ungültig gemacht und ersetzt, wenn die Anwendung neu gestartet oder die Quelle der Seite auf dem Webserver geändert wird. In Abbildung 2 ist die Hierarchie der Seitenklassen von der grundlegenden Seitenklasse bis hinunter zur dynamisch generierten Klasse dargestellt, die die Benutzeranforderung erfüllen.
Abbildung 2 Hierarchie der Seitenklassen (Klicken Sie zum Vergrößern auf das Bild)
Mit der ASP.NET-Laufzeit wird der Visual Basic- oder C#-Quellcode der dynamischen Seitenklasse erstellt, indem der Quellcode der entsprechenden ASPX-Datei analysiert wird. Jedes Tag mit runat="server" wird einer Instanz eines Serversteuerelements zugeordnet. Der gesamte weitere Text wird einem Literalsteuerelement zugeordnet und wortgetreu ausgegeben. Die Register-Direktive hilft ggf. bei der Auflösung von Tags, die auf nicht standardmäßige Steuerelemente zeigen. Das an den Clientbrowser zurückgegebene Markup entsteht durch das Kumulieren von Markup, das von jedem Serversteuerelement auf der Seite ausgegeben wird. Beachten Sie, dass im Allgemeinen jede Seite Markup ausgibt, in der Regel HTML-Markup. Dies ist jedoch keine Anforderung, und eine ASP.NET-Seite kann beliebige Daten ausgeben.

Analyse des HTML-Clientcodes
In Abbildung 3 ist die HTML-Ausgabe für die Beispielseite in Abbildung 1 dargestellt. Im HTML gibt es keinen Hinweis darauf, dass in der serverseitigen ASPX-Seite eine Page-Direktive vorhanden war. Stattdessen wird die !DOCTYPE-Direktive wortgetreu kopiert. Bei dem ersten runat="server"-Block in Abbildung 1 handelt es sich um das <form>-Tag. Das bedeutet, dass der gesamte Text zwischen Page und <form> wortgetreu ausgegeben wird. Im Quellcode der dynamisch erstellten Seitenklasse auf dem Server wird dieser Text in eine einzelne Instanz der Klasse LiteralControl konvertiert. Das <form>-Tag wird wie folgt ausgegeben:
<form name="form1" method="post" action="Test.aspx" id="form1">
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html  >
<head><title>Test page</title></head>
<body>
  <form name="form1" method="post" action="Test.aspx" id="form1">
    <div>
      <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" 
        value="/wEPDw ... eeY=" />
    </div>
    <div>
      <input name="TextBox1" type="text" id="TextBox1" />  
      <input type="submit" name="Button1" 
        value="Click me" id="Button1" />
    </div>
    <hr />
    <h1><span id="Msg"></span></h1>
    <div>
      <input type="hidden" name="__EVENTVALIDATION" id="__
      EVENTVALIDATION" 
        value="/wE ... u7" />
    </div>
  </form>
</body>
</html>

Das Tag <form runat="server" ...> wird durch eine Instanz der Klasse HtmlForm wiedergegeben. Die Steuerelementklasse verfügt über keine Eigenschaft, mit der Sie das Aktionsattribut für das Ausgabemarkup festlegen können. Das Aktionsattribut wird in die URL der aktuellen Seite hartcodiert. Dieses Verhalten findet sich im Fundament der ASP.NET-Plattform. Beachten Sie, dass das ID-Attribut mit einem identischen Namensattribut gepaart ist.
Das <asp:textbox>-Tag wird in HTML durch ein <input type="text">-Element wiedergegeben. In diesem Fall wird ein Namensattribut hinzugefügt, das dem ursprünglichen ID-Attribut zugeordnet wird. Beachten Sie, dass Visual Studio 2005 eine Warnmeldung ausgibt, wenn Sie das ID-Attribut auslassen, doch ASP.NET wird die Seite dennoch erfolgreich kompilieren. Wenn das ID-Attribut fehlt, wird eine zufällige Zeichenfolge generiert und an das Namensattribut gebunden. Das <asp:Button>-Tag wird durch eine <input type="submit">-Schaltfläche dargestellt. Ein <asp:Label>-Tag gibt das HTML-Tag <span> im Clientbrowser wieder.
In den meisten (aber nicht in allen) Fällen generiert jedes mit dem Attribut runat="server" versehene Tag einen entsprechenden Block HTML-Markup. Durch die ID-Zeichenfolge wird eine dauerhafte Übereinstimmung zwischen den beiden Blöcken garantiert – eine auf der Clientseite, und eine auf der Serverseite. Wie Sie in Abbildung 3 sehen können, wird das HTML-Markup durch ein paar verborgene Felder vervollständigt: __VIEWSTATE und __EVENTVALIDATION.

Das Feld Ansichtszustand
Die Inhalte des Feldes __VIEWSTATE repräsentieren den Zustand der Seite bei der letzten Verarbeitung auf dem Server. Obwohl das Feld an den Client gesendet wurde, enthält das Feld Ansichtszustand keine Informationen, die vom Client in Anspruch genommen werden sollten. Die im Ansichtszustand gespeicherten Informationen sind nur für die Serverseite und einige von deren untergeordneten Steuerelementen relevant und werden ausschließlich vom Server gelesen, in Anspruch genommen und geändert.
Wenn das Feld Ansichtszustand auf diese Weise implementiert wird, nimmt es keine wichtigen Serverressourcen in Anspruch und lässt sich schnell abrufen und verwenden. Andererseits jedoch nimmt die Größe der HTTP-Anforderung und -Antwort um einige Kilobytes zu, nur weil der Ansichtszustand mit der Seite verpackt wird. Eine wirklichkeitsnahe Seite, die mit einem Datenraster ausgefüllt ist, kann im Ansichtszustand ohne Weiteres eine Größe von 20 KB erreichen. Diese zusätzlichen Daten werden jedes Mal hoch- und heruntergeladen. Der Ansichtsstatus ist eine der wichtigsten Funktionen von ASP.NET, da er statusbehaftete Programmierung über ein statusfreies Protokoll wie HTTP ermöglicht. Wenn der Ansichtszustand jedoch ohne strenge Kriterien verwendet wird, kann er den Seiten leicht zur Last werden.
Indem Sie ein paar Methoden der Klassencodedatei außer Kraft setzen, können Sie die Inhalte des Feldes „Ansichtszustand“ auf dem Server belassen, wo sie in einer Datenbank, im Zwischenspeicher oder im Session-Objekt gespeichert werden. Beachten Sie jedoch, dass das Belassen der Informationen des Ansichtszustands auf dem Server nicht die offensichtliche Problemumgehung ist, als die sie zunächst erscheint. Eigentlich ist es kein Zufall, dass das ASP.NET-Team sich für einen seitenbasierten Ansichtszustand entschieden hat. Ein serverbasierter Ansichtszustand ist so lange in Ordnung, wie der Benutzer von einer Seite zur nächsten navigiert und dabei den Verknüpfungen in der Anwendung folgt. Denken Sie daran, dass ASP.NET-Anwendungen wiederholt über die selbe Seite bereitstellen. Doch was geschieht, wenn der Benutzer auf die Schaltfläche „Zurück“ klickt? Um sicher zu sein, sollten Sie den Ansichtszustand pro Anforderung bereithalten, und nicht pro Seite. Außerdem sollte die Kette der verfolgten Anforderungen so lang sein wie die Anforderungen, die der Benutzer mithilfe der Schaltflächen „Zurück“ und „Vorwärts“ aufrufen kann. Es ist vielleicht keine perfekte Lösung, den Ansichtszustand auf dem Client zu speichern, doch das gilt auch für die Speicherung auf dem Server. Welche Option Sie für Ihre Anwendung bevorzugen, hängt von den Erwartungen ab, die Sie daran knüpfen.
In ASP.NET 2.0 enthält das verborgene Feld __VIEWSTATE zwei Typen von Informationen: den Ansichtszustand und den Steuerelementzustand. Entwickler können den Ansichtszustand vollständig deaktivieren und ihre Anwendungen auf rein statusfreie Weise betreiben. Dies ist kein Problem, solange Sie integrierte Steuerelemente und selbst erstellte Steuerelemente verwenden, oder zumindest Steuerelemente, auf deren Quellcode Sie zugreifen können. Was geschieht, wenn Sie ein benutzerdefiniertes Steuerelement verwenden, das von einem aktivierten Ansichtszustand ausgeht? Einige Steuerelemente, in der Regel reichhaltige Steuerelemente von Drittanbietern und benutzerdefinierte Steuerelemente, müssen postbackübergreifend private Informationen beibehalten. Diese Informationen sind nicht öffentlich und nicht dafür ausgelegt, auf Anwendungsebene verfügbar gemacht zu werden, z. B. im reduzierten/erweiterten Zustand eines Dropdownbereichs. Diese Informationen können nur für den Ansichtszustand beibehalten werden. Wenn der Ansichtszustand deaktiviert ist, schlägt das Steuerelement u. U. ungewollt fehl.
Um dieses Problem zu verringern, wurde in ASP.NET 2.0 der Begriff Steuerelementzustand eingeführt. Jedes Serversteuerelement kann alle kritischen Eigenschaften in eine Sammlung packen und im Steuerelementzustand der Seite speichern. Der Steuerelementzustand wird im Feld __VIEWSTATE gespeichert, kann jedoch im Gegensatz zum herkömmlichen Ansichtszustand nicht deaktiviert werden und ist immer verfügbar. Entwickler verwalten den Steuerelementzustand durch ein Paar von neuen, überschreibbaren Methoden in der Seitenklasse: LoadControlState und SaveControlState. Hinsichtlich des Ansichtszustands in ASP.NET 2.0 ist außerdem zu beachten, dass ein neuer und effektiverer Serialisierungsalgorithmus verwendet wird, um den Zustand individueller Steuerelemente für ein verborgenes Feld zu vereinfachen. Aus diesem Grund ist die Gesamtgröße des verborgenen Feldes __VIEWSTATE halb so groß wie das entsprechende Feld in ASP.NET 1.x.
Wie bereits erwähnt, wird der Ansichtszustand in einem verborgenen Feld gespeichert, um dieses eindeutig mit einer bestimmten Seitenanforderung zu verknüpfen. Wenn eines der HTML-Elemente in einer bestimmten Seiteninstanz Daten zurückgibt, beginnt die dynamisch generierte Seitenklasse, auf dem Server zu arbeiten. Dabei werden die Daten verwendet, die in dem Ansichtszustand gespeichert sind, um den zuletzt bekannten guten Zustand der Steuerelemente auf der Seite wieder herzustellen. Was geschieht, wenn der Ansichtszustand auf dem Client manipuliert wird? Ist das überhaupt möglich? Standardmäßig wird der Ansichtszustand mit dem Base64-Schema codiert und gehasht, und der sich daraus ergebende Hashwert wird ebenfalls mit dem Ansichtszustand gespeichert. Der Hashwert wird aus den Inhalten des Ansichtszustands und einem Serverschlüssel berechnet. Immer wenn die Seite Daten zurückgibt, trennt der Code in der Seitenklasse die Inhalte und den Hashwert des Ansichtszustands voneinander. Als Nächstes wird der Hashwert auf der Basis der abgerufenen Inhalte des Ansichtszustands und des Serverschlüssels neu berechnet. Wenn die beiden Hashwerte nicht übereinstimmen, wird eine Sicherheitsausnahme ausgelöst (siehe Abbildung 4).
Abbildung 4: Seitenansicht kann auf dem Client nicht geändert werden (Klicken Sie zum Vergrößern auf das Bild)
Was geschieht, wenn ein bösartiger Benutzer versucht, eine gefälschte Anforderung mit einem geänderten Ansichtszustand bereitzustellen? Der bösartige Benutzer müsste den Serverschlüssel kennen, um einen Hashwert für die veränderten Inhalte des Ansichtszustands zu generieren, der auf dem Server abgeglichen werden kann. Der Serverschlüssel besteht jedoch aus Informationen, die sich ausschließlich auf dem Server befinden, und ist nicht in dem Feld Ansichtszustand enthalten. Die Seite tweakviewstate.aspx im Begleitcode enthält Skriptcode zum Ändern des Ansichtszustands und zum Ausüben mit der in Abbildung 4 dargestellten Ausnahme.
Obwohl der Ansichtszustand kaum dazu verwendet werden kann, einen Angriff zu planen, ist die Vertraulichkeit der Daten nicht gewährleistet, es sei denn, sie werden verschlüsselt. Die Inhalte des Ansichtszustands können tatsächlich auf dem Client decodiert und untersucht, aber nicht erfolgreich verändert werden, um der Serverumgebung einen geänderten Seitenzustand bereitzustellen.
Das verborgene Feld __EVENTVALIDATION ist eine neue Sicherheitsmaßnahme in ASP.NET 2.0. Die Funktion verhindert nicht autorisierte Anforderungen, die von potenziell bösartigen Benutzern vom Client gesendet werden. Um sicherzustellen, dass jedes Postback- und Rückrufereignis den erwarteten Benutzeroberflächenelementen entstammt, wird von der Seite eine zusätzliche Validierungsebene für Ereignisse hinzugefügt. Im Grunde werden in der Seite die Inhalte der Anforderung mit den Informationen in dem Feld __EVENTVALIDATION verglichen, um zu überprüfen, ob auf dem Client ein zusätzliches Eingabefeld hinzugefügt wurde. Dieser Wert wird in einer Liste ausgewählt, die auf dem Server bereits bekannt ist. Das Ereignisvalidierungsfeld wird während der Wiedergabe von der Seite erstellt. Das ist der letztmögliche Moment, in dem die Informationen zur Verfügung stehen. Wie im Ansichtszustand enthält das Ereignisvalidierungsfeld einen Hashwert, um clientseitige Manipulationen zu verhindern.
In Steuerelementen wird die RegisterEventForValidation-Methode für das ClientScriptManager-Objekt verwendet, in denen die Informationen der Steuerelemente für sichere Postbacks gespeichert werden. Für jedes Steuerelement wird wenigstens die eigene, eindeutige ID registriert. Listensteuerelemente speichern außerdem alle Werte in der Liste. Serversteuerelemente, die Ereignisvalidierung unterstützen, rufen in ihrer Implementierung der IPostBackDataHandler-Schnittstelle die Methode ValidateEvent auf. Wenn die Validierung fehlschlägt, wird eine Sicherheitsausnahme ausgelöst.
Sie können die Ereignisvalidierung auf Seitenbasis aktivieren bzw. deaktivieren; in jeder Steuerelementklasse wird die Ereignisvalidierung durch das Attribut SupportsEventValidation aktiviert. Derzeit gibt es keine Möglichkeit, die Ereignisvalidierung für eine bestimmte Steuerelementinstanz zu aktivieren bzw. zu deaktivieren.
Die Ereignisvalidierung ist eine Verteidigungsschranke, mit der die Eingabe auf einen bekannten Satz an Werten beschränkt werden soll. Dadurch wird schlicht die Sicherheitsschranke angehoben, Skriptangriffe werden dadurch nicht aufgehalten.
Die Ereignisvalidierung kann Probleme verursachen, wenn sie im Zusammenhang mit AJAX-fähigen Anwendungen verwendet wird. In solchen Anwendungen können durch ein wenig Arbeit des Clients dynamisch neue Eingabeelemente erstellt werden. Dadurch schlägt das Postback aufgrund unbekannter Elemente fehl. Die beste Problemumgehung ist, nach Möglichkeit jede Benutzeroberfläche auf dem Server wiederzugeben und mithilfe des Anzeigeattributs für Cascading Stylesheets auf dem Client zu verbergen. Auf diese Weise wird jede Benutzeroberfläche, die Sie verwenden werden, im Ereignisvalidierungsfeld registriert. Wenn Sie benutzerdefinierte Steuerelemente schreiben, sollten Sie diese mit dem SupportsEventValidation-Attribut versehen, um diese Funktion zu aktivieren.

Der PostBack-Mechanismus
Von der ASP.NET-Seite in Abbildung 1 werden Daten zurückgegeben, wenn der Benutzer auf die Schaltfläche klickt. Dies liegt daran, dass das Tag <asp:Button> als HTML-Element zum Übermitteln von <input> wiedergegeben wird. Wenn der Benutzer auf ein Feld zum Übermitteln von Eingaben klickt, wird vom Browser das HTML-Clientereignis onsubmit ausgelöst. Anschließend wird die neue Anforderung auf der Basis der Inhalte des übermittelten Formulars vorbereitet. Die gesendete HTTP-Anforderung enthält zusätzliche Informationen, mit der die ID der Schaltfläche ausgewertet wird.
Mit der Seitenklasse wird der Hauptteil der HTTP-Anforderung gescannt, um zu sehen, ob eines der gesendeten Felder mit der ID eines Schaltflächen-Steuerelements in der ASP.NET-Seite übereinstimmt. Wird eine Übereinstimmung gefunden, wird dieses Schaltflächen-Steuerelement aufgerufen, um jeglichen Code auszuführen, der mit dessen Click-Ereignis verknüpft ist. Genauer gesagt wird anhand der Seitenklasse überprüft, ob das übereinstimmende Schaltflächen-Steuerelement die IPostBackEventHandler-Schnittstelle unterstützt. Wenn das der Fall ist, wird die Methode RaisePostbackEvent für die Schnittstelle aufgerufen. Bei einem Schaltflächen-Steuerelement löst die Methode das serverseitige Click-Ereignis aus.
So weit, so gut. Doch was geschieht, wenn die Seite stattdessen ein LinkButton-Steuerelement enthält? In Abbildung 5 ist das Markup für eine ASP.NET-Seite dargestellt, das mit dem der Seite in Abbildung 1 identisch ist, mit der Ausnahme, dass anstelle der Schaltfläche „Submit“ eine LinkButton-Schaltfläche verwendet wird. Wie Sie sehen können, beinhaltet das Markup zwei weitere verborgene Felder, __EVENTTARGET und __EVENTARGUMENT, und ein wenig JavaScript-Code. Das href-Ziel der Verknüpfungsschaltfläche ist an die Skriptfunktion __doPostback gebunden. Das bedeutet, dass die Funktion aufgerufen wird, wenn festgestellt wird, dass im Client auf das Element geklickt wurde. Die Funktion __doPostback wird in der Seite durch den wiedergebenden Code des Steuerelements LinkButton ausgeben. Dieses füllt die Felder __EVENTTARGET und __EVENTARGUMENT mit den richtigen Informationen aus und löst anschließend über das Skript das Postback aus. In diesem Fall enthält der Hauptteil der HTTP-Postbackanforderung schlicht die Eingabefelder der Seite, und keine zurückgegebenen Daten verweisen auf die Schaltfläche Submit.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html  >
<head><title>Test page</title></head>
<body>
  <form name="form1" method="post" action="LinkBtn.aspx" id="form1">
    <div>
      <input type="hidden" name="__EVENTTARGET" 
        id="__EVENTTARGET" value="" />
      <input type="hidden" name="__EVENTARGUMENT" 
        id="__EVENTARGUMENT" value="" />
      <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" 
        value="/wEP ... Eag==" />
    </div>

    <script type="text/javascript">
    <!--
      var theForm = document.forms['form1'];
      if (!theForm) {
        theForm = document.form1;
      }
      function __doPostBack(eventTarget, eventArgument) {
        if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
          theForm.__EVENTTARGET.value = eventTarget;
          theForm.__EVENTARGUMENT.value = eventArgument;
          theForm.submit();
        }
      }
    // -->
    </script>

    <div>
      <input name="TextBox1" type="text" value="" id="TextBox1" /> 
      <a id="LinkButton1" 
        href="http://javascript:__doPostBack('LinkButton1','')">Click me</a> 
      <hr />
      <h1></h1>
    </div>

    <div>
      <input type="hidden" name="__EVENTVALIDATION" id=
         "__EVENTVALIDATION" 
        value="/wEW ... JbJ" />
    </div>
  </form>
</body>
</html>

Wie erkennt ASP.NET das Steuerelement, das für die Verarbeitung des Postbacks verantwortlich ist? Wenn keine Steuerelemente, auf die im Hauptteil der Anforderung verwiesen wird, die Schnittstelle IPostBackEventHandler implementieren, sucht die Seitenklasse ggf. nach dem verborgenen Feld __EVENTTARGET. Es wird davon ausgegangen, dass es sich bei den Inhalten des Feldes um die ID des Steuerelements handelt, das das Postback ausgelöst hat. Wenn dieses Steuerelement die Schnittstelle IPostBackEventHandler implementiert, wird die Methode RaisePostbackEvent aufgerufen. Bei einem LinkButton-Steuerelement führt dies zum Aufruf des Serverereignisses Click.

Analyse von Klassencode
Mit dem ASPX-Markup wird das Layout einer ASP.NET-Seite definiert und Größe, Stil und Position der enthaltenen Steuerelemente werden festgelegt. Das Markup enthält jedoch keine Logik, außer möglicherweise etwas Client-Skriptcode und jeglichen Visual Basic- oder C#-Inlinecode, über den Sie u. U. verfügen. Initialisierungscode, Ereignishandler und alle Hilfsroutinen werden in der Regel in eine separate Begleitdatei geschrieben, die Code-Behind-Datei genannt wird:
public partial class Test : System.Web.UI.Page
{
  protected void Page_Load(object sender, EventArgs e)
  {
    ...
  }
  protected void Button1_Click(object sender, EventArgs e)
  {
    ...
  }
}
Die Klasse in der Codedatei erbt direkt oder indirekt von System.Web.UI.Page. Die Codedatei und das Markup repräsentieren erforderliche, aber unterschiedliche Informationen. Um die ASP.NET-Seite vollständig darzustellen, müssen sie zu einer Seitenklasse zusammengefügt werden, die die Logik der Codedatei und die Layoutdaten der Markupdatei beinhaltet. Bei der Klassencodedatei handelt es sich bereits um eine Seitenklasse, aber es fehlen noch zwei wichtige Informationen: die Liste der untergeordneten Serversteuerelemente, mit denen die Benutzeroberfläche ausgefüllt wird, und die Deklaration von Klassenmitgliedern, mit denen die verschiedenen Serversteuerelemente identifiziert werden.
Jedes Mal, wenn der Autor der Seite in ASP.NET 1.x ein Steuerelement auf dem Webformular ablegt, fügt Visual Studio .NET 2003 automatisch eine neue Zeile zur Codedatei hinzu, um ein Klassenelement zu erstellen, das das soeben abgelegte Serversteuerelement verarbeitet. Dadurch wird alles recht gut synchronisiert, doch häufig stoßen Entwickler aufgrund eines Mangels an Klassenelementen oder dem Vorhandensein von nutzlosen Klassenelementen auf Kompilierungsfehler.
In ASP.NET 2.0 wurde dieses Problem auf elegante Weise gelöst. Sie können partielle Klassen bzw. an eine Assembly gebundene Möglichkeit auf Codeebene eingeben, die nichts mit Objektorientierung zu tun hat, um das Verhalten einer Klasse zu erweitern. In .NET Framework 2.0 kann eine Klassendefinition zwei und mehr Dateien umspannen. Jede Datei enthält ein Fragment der endgültigen Klassendefinition, und der Compiler kümmert sich um die Zusammenführung partieller Definitionen, um eine einzelne, einheitliche Klasse zu bilden. Alle Fragmente müssen über dieselbe Signatur verfügen, und die endgültige Klassendefinition muss syntaktisch korrekt sein.
Als Nächstes wird dynamisch eine zweite partielle Klasse generiert, um alle Steuerelementmembers aufzulisten. Die beiden partiellen Klassen werden zur Kompilierungszeit zusammengeführt. Wenn die ASPX-Markupdatei analysiert wird, um die temporäre Klasse ASP.test_aspx zu erstellen, erbt diese Klasse aus der kombinierten Codedatei in ihrer endgültigen Version. Wenn die ASP.NET-Seite nicht an eine Codedatei gebunden ist, ihr Code jedoch integriert ist, erbt die dynamische Seitenklasse von System.Web.UI.Page und nimmt den gesamten Inlinecode in ihren Hauptteil auf.
Es gibt noch sehr viel über die dynamische Seitenkompilierung zu lernen, doch das ist Stoff für einen zukünftigen Artikel.

Senden Sie Fragen und Kommentare für Dino in englischer Sprache an cutting@microsoft.com.


Dino Esposito ist Mentor von „Solid Quality Learning“ und Autor von Programming Microsoft ASP.NET 2.0 (Microsoft Press, 2005). Dino arbeitet in Italien und ist ein weltweit gefragter Referent bei Branchenveranstaltungen. Sie können Dino unter cutting@microsoft.com (in englischer Sprache) erreichen oder dem Weblog unter weblogs.asp.net/despos (in englischer Sprache) beitreten.

Page view tracker