MSDN Magazin > Home > Ausgaben > 2007 > April >  Cutting Edge: Unterklassen und Überschreib...
Cutting Edge
Unterklassen und Überschreiben von ASP.NET-Seiten (Teil I)
Dino Esposito

Codedownload verfügbar unter: CuttingEdge2007_04.exe (874 KB)
Browse the Code Online
Ein Kunde kam vor kurzem auf mich zu und sagte „Wir müssen für einige ASP.NET-Seiten Änderungen einfügen. Können Sie uns dabei helfen?“ Wie jeder Berater sagte ich prompt „Natürlich. Worum genau geht es?“ Der Kunde hatte allerdings nicht viel mehr zu bieten als die URLs für die Seiten. Mit anderen Worten, der Kunde musste ASP.NET-Seiten ändern, verfügte jedoch nicht über den Quellcode. Zunächst sah ich eine Menge Schwierigkeiten auf mich zukommen, je mehr ich die Sache aber mit dem Kunden besprach, desto interessanter schien das Problem zu werden.
Zum Erstellen einer abgeleiteten Klasse ist der Quellcode nicht erforderlich, daher ist es möglich, auch das Verhalten einer ASP.NET-Seite ohne den Quellcode zu ändern. In der Windows-Programmierung werden normalerweise Nachrichten auf niedriger Ebene verknüpft und Unterklassen eines Fensters erstellt. In ASP.NET könnte vielleicht durch Verknüpfen von Seitenereignissen sowohl das Seitenverhalten als auch die Ausgabe überschrieben werden.
Nach einigem Nachdenken stellte ich eine Liste realistischer Szenarios auf, in denen möglicherweise das Laufzeitverhalten einer Seite ohne Aktualisieren des Quellcodes geändert werden muss. Außerdem habe ich eine Reihe von Verfahren entworfen, die für diese Aufgabe verwendet werden können. In diesem zweiteiligen Artikel werden die Verfahren im Einzelnen beschrieben. Es wird aufgezeigt, wie die Benutzeroberfläche und das Verhalten von ASP.NET-Seiten lediglich mit schreibgeschütztem Zugriff oder sogar ganz ohne Zugriff auf den Quellcode geändert werden kann.

Mögliche Szenarios
Angenommen, eine bestimmte Gruppe von Benutzern führt eine personalisierte Version einer Webanwendung aus, und diese Anwendung erfordert Remote-Debuggen oder Profilerstellung. Sie können den Dienst nicht unterbrechen, müssen jedoch trotzdem herausfinden, wo das Problem liegt. Leistungsindikatoren und integrierte Ablaufverfolgungsfunktionen sind möglicherweise nicht ausreichend. Außerdem verfügen die bereitgestellten Seiten möglicherweise nicht einmal über eingebundene oder aktivierte Ablaufverfolgungsanleitungen. Wenn dies der Fall ist, müssen Sie zumindest entfernt eingreifen, um einen Ablaufverfolgungscode zu injizieren.
Temporäre Änderungen einer Vielzahl von Seiten ist eine weitere Situation, in der dies notwendig sein kann. Für eine einzelne Seite können Sie einfach eine Kopie der alten Seite anfertigen und diese ersetzen. Das Aktualisieren mehrerer Seiten kann jedoch schwieriger sein und die Verwaltung um einiges komplexer gestalten.
Ein weiteres Szenario entsteht, wenn der Schreibzugriff auf den Quellcode durch eine Unternehmensrichtlinie untersagt ist. In diesem Fall ist der Quellcode zum Lesen verfügbar, kann jedoch nicht geändert werden. Erweiterungen zur Site sind aber möglich.
Die letzte und schwerwiegendste Situation (die ich wirklich schon einmal erlebt habe) ist, wenn das Unternehmen eine Webanwendung ausführt, für die der Quellcode aus irgendeinem Grund nicht mehr verfügbar ist.
Es gibt also eine ganze Reihe von Szenarios, in denen das Laufzeitverhalten von Seiten möglicherweise ohne Aktualisieren des Quellcodes geändert werden muss. Wie würde man dabei am besten vorgehen?

Mögliche Verfahren
Es gibt eine Reihe von Verfahren zum Ändern einer laufenden ASP.NET-Seite ohne Aktualisieren des Quellcodes. Abbildung 1 listet einige dieser Ansätze auf. Nicht alle Verfahren sind in allen Szenarios gültig, und einige Verfahren können gemeinsam eingesetzt werden. Die verschiedenen Verfahren erfordern möglicherweise das Schreiben eines neuen HTTP-Moduls oder HTTP-Handlers, oder die Eingabe von Änderungen in die web.config-Datei. In den meisten Fällen müssen Sie die Anwendung neu starten. Änderungen der Dateien web.config oder global.asax und Zusätze zum Bin-Ordner oder dem App_Code-Ordner starten die Anwendung sogar automatisch neu.

Verfahren Implementierung
Zugriff auf die Steuerelementstruktur HTTP-Modul
Ändern der Seitenbasisklasse web.config
Ersetzen des Steuerelements web.config
Buildprovider Assembly
Umleiten einer Seite HTTP-Handler
Überschreiben einer Seite Umschreiben von HTTP-Handler oder URL
Mithilfe von HTTP-Modulen können alle Ereignisse im Lebenszyklus der Anwendung verknüpft und der HTTP-Handler für die Wiedergabe der Seite erfasst werden. Wenn Sie über einen solchen Verweis verfügen, können Sie Handler für Seitenereignisse (wie z. B. Page_Load und Page_PreRender) verwalten und Steuerelemente hinzufügen oder entfernen, Verknüpfungen zu Ressourcen und Bildern ändern und CSS-Klassen hinzufügen oder entfernen. Ebenso kann ein HTTP-Handler für das Ersetzen einer einzelnen Seite verwendet werden. Ferner kann das Umschreiben von URLs für die Umleitung von Benutzern zu Seiten verwendet werden, die bei der ersten Bereitstellung der Anwendung noch nicht einmal vorhanden sind.
Wenn Sie Schwierigkeiten mit einem bestimmten Steuerelement haben, verwenden Sie das Element <tagMapping> der web.config-Datei, um den Seitenparser automatisch zu einem neuen Steuerelement umzuleiten, das aus dem alten abgeleitet wurde. Dieses Verfahren wird in meinem nächsten Artikel erläutert. Konzentrieren wir uns zunächst auf Tools und Verfahren, die für das Überschreiben von Ereignissen auf Seitenebene und das schnelle Ändern der Benutzeroberfläche einer Seite verwendet werden können, ohne den Quellcode ändern zu müssen.

Abfangen des Seitenhandlers
Die Verarbeitung jeder ASP.NET-Anforderung verursacht eine Reihe von Ereignissen auf Anwendungsebene, wie in Abbildung 2 dargestellt. Die Anforderung beginnt mit dem BeginRequest-Ereignis und endet mit dem EndRequest-Ereignis. Ein Systemobjekt namens HttpApplication steuert die Anforderungsverarbeitung und stellt sicher, dass spezielle Komponenten – HTTP-Module – Handler für einige dieser Ereignisse auf Anwendungsebene registrieren können. Wenn die Anforderung authentifiziert und autorisiert (und nicht durch den Ausgabezwischenspeicher gelöst ist), wird der Anforderung ihre eigene Handlerkomponente zugewiesen.
Abbildung 2 Anforderungen 
Jede ASP.NET-Anforderung benötigt zur Verarbeitung eine spezielle Komponente, den HTTP-Handler. Ein Verweis zum für die aktuelle Anforderung verantwortlichen HTTP Handler ist verfügbar, wenn im Laufe der Anwendungsausführung das Ereignis PostMapRequestHandler ausgelöst wird.
Ereignisse auf Anwendungsebene können auf zwei Arten verknüpft werden. Sie können Handler in der global.asax-Datei schreiben (eine der Quelldateien der Anwendung), oder Sie können ein benutzerdefiniertes HTTP-Modul schreiben, das einen Listener für das angegebene Ereignis registriert. Auf Funktionsebene sind beide Ansätze vergleichbar. Wenn Sie jedoch HTTP-Module verwenden, können Sie Ihr Ziel diskreter durch einfaches Hinzufügen einer neuen Komponente und Ändern der Datei web.config erreichen. In beiden Fällen wird die Anwendung neu gestartet.
Das erste Anwendungsereignis, das nach dem Bestimmen des Seitenhandlers ausgelöst wird, ist PostMapRequestHandler. Wie in Abbildung 3 gezeigt, kann ein HTTP-Modul einen Listener für dieses Ereignis registrieren und benachrichtigt werden, wenn der Handler für eine ASP.NET-Anforderung gefunden wird. Wenn Sie dies nur für ein paar Seiten durchführen müssen, möchten Sie möglicherweise ungewünschte Seiten im Modulcode herausfiltern.
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Web.Compilation
Imports System.Web.Hosting
Imports System.IO
Imports System.Text

Namespace Samples

    Public Class SubclassModule : Implements IHttpModule

        Private _app As HttpApplication

#Region “IHttpModule”
        Public Sub Dispose() Implements IHttpModule.Dispose
        End Sub

        Public Sub Init(ByVal context As HttpApplication) _
                Implements IHttpModule.Init
            If context Is Nothing Then
                Throw New ArgumentNullException(“[context] argument”)
            End If

            ‘ Cache the HTTP application reference
            _app = context

            ‘ Map the app event to hook up the page
            AddHandler context.PostMapRequestHandler, _
                AddressOf OnPostMapRequestHandler
        End Sub
#End Region
#Region “App Event Handlers”
        Private Sub OnPostMapRequestHandler( _
                ByVal source As Object, ByVal e As EventArgs)
            ‘ Get the just mapped HTTP handler and cast to Page
            Dim pageHandler As IHttpHandler = Nothing
            If TypeOf _app.Context.Handler Is System.Web.UI.Page Then
                pageHandler = _app.Context.Handler
            End If

            ‘ If OK, register an event handler for PreRender
            If Not pageHandler Is Nothing Then
                HookUpPage(pageHandler)
            End If
        End Sub
#End Region

#Region “Helpers”
        Private Sub HookUpPage(ByVal pageHandler As Page)
            ‘ Wire up as many application events as needed
            AddHandler pageHandler.Load, AddressOf OnLoad
            ‘ ...
        End Sub

        Private Sub OnLoad(ByVal sender As Object, ByVal e As EventArgs)
            Dim pageHandler As Page = DirectCast(sender, Page)
            ‘ TODO: add your code here
        End Sub
#End Region

    End Class
End Namespace
Ein HTTP-Modul ist eine Klasse, die zwei Methoden implementiert: Init und Dispose. Die Init-Methode wird einmal aufgerufen, wenn die Anwendung startet. Wie in Abbildung 3 registriert die Init-Methode Listener, die für jede ASP.NET-Anforderung aufgerufen werden. Normalerweise überprüfen Sie die URL der eingehenden Anforderung innerhalb der OnPostMapRequestHandler-Methode. Hier ein Beispiel:
Sub OnPostMapRequestHandler( _
       ByVal source As Object, ByVal e As EventArgs)
    Dim url as Uri
    url = _app.Context.Request.Url
    If Not url.AbsolutePath.Equals( _
           "/WebSite1/default.aspx") Then
       Return
    End If
    ...
End Sub
Eine ASP.NET-Anforderung durchläuft die Laufzeitpipeline gemeinsam mit einem Systemobjekt, das den Kontext der laufenden Anforderung darstellt. Dieses Objekt ist eine Instanz der HttpContext-Klasse. Handler, eine der Eigenschaften auf dem HttpContext-Objekt, enthält einen Verweis auf das HTTP-Handlerobjekt für die Bereitstellung der Anforderung. Sofern kein benutzerdefinierter HTTP-Handler für die angeforderte Ressource definiert wurde, wird eine ASPX-Anforderung von einer Instanz einer Klasse verwaltet, die von der System.Web.UI.Page-Klasse erbt. An dieser Stelle verfügen Sie über einen Verweis auf das Objekt, das für die Verarbeitung der Seite verwendet wird, und Sie können neue Handler dynamisch für allgemeine Seitenereignisse wie z. B. PreRender und Load hinzufügen. Hier ein Beispiel:
Private Sub HookUpPage(ByVal pageHandler As Page)
   ' Wire up as many application events as needed
   AddHandler pageHandler.Load, AddressOf OnLoad
    ...
End Sub
Genauer gesagt handelt es sich bei dem Objekt, das an die Handlereigenschaft gebunden ist, um eine Instanz der dynamisch erstellten Page-Klasse, die auf dem Markup basiert, das Sie in der ASPX-Serverressource geschrieben haben. Der Seitenhandler enthält also einen Ereignishandler für das Load-Ereignis der Seite, wenn Sie eine Page_Load-Methode in die codebehind-Klasse gestellt haben.
Das AddHandler-Schlüsselwort in Visual Basic® (und der Operator += in C#) fügt einen neuen Handler zur Kette von Delegaten hinzu, die an das bestimmte Ereignis gebunden sind. Demzufolge wird ein beliebiger Code, den Sie zum Handler-Objekt hinzufügen, nach allen entsprechenden Handlern ausgeführt, die in der ASPX der Seite definiert sind. Der Code in der OnLoad-Methode, auf den oben verwiesen wird, wird nach dem Code in Page_Load ausgeführt.
Was können Sie an dieser Stelle in OnLoad tun? Fast alles, solange Sie die Struktur der Seite und die Elemente in deren Steuerelementstruktur kennen. Wenn Sie den Quellcode der Seite lesen können, sind diese Informationen einfach zu erfassen. Ansonsten benötigen Sie ein Abbild der Steuerelementstruktur der Seite. Eine Möglichkeit hierfür ist das Aktivieren der Seitenverfolgung und Umleiten der Ausgabe an das integrierte ASP.NET trace.axd-Tool. Sie können die Ablaufverfolgung über die web.config-Datei aktivieren, wie hier gezeigt:
<system.web>
    <trace enabled="true" />
</system.web>
Der nächste Schritt beinhaltet den Aufruf des trace.axd-Dienstprogramms auf der Website, die Sie bearbeiten, und eine Ansicht der Steuerelementstruktur. Abbildung 4 zeigt das Dienstprogramm in Aktion.
Abbildung 4 Trace.axd in Aktion (Klicken Sie zum Vergrößern auf das Bild)
Als Beispiel habe ich eine neue Website mithilfe des Personal Starter Kit erstellt (siehe Abbildung 5). Ich werde demonstrieren, wie Seiten darin ohne Aktualisieren des Quellcodes geändert werden können. Insbesondere will ich aufzeigen, wie der Eingabefokus automatisch in das Benutzernamensfeld des Anmeldefelds verschoben werden kann.
Abbildung 5 Standardversion der persönlichen Website (Klicken Sie zum Vergrößern auf das Bild)

Hinzufügen neuer Ereignishandler
Wenn Sie den Quellcode bearbeiten könnten, würden Sie wahrscheinlich ein oder zwei Zeilen zum Page_Load-Handler hinzufügen und die Methode Focus im Verweis des Login-Steuerelements aufrufen. Aus der Steuerelementstruktur (teilweise in Abbildung 4 gezeigt) ist ersichtlich, dass die Seite ein Login-Steuerelement namens Login1 enthält. Der folgende Code ruft ein untergeordnetes Steuerelement aus der Seitenstruktur ab, verschiebt den Eingabefokus in seinen Bereich, und setzt den Titel der Seite zurück.
Private Sub OnLoad(ByVal sender As Object, ByVal e As EventArgs)
   Dim pageHandler As Page = DirectCast(sender, Page)

   ' Move the input focus to control Login1
   Dim ctl As Control = FindControlRecursive(pageHandler, "Login1")
   ctl.Focus()

   ' Change title of the page
   pageHandler.Title = "This page has been hooked up"
End Sub
Mit der FindControl-Methode auf jeder ASP.NET-Steuerelementklasse können Sie nur die direkt untergeordneten Elemente des Steuerelements anzeigen. Die im Steuerelement verwurzelte Unterstruktur kann nicht eingesehen werden. Für diese Funktionalität müssen Sie Ihre eigene rekursive Suchmethode schreiben, wie ich es in meiner FindControlRecursive-Methode getan habe.
Gibt es eine Möglichkeit, das Durchsuchen der Steuerelementstruktur für jede Anforderung einer bestimmten Seite zu vermeiden? Eigentlich nicht. Die Steuerelementinstanz kann nicht zwischengespeichert werden, da die Seitenstruktur für jede Anforderung neu erstellt wird. Ohne Aktualisieren des Quellcodes können außerdem keine zusätzlichen Eigenschaften zur Codebehind-Seite hinzugefügt werden, um ein bestimmtes Steuerelement über eine Eigenschaft verfügbar zu machen. Selbstverständlich wäre es sehr viel einfacher, wenn Sie Kopien vorhandener Quelldateien anfertigen und Klassen oder vorkompilierte Assemblys ersetzen könnten.
Abbildung 6 zeigt eine geänderte Version der Beispielsite. Der Titel der Seite wurde über ein HTTP-Modul geändert, der Eingabefokus wurde zum Anmeldesteuerelement verschoben, und das Thema der Seite wurde über externen Code geändert. Zum Ändern des Themas müssen Sie eine Verbindung zum PreInit-Ereignis herstellen:
Private Sub HookUpPage(ByVal pageHandler As Page)
    AddHandler pageHandler.PreInit, AddressOf OnPreInit
    AddHandler pageHandler.Load, AddressOf OnLoad
End Sub

Private Sub OnPreInit(ByVal sender As Object, _
    ByVal e As EventArgs)
    Dim pageHandler As Page = DirectCast(sender, Page)
    pageHandler.Theme = "Black"
End Sub
Abbildung 6 Geänderte persönliche Website (Klicken Sie zum Vergrößern auf das Bild)
Seitenmanipulationen erfordern die Bereitstellung eines Verweises für die Steuerelementstruktur der Seite. Die Sammlung Controls des Seitenhandlers ist die oberste Sammlung von Steuerelementen in der Seite und Startpunkt für die Navigation der Steuerelementstruktur.

Bereitstellen eines Verweises für die Steuerelementstruktur
Angenommen, Ihr Kunde benötigt eine schnelle Methode, neuen Inhalt wie die aktuellsten Nachrichten zur Site hinzuzufügen. Der Besitz eines Verweises zum Seitenhandler ist dabei ausschlaggebend. Als ersten Schritt kann ein neuer Handler für das PreRender-Ereignis in der HookUpPage-Methode des Beispiel-HTTP-Moduls hinzugefügt werden.
AddHandler pageHandler.PreRender, AddressOf OnPreRender
Rufen Sie im Handler zunächst den Seitenverweis ab, und suchen Sie daraufhin nach dem gewünschten Injektionspunkt, wie in Abbildung 7 gezeigt. Sie möchten einen Hinweis darüber bereitstellen, dass die Website durch eine externe Komponente gesteuert wird. Zu diesem Zweck wollen Sie eine neue Beschriftung ganz oben auf jeder Seite anbringen, die vom HTTP-Modul verwaltet wird. Der folgende Codeausschnitt richtet ein Label-Steuerelement ein und fügt dieses dann der Controls-Sammlung der Seite hinzu:
Sub AddStaticText(ByVal pageHandler As Page)
    Dim msg As String = ConfigurationManager.AppSettings("StaticMessage")
    Dim lbl As New Label
    lbl.ID = "SubclassModule_Label1"
    lbl.BackColor = Color.White
    lbl.ForeColor = Color.Red
    lbl.Text = String.Format("<div>{0}</div>", msg)
    pageHandler.Controls.AddAt(0, lbl)
End Sub
Private Sub OnLoad(ByVal sender As Object, ByVal e As EventArgs)
    Dim pageHandler As Page = DirectCast(sender, Page)
    If pageHandler Is Nothing Then Return

    ‘ Move the input focus to control Login1
    Dim controlName As String = “Login1”
    Dim ctl As Control = FindControlRecursive(pageHandler, controlName)
    If Not ctl Is Nothing Then ctl.Focus()

    ‘ Change the page title
    pageHandler.Title = “This page has been hooked up”

    ‘ Add a new control tree with postback support
    AddLinkButton(pageHandler)
End Sub

Private Sub OnPreRender(ByVal sender As Object, ByVal e As EventArgs)
    Dim pageHandler As Page = DirectCast(sender, Page)
    If pageHandler Is Nothing Then Return

    ‘ Add static text (no postback)
    AddStaticText(pageHandler)
End Sub

Private Sub AddStaticText(ByVal pageHandler As Page)
    Dim msg As String = ConfigurationManager.AppSettings(“StaticMessage”)
    Dim lbl As New Label
    lbl.ID = “SubclassModule_Label1”
    lbl.BackColor = Color.White
    lbl.ForeColor = Color.Red
    lbl.Text = String.Format( _
        “<div style=’margin:0;width:100%;font-size:20pt’;>{0}</div>”, msg)
    pageHandler.Controls.AddAt(0, lbl)
End Sub

Private Sub AddLinkButton(ByVal pageHandler As Page)
    Dim controlName As String = “Main”
    Dim ctl As Control = FindControlRecursive(pageHandler, controlName)
    If ctl Is Nothing Then Return

    Dim msg As String = ConfigurationManager.AppSettings(“LinkMessage”)
    Dim link As New LinkButton
    link.ID = “SubclassModule_LinkButton1”
    link.ToolTip = “Click to hide the ‘Create account’ button”
    link.BackColor = Color.Blue
    link.ForeColor = Color.Yellow
    link.Text = String.Format( _
        “<hr><div style=’width:100%;font-size:30pt’;>{0}</div><hr>”, msg)
    AddHandler link.Click, AddressOf SubclassModule_LinkButton1_Click
    ctl.Controls.AddAt(0, link)
End Sub

Private Sub SubclassModule_LinkButton1_Click( _
        ByVal sender As Object, ByVal e As EventArgs)
    Dim pageHandler As Page = DirectCast(_app.Context.Handler, Page)
    If pageHandler Is Nothing Then Return

    Dim controlName As String = “Image1”
    Dim ctl As WebControl = DirectCast( _
        FindControlRecursive(pageHandler, controlName), WebControl)
    If ctl Is Nothing Then Return

    ctl.Visible = False
End Sub
Die Eigenschaft Controls ist eine Instanz der ControlCollection-Klasse und stellt zwei Methoden für das Hinzufügen neuer Steuerelementinstanzen bereit: Add und AddAt. Add fügt der Sammlung neue Steuerelemente hinzu. AddAt ist flexibler und ermöglicht die Angabe eines 0-basierten Index für die gewünschte Position. Der Beschriftungstext kann ein beliebiges HTML-Markup enthalten und kann (ganz oder teilweise) von allen externen Ressourcen aus gelesen werden, einschließlich Datenbanken, XML-Dokumenten und der web.config-Datei.
Ein Beschriftungs- oder Literalsteuerelement eignet sich gut für die Anzeige von dynamischem Inhalt, jedoch nicht, wenn Sie Postbackereignisse erzeugen und verarbeiten möchten. Fügen Sie stattdessen eine Verknüpfungsschaltfläche hinzu. Wenn die Verknüpfungsschaltfläche dazu gedacht ist, den Benutzer zu einer neuen Seite zu leiten, gibt es keinen erheblichen Unterschied zum eben beschriebenen Szenario. Wenn die Verknüpfungsschaltfläche allerdings zu einem Postback führen, Code auf dem Server ausführen und die Seite daraufhin aktualisieren soll, müssen Sie die Lösung nochmals überdenken.
So offensichtlich es auch scheinen mag, ist das Hinzufügen eines Steuerelements vor der eigentlichen Wiedergabe noch nicht Teil der Steuerelementstruktur, wenn das Postbackereignis verarbeitet wird. Die ASP.NET-Seitenlaufzeit ermittelt das Ziel des Postbacks und löst das Ereignis vor der Wiedergabe aus. Der Absender des Postbacks ist in die HTTP-Anforderung geschrieben.
Die ASP.NET-Laufzeit muss eine Übereinstimmung zwischen dem Absendernamen und einem vorhandenen Steuerelement finden, um das Postbackereignis auszulösen. Aus diesem Grund muss die Verknüpfungsschaltfläche früher als das PreRender-Ereignis erstellt werden. Eine guter Zeitpunkt hierfür ist das Load-Ereignis. Angenommen, die eingefügte Steuerelementstruktur enthält verschiedene Eingabefelder, wie Kontrollkästchen oder Textfelder. Der Status dieser Steuerelemente muss mit den vom Kunden bereitgestellten Daten aktualisiert werden. Damit die Aktualisierung automatisch stattfindet, dürfen die Steuerelemente nicht nach dem Load-Ereignis erstellt werden. In diesem Fall garantiert die ASP.NET-Laufzeit, dass der Anzeigezustand korrekt wiederhergestellt wird und dass alle bereitgestellten Daten berücksichtigt werden.
Fügen Sie im Load-Ereignis ein neues LinkButton-Steuerelement sowie einen Handler für das Click-Ereignis hinzu:
AddHandler link.Click, AddressOf SubclassModule_LinkButton1_Click
Wenn der Benutzer auf die Verknüpfung klickt, wird die Seite zurückgegeben, sodass ASP.NET über alle Informationen für eine korrekte Auflösung des Rückgabeziels verfügt. Als Folge daraus wird der angegebene Ereignishandler aufgerufen.
Was können Sie nun aus dem Handler heraus tun? Mit entsprechenden Kenntnissen so gut wie alles. Ohne das Quellmarkup und die Codebehind-Klassenumgebung können bestimmte Aufgaben jedoch eine Herausforderung darstellen. Angenommen, Sie möchten den Status eines anderen Steuerelements in der Seite ändern. Zum Beispiel soll der Benutzer auf die neu hinzugefügte Verknüpfungsschaltfläche klicken und eine andere Schaltfläche in der Seite deaktivieren können. Zugegebenermaßen macht diese Funktionalität wenig Sinn, wenn sie so dargestellt wird. Getrennt gesehen sind dies jedoch die Aufgaben, die möglicherweise für verknüpfte und Unterklassen-ASP.NET-Seiten durchgeführt werden müssen.
Die Frage ist also, wie das Seitenobjekt innerhalb des Handlers abgerufen wird. Das Seitenobjekt wird für den Zugang zur gesamten Steuerelementstruktur und für die Suche nach dem Steuerelement benötigt, mit dem gearbeitet werden soll. Ein Verweis auf den aktuellen HTTP-Handler, also das Seitenobjekt, ist in der Handlereigenschaft des HttpContext-Objekts gespeichert. Normalerweise sind diese Art von Tricks nicht erforderlich, um den Seitenverweis der Codebehind-Klasse zu erhalten, ausgehend von einem HTTP-Modul ist dies jedoch unbedingt erforderlich:
Sub LinkButton1_Click(sender As Object, e As EventArgs)
    Dim pageHandler As Page = DirectCast(_app.Context.Handler, Page)
    If pageHandler Is Nothing Then Return
    ...
End Sub
Mithilfe des Seitenobjekts wird eine weitere rekursive Suche nach dem gewünschten Steuerelement gestartet. Beispielsweise möchten Sie die Schaltfläche „Konto erstellen“ (in Abbildung 5 gezeigt) deaktivieren. Eine schnelle Analyse der Ausgabe der Ablaufverfolgung und der HTML-Quelle der Seite zeigt, dass sich dahinter keine echte Schaltfläche verbirgt. Bei der Erstellung von persönlichen Websites werden eine Reihe gängiger Verfahren eingesetzt. Mithilfe der Layout-Funktionen von Cascading Stylesheets und einer Vielzahl von Bildern wird eine ansprechendere Benutzeroberfläche bereitgestellt. Die Schaltfläche „Konto erstellen“ wird daher wie folgt ausgedrückt:
<a href="http://register.aspx">
  <asp:image id="Image1" runat="server" />
</a>
In der Sammlung Controls finden Sie einen Verweis auf das Image-Steuerelement, jedoch nichts speziell für das <a>-Tag, da dieses nicht über das runat="server"-Attribut verfügt. In diesem Punkt sind die hier verwendeten Verfahren zum Erstellen von Unterklassen sehr eingeschränkt. Das Erstellen von Unterklassen ist keine direkte Programmierung und sollte nur im Notfall verwendet werden. Durch Erstellen von Unterklassen für eine nicht kompatible Seite können nicht alle Funktionen von ASP.NET reproduziert werden. In ASP.NET wird jede Sequenz mit Markuptext, die nicht mit dem runat-Attribut versehen ist, wie Nur-Text behandelt und wörtlich ausgegeben. Des Weiteren wird zusammenhängender Text in einer einzelnen LiteralControl gruppiert, was es für Ersteller von Unterklassen noch schwieriger macht, einen Verweis auf ein Markup und sein Servergegenstück zu finden. Es wäre nützlich, die Kennung des HTML-Tags herauszufinden und entsprechenden Skriptcode einzugeben, der die Arbeit während des Ladens der Seite durchführt. Leider fehlt dem <a>-Tag in diesem Fall sogar das ID-Attribut, was eine Programmierung sowohl auf dem Server als auch dem Client praktisch unmöglich macht. Ein Deaktivieren des Bilds ist möglich, es kann aber trotzdem noch darauf geklickt werden. Eine mögliche Alternative ist das Ausblenden des Steuerelements.
Dim controlName As String = "Image1"
Dim ctl As WebControl = DirectCast( _
    FindControlRecursive(pageHandler, controlName), WebControl)
If ctl Is Nothing Then Return
ctl.Visible = False
Ein weiteres Verfahren für das Bearbeiten der Ausgabe einer vorhandenen Seite ist das Erfassen der Antwort, bevor sie an den Browser gesendet wird. Verknüpfen Sie das PostRequestHandlerExecute-Ereignis von einem HTTP-Modul und weisen Sie einen benutzerdefinierten Stream zur Filtereigenschaft des HttpResponse-Objekts zu. Danach durchläuft beliebiger Text, der in den Antwortstream geschrieben wird, Ihren eigenen Stream und bietet Ihnen die Möglichkeit, die HTML-Quelle voranzuzeigen und möglicherweise erforderliche Änderungen vorzunehmen, z. B. das Ändern von DOM und Skript. Ein Szenario aus der Praxis, in dem dieses Verfahren nützlich ist, ist das Ändern der URL eines als einfaches <a>-Tag eingegebenen Hyperlinks in der Quellseite.

Überschreiben der Seite mit HTTP-Handlern
Das Erstellen von Unterklassen ist ein geeignetes Verfahren für die Eingabe begrenzter Änderungen in eine vorhandene Seite ohne Aktualisieren des Quellcodes. Wenn es möglich ist, eine bestimmte Seite durch eine neue Seite (mit dem gleichen Namen aber anderem Inhalt) zu ersetzen, ist dies auf jeden Fall das bevorzugte Verfahren. Das Erstellen von Unterklassen unterscheidet sich vom Ersetzungsvorgang und sollte nur verwendet werden, wenn keine einfacheren Alternativen zur Verfügung stehen.
Um die Ausgabe einer Seite ohne Aktualisieren des Quellcodes komplett zu ändern, leiten Sie die Seite zu einem anderen HTTP-Handler um. Die Änderung wird in der web.config-Datei folgendermaßen angezeigt:
<httpHandlers>
   <add verb="*" 
        path="register.aspx" 
        type="Samples.Modules.RegisterAspxHandler, Subclass"
        validate="false" />
</httpHandlers>
Jeder Versuch, auf register.aspx zuzugreifen, wird vom angegebenen Handler anstelle der ursprünglichen Seite verarbeitet. Der HTTP-Handler ist natürlich für die Ausgabe verantwortlich, die dem Endbenutzer angezeigt wird. Ein HTTP-Handler ist eine Klasse, die die IHttpHandler-Schnittstelle implementiert. Die endgültige Ausgabe wird innerhalb der ProcessRequest-Methode erzeugt:
Public Sub ProcessRequest(ByVal context As HttpContext) _
       Implements IHttpHandler.ProcessRequest
   context.Response.ContentType = "text/plain"
   context.Response.Write("Hello World")
End Sub
Wenn ein solcher Handler in der web.config-Datei registriert ist, wird die Ausgabe der register.aspx-Seite eine einfache „Hello World“-Nachricht.
Ein HTTP-Handler stellt das Tool auf niedrigster Ebene für die Verarbeitung von ASP.NET-Anforderungen dar. Nach der Einrichtung ist der HTTP-Handlercode der einzige Endpunkt, der für die Anforderung ausgeführt wird. Für Seitenanforderungen bietet ein benutzerdefinierter HTTP-Handler daher keinen Anzeigezustand, kein Postback und keine Lebenszyklusereignisse. Wenn die Anforderung den regelmäßigen Lebenszyklus durchlaufen muss, ziehen Sie die SetRenderMethodDelegate-Methode auf der Page-Klasse in Betracht. Diese Methode bestimmt einen Rückruf, der zur Wiedergabe des Seiteninhalts im Browser aufgerufen wird. Die Methode ist eigentlich auf der Control-Klasse definiert und steht Serversteuerelementen für die Wiedergabe von Inhalt in übergeordneten Steuerelementen zur Verfügung. Hier ein Beispiel:
Private Sub OnPreRenderComplete(object sender, EventArgs e)
   Dim pageHandler As Page = DirectCast(sender, Page)
   If Not pageHandler Is Nothing Then
       Dim method As New RenderMethod(RenderPageContents) 
       pageHandler.SetRenderMethodDelegate(method);
   End If
End Sub

Private Sub RenderPageContents( _
        ByVal output As HtmlTextWriter, _
        ByVal container As Control)
   ...
End Sub
Die Methode wurde für einen ganz bestimmten Einsatz im Framework entworfen und ist nicht für eine öffentliche Anwendung gedacht. Sie ist jedoch eine brauchbare Option, wenn lediglich die Seitenwiedergabe überschrieben werden muss, ohne dass der Lebenszyklus der Seite beeinträchtigt wird. Beachten Sie aber, dass dieses Verfahren Schwierigkeiten verursachen kann, wenn das Wiedergabemethodendelegat zuvor auf eine andere Methode eingestellt war.

Umschreiben von URL
Das Umschreiben von URL ist ein geeignetes Verfahren für die programmgesteuerte Umleitung einer Anforderung zu einer anderen URL. Grundsätzlich besteht das Umschreiben von URL aus einer Änderung der Anwendungsanforderungs-URL in einem frühen Stadium ihres Lebenszyklus, z. B. in BeginRequest. Verwenden Sie zum Umschreiben der URL die RewritePath-Methode des HttpContext-Objekts.
HttpContext context = HttpContext.Current;
context.RewritePath("newpage.aspx");
Das Ergebnis gleicht einer herkömmlichen Umleitung von HTTP 302, außer, dass keine neue physische Anforderung vom Browser gestellt wird. Durch Einbetten des vorhergehenden Codeausschnitts in ein HTTP-Modul (oder durch einfaches Bearbeiten der global.asax-Datei) können Sie das Ziel der aktuellen Anforderung überprüfen und entscheiden, ob sie sie zu einer anderen Seite und einem anderen Inhalt umleiten möchten.
ASP.NET 2.0 unterstützt den Abschnitt <urlMappings> für ein rein deklaratives und bedingungsloses Umschreiben der URL.

Zusammenfassung
Wie jede andere Anwendung besteht eine Website aus Quellcode, ob kompilierter Codebehind-Code, Markup oder Skript. Benutzeroberfläche und das Verhalten der Site hängen von der Quellcodebasis ab. Die einfachste Methode für ein späteres Ändern der Benutzeroberfläche und des Verhaltens ist das Bearbeiten des Quellcodes. Wenn dies jedoch nicht möglich ist, probieren Sie die in diesem Artikel beschriebenen Verfahren.
Beim Vergleich von Abbildung 5 mit Abbildung 6 wird deutlich, was eine extern zur Site hinzugefügte Binärkomponente bewirken kann. Alle Änderungen wurden durch einfaches Hinzufügen einer Komponente, also eines HTTP-Moduls erzielt, nicht durch Aktualisieren des Quellcodes. Beachten Sie, dass Laufzeitänderungen pro Anfrage einen Overhead nach sich ziehen, daher sollten Sie die Site so flexibel und konfigurierbar wie möglich gestalten und eventuelle Änderungen vorhersehen.

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


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

Page view tracker