7. ASP-Serverkomponenten – 1
7.1. Einführung
In diesem Kapitel beschreiben wir die in ASP.NET empfohlene Technologie zum Erstellen der Benutzeroberfläche. Wir wissen, dass es bei der Verarbeitung einer .aspx-Seite durch den Webserver zwei unterschiedliche Phasen gibt:
- Zunächst wird der Seiten-Controller ausgeführt. Dieser besteht aus Code, der sich entweder innerhalb der .aspx-Seite selbst (WebMatrix-Lösung) oder in einer separaten Datei (Visual Studio.NET-Lösung) befindet.
- Anschließend wird der Präsentationscode der .aspx-Seite ausgeführt und in HTML-Code umgewandelt, der an den Client gesendet wird.

ASP.NET bietet drei Tag-Bibliotheken zum Schreiben des Präsentationscodes der Seite:
- Klassische HTML-Tags. Diese haben wir bisher verwendet.
- Serverseitige HTML-Tags
- Web-Forms-Tags
Unabhängig davon, welche Tag-Bibliothek verwendet wird, bleibt die Rolle des Seiten-Controllers dieselbe. Er muss den Wert der dynamischen Parameter berechnen, die im Präsentationscode vorkommen. Bislang waren diese dynamischen Parameter einfach: Es handelte sich um Objekte vom Typ [String]. Wenn wir also im Präsentationscode ein <%=name%>-Tag haben:
- erklärt der Seiten-Controller eine Variable [name] vom Typ [String] und berechnet deren Wert
- Wenn der Seiten-Controller seine Arbeit beendet hat und der Präsentationscode zur Generierung der HTML-Antwort ausgeführt wird, wird das <%=name%>-Tag durch den vom Controller-Code berechneten Wert ersetzt
Wir wissen, dass die Trennung von Controller und View willkürlich ist und dass Controller-Code und View-Code auf derselben Seite gemischt werden können. Wir haben erklärt, warum von diesem Ansatz abgeraten wird, und wir werden weiterhin an der Trennung von Controller und View festhalten.
Die Tags [server HTML] und [WebForms] ermöglichen es Ihnen, Objekte in den Präsentationscode einzubinden, die komplexer sind als das einfache [String]-Objekt. Dies kann manchmal sehr nützlich sein. Nehmen wir das Beispiel eines Formulars mit einer Liste. Diese Liste muss dem Client mit HTML-Code präsentiert werden, der wie folgt aussieht:
<select name="uneListe" size="3">
<option value="opt1">option1</option>
<option value="opt2">option2</option>
<option value="opt3" selected>option3</option>
</select>
Der Inhalt der Liste und die auszuwählende Option sind dynamische Elemente und müssen daher vom Seiten-Controller generiert werden. Wir sind bereits auf dieses Problem gestoßen und haben es durch Einfügen des Tags
Dieses Tag wird durch den [String]-Wert der Variablen [uneListeHTML] ersetzt. Dieser vom Controller berechnete Wert muss den HTML-Code für die Liste enthalten, d. h. „<select name=..>...</select>“. Dies ist nicht besonders schwierig und scheint eine elegante Lösung zu sein, die es vermeidet, Generierungscode direkt in die Präsentationsschicht der Seite einzufügen. Hier gäbe es eine Schleife, in die Tests eingefügt werden müssten, was diese somit stark „überladen“ würde. Dennoch hat diese Methode einen Nachteil. Die Trennung von Steuerung und Darstellung auf einer Seite dient auch dazu, zwei Zuständigkeitsbereiche abzugrenzen:
- den des .NET-Entwicklers, der sich um den Seiten-Controller kümmert,
- den des Grafikdesigners, der sich um den Präsentationsteil der Seite kümmert
Hier sehen wir, dass die Generierung des HTML-Codes für die Liste in den Controller verlagert wurde. Der Grafikdesigner möchte diesen HTML-Code möglicherweise ändern, um das „visuelle“ Erscheinungsbild der Liste anzupassen. Er wäre gezwungen, im [Controller]-Bereich zu arbeiten und damit außerhalb seines Fachgebiets, was das Risiko birgt, versehentlich Fehler in den Code einzufügen.
Server-Tag-Bibliotheken lösen dieses Problem. Sie stellen ein Objekt bereit, das eine HTML-Liste repräsentiert. Die [WebForms]-Bibliothek bietet beispielsweise das folgende Tag:
Dieses Tag repräsentiert ein [ListBox]-Objekt, das vom Seiten-Controller manipuliert werden kann. Dieses Objekt verfügt über Eigenschaften, die die verschiedenen Optionen in der HTML-Liste darstellen und die ausgewählte Option kennzeichnen. Der Seiten-Controller weist diesen Eigenschaften daher die entsprechenden Werte zu. Bei der Ausführung der Präsentationsschicht wird das Tag
durch den HTML-Code ersetzt, der das [ListBox]-Objekt darstellt, d. h. den Code „<select ..>...</select>“. Derzeit gibt es keinen grundlegenden Unterschied zur vorherigen Methode, abgesehen von einem objektorientierten Programmieransatz, was interessant ist. Kehren wir zu unserem Grafikdesigner zurück, der das „Aussehen“ der Liste ändern muss. Server-Tags verfügen über Stilattribute (BackColor, Bordercolor, BorderWidth, ...), mit denen Sie das visuelle Erscheinungsbild des entsprechenden HTML-Objekts festlegen können. Wir können also schreiben:
<asp:ListBox id="ListBox1" runat="server" BackColor="#ffff99"></asp:ListBox></P>
Der Vorteil besteht darin, dass der Grafikdesigner direkt im Präsentationscode arbeitet, um diese Änderungen vorzunehmen. Dies ist ein klarer Vorteil gegenüber der vorherigen Methode. Serverseitige Tag-Bibliotheken vereinfachen somit den Aufbau der Präsentationsschicht von Webseiten – was wir in den vorangegangenen Kapiteln als Benutzeroberfläche bezeichnet haben. Der Zweck dieses Kapitels ist es, sie vorzustellen. Wir werden sehen, dass sie manchmal komplexe Objekte wie Kalender oder mit Datenquellen verknüpfte Tabellen bieten. Sie sind erweiterbar, was bedeutet, dass der Benutzer seine eigene Tag-Bibliothek erstellen kann. So kann er beispielsweise ein Tag erstellen, das ein Banner auf einer Seite generiert. Alle Seiten, die dieses Tag verwenden, werden dann dasselbe Banner anzeigen.
Der für ein Tag generierte HTML-Code passt sich dem Typ des Client-Browsers an. Wenn der Browser eine Anfrage an den Webserver sendet, enthält er unter seinen HTTP-Headern einen [User-Agent: xx]-Header, wobei [xx] den Client identifiziert. Hier ein Beispiel:
Anhand dieser Informationen kann der Webserver die Fähigkeiten des Clients ermitteln, insbesondere den Typ des HTML-Codes, den er verarbeiten kann. Im Laufe der Zeit gab es tatsächlich mehrere Versionen der HTML-Sprache. Moderne Browser unterstützen die neuesten Versionen der Sprache, ältere Browser hingegen nicht. Basierend auf dem vom Client gesendeten HTTP-Header [User-Agent:] sendet der Server eine HTML-Version zurück, die der Client verstehen kann. Dies ist ein nützlicher und praktischer Ansatz, da sich der Entwickler so keine Gedanken über den spezifischen Typ des von seiner Anwendung verwendeten Client-Browsers machen muss.
Schließlich ermöglichen fortschrittliche IDEs wie Visual Studio.NET, WebMatrix usw. eine Gestaltung der Weboberfläche im „Windows-Stil“. Diese Tools sind zwar nicht unverzichtbar, bieten dem Entwickler jedoch eine erhebliche Unterstützung. Der Entwickler gestaltet die Weboberfläche mithilfe grafischer Komponenten, die er auf der Oberfläche platziert. Er hat direkten Zugriff auf die Eigenschaften jeder Oberflächenkomponente, die er nach Belieben konfigurieren kann. Diese Eigenschaften werden in den HTML-Darstellungscode der Oberfläche als Attribute des <asp:>-Tags der Komponente übersetzt. Der Vorteil für den Entwickler besteht darin, dass er sich weder die Liste noch die Syntax der Attribute der einzelnen Tags merken muss. Dies ist ein erheblicher Vorteil, wenn man mit den von ASP.NET angebotenen Server-Tag-Bibliotheken nicht vollständig vertraut ist. Sobald diese Syntax beherrscht wird, ziehen es manche Entwickler möglicherweise vor, die Tags direkt in den Präsentationscode der Seite zu schreiben, ohne die grafische Entwurfsphase zu durchlaufen. Eine IDE ist dann nicht mehr erforderlich; ein einfacher Texteditor reicht aus. Je nach Ihrem Arbeitsablauf liegt der Fokus dann auf Komponenten (bei Verwendung einer IDE) oder Tags (bei Verwendung eines Texteditors). Diese beiden Begriffe sind gleichbedeutend. Eine Komponente ist das Objekt, das vom Steuerungscode der Seite manipuliert wird. Die IDE gewährt uns während der Entwurfsphase Zugriff auf ihre Eigenschaften. Die diesen Eigenschaften zugewiesenen Werte werden sofort in die Tag-Attribute der Komponente im Präsentationscode übersetzt. Während der Laufzeitphase manipuliert der Steuerungscode der Seite die Komponente und weist einigen ihrer Eigenschaften Werte zu. Der Präsentationscode generiert den HTML-Code der Komponente, indem er einerseits die während des Entwurfs für das entsprechende Server-Tag festgelegten Attribute und andererseits die vom Steuerungscode berechneten Werte der Eigenschaften der Komponente verwendet.
7.2. Der Ausführungskontext der Beispiele
Wir werden die Gestaltung von Weboberflächen auf Basis von Serverkomponenten anhand von Programmen veranschaulichen, deren Ausführungskontext größtenteils wie folgt aussehen wird:
- Die Webanwendung besteht aus einer einzigen Seite P, die ein Formular F enthält.
- der Client stellt seine erste Anfrage direkt an diese Seite P. Dies beinhaltet die Anforderung der URL der Seite P über einen Browser. Es handelt sich also um eine GET-Anfrage, die an diese URL P gestellt wird. Der Server liefert die Seite P und damit das darin enthaltene Formular F aus,
- der Benutzer füllt es aus und sendet es ab, d. h., er führt eine Aktion aus, die den Browser dazu veranlasst, das Formular F an den Server zu senden. Die POST-Operation des Browsers ist immer an die Seite P gerichtet. Der Server liefert erneut die Seite P mit dem Formular F, dessen Inhalt durch die Aktion des Benutzers möglicherweise geändert wurde.
- Dann werden die Schritte 2 und 3 fortgesetzt.
Dies ist ein sehr spezifischer Ausführungsprozess, außerhalb dessen bestimmte, im Folgenden besprochene Konzepte nicht mehr funktionieren. Wir befinden uns nicht mehr im Kontext einer MVC-Architektur, in der eine mehrseitige Anwendung von einer bestimmten Seite gesteuert wird, die wir als „Anwendungscontroller“ bezeichnet haben. In dieser Art von Architektur zielen Formular-POSTs auf den Controller und nicht auf die Formulare selbst. Wir werden jedoch sehen, dass die Erstellung eines Formulars mit serverseitigen Komponenten impliziert, dass dieses Formular an sich selbst gesendet wird.
7.3. Die Label-Komponente
7.3.1. Verwendung
Mit dem <asp:label>-Tag können Sie dynamischen Text in den Präsentationscode einer Seite einfügen. Es leistet also nicht mehr als das bisher verwendete <%=variable%>-Tag. Die Untersuchung dieses ersten Tags wird uns helfen zu verstehen, wie Server-Tags funktionieren. Wir erstellen eine Seite mit einem Steuerungsabschnitt [form1.aspx.vb] und einem Präsentationsabschnitt [form1.aspx]. Das Ziel ist es, die Uhrzeit anzuzeigen:

Dieses Thema wurde bereits in Kapitel 2 behandelt, und der Leser wird gebeten, dort nachzuschlagen, wenn er wissen möchte, wie es umgesetzt wurde. Der Präsentationscode [form1.aspx] lautet wie folgt:
<%@ page src="form1.aspx.vb" inherits="form1" AutoEventWireup="false" %>
<HTML>
<HEAD>
<title>Webforms</title>
</HEAD>
<body>
<asp:Label Runat="server" ID="lblHeure" />
</body>
</HTML>
Wir führen das <asp:label>-Tag ein. In Tag-Bibliotheken ist das Attribut [runat="server"] erforderlich. Das ID-Attribut identifiziert die Komponente. Der Controller muss über diesen Bezeichner darauf verweisen. Der Controller-Code [form1.aspx.vb] lautet wie folgt:
Imports System.Web.UI.WebControls
Public Class form1
Inherits System.Web.UI.Page
Protected lblHeure As Label
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Init
' saves the current query in request.txt of the page folder
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
' sets time in lblHeure
lblHeure.Text = "Il est " + Date.Now.ToString("T")
End Sub
End Class
Der Controller muss dem Objekt [lblHeure] vom Typ [System.Web.UI.WebControls.Label] einen Wert zuweisen. Alle Objekte, die durch <asp:>-Tags angezeigt werden, gehören zum Namespace [System.Web.UI.WebControls]. Daher können wir diesen Namespace systematisch importieren:
Imports System.Web.UI.WebControls
Das [Label]-Objekt verfügt über verschiedene Eigenschaften, darunter die [Text]-Eigenschaft, die den Text darstellt, der vom entsprechenden <asp:label>-Tag angezeigt wird. Hier setzen wir diese Eigenschaft auf die aktuelle Uhrzeit. Dies geschieht in der [Form_Load]-Prozedur des Controllers, die immer ausgeführt wird. In der [Form_Init]-Prozedur, die ebenfalls immer ausgeführt wird, jedoch vor der [Form_Load]-Prozedur, speichern wir die Anfrage des Clients in einer [request.txt]-Datei im Anwendungsordner. Wir werden Gelegenheit haben, diese Datei zu untersuchen, um bestimmte Aspekte der Funktionsweise von Seiten mit Server-Tags zu verstehen.
Das [Label]-Objekt verfügt über viele Eigenschaften, Methoden und Ereignisse. Der Leser wird gebeten, die Dokumentation zur [Label]-Klasse zu konsultieren, um diese zu erkunden. Dies gilt für den gesamten Rest des Buches. Für jedes Tag stellen wir nur die wenigen Eigenschaften vor, die wir benötigen.
7.3.2. Tests
Wir legen die Dateien (form1.aspx, form1.aspx.vb) in einem Ordner <application-path> ab und starten Cassini mit den Parametern (<application-path>,/form1). Anschließend rufen wir die URL [http://localhost/form1/form1.aspx] auf. Wir erhalten das folgende Ergebnis:

Der vom Browser empfangene HTML-Code lautet wie folgt:
<HTML>
<HEAD>
<title>Webforms</title>
</HEAD>
<body>
<span id="lblHeure">Il est 19:39:37</span>
</body>
</HTML>
Wir sehen, dass das Server-Tag
<asp:Label Runat="server" ID="lblHeure" />
in den folgenden HTML-Code umgewandelt wurde:
Es ist die Eigenschaft [Text] des Objekts [lblHeure], die zwischen den Tags <span> und </span> platziert wurde. Die vom Client gestellte und in [request.txt] gespeicherte Anfrage lautet wie folgt:
GET /form1/form1.aspx HTTP/1.1
Cache-Control: max-age=0
Connection: keep-alive
Keep-Alive: 300
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
Nichts Ungewöhnliches.
7.3.3. Erstellen der Anwendung mit WebMatrix
Wir haben den Präsentationscode für die Seite [form1.aspx] von Hand erstellt. Diese Methode ist nützlich, wenn man die Tags kennt. Ein einfacher Texteditor reicht dann aus, um die Benutzeroberfläche zu erstellen. Zu Beginn ist oft ein grafisches Design-Tool in Kombination mit automatischer Codegenerierung erforderlich, da man die Syntax der benötigten Tags noch nicht kennt. Wir werden nun dieselbe Anwendung mit dem WebMatrix-Tool erstellen. Sobald WebMatrix gestartet ist, wählen wir die Option [Datei/Neue Datei]:

Wir erstellen eine ASP.NET-Seite mit dem Namen [form2.aspx]. Nach Bestätigung des vorherigen Assistenten sehen wir das Designfenster für die Seite [form2.aspx]:


Beachten Sie, dass WebMatrix sowohl den Steuerungscode als auch den Präsentationscode der Seite in einer einzigen Datei ablegt, in diesem Fall [form2.aspx]. Die Registerkarte [Alle] zeigt den Inhalt dieser Textdatei an. Wir können bereits sehen, dass sie nicht leer ist:

Der Nachteil dieser Art von Tool ist, dass es oft unnötigen Code generiert. Dies ist hier der Fall, wo WebMatrix ein HTML-Tag <form> generiert hat, obwohl wir kein Formular erstellen werden... Außerdem sehen wir, dass dem Dokument ein <title>-Tag fehlt. Wir werden diese beiden Probleme sofort beheben, um die folgende aktualisierte Version zu erhalten:

Der sogenannte Controller-Code wird zwischen den Tags <script> und </script> eingefügt, wodurch zumindest eine visuelle Trennung zwischen den beiden Codetypen – Steuerung und Darstellung – gewährleistet ist. Wir kehren zur Registerkarte [Design] zurück, um unsere Benutzeroberfläche zu entwerfen. Eine Liste der Komponenten ist in einem Werkzeugfenster links neben dem Designfenster verfügbar:

Das Werkzeugfenster bietet Zugriff auf zwei Arten von Komponenten:
- [WebControls]-Komponenten, die in <asp:>-Tags übersetzt werden
- [HTML Elements]-Komponenten, die in Standard-HTML-Tags übersetzt werden. Sie können jedoch das Attribut [runat="server"] zu den Attributen eines HTML-Tags hinzufügen. In diesem Fall sind das HTML-Tag und seine Attribute für den Controller über ein Objekt zugänglich, dessen Eigenschaften denen des HTML-Tags entsprechen, das es repräsentiert. Wir haben diese Tags zuvor als serverseitige HTML-Tags bezeichnet.
Doppelklicken Sie in der Liste [WebControls] auf die Komponente [Label]. Auf der Registerkarte [Design] erhalten Sie das folgende Ergebnis:

Auf der Registerkarte [All] sieht der Code nun wie folgt aus:
<%@ Page Language="VB" %>
<html>
<head>
<title>webforms</title>
</head>
<body>
<asp:Label id="Label1" runat="server">Label</asp:Label>
</body>
</html>
Zunächst fällt auf, dass das <script>-Tag verschwunden ist. Es wurde ein <asp:label>-Tag generiert. Es hat einen Namen [Label1] und einen Wert [Label]. Kehren wir zur Registerkarte [Design] zurück, um diese beiden Werte zu ändern. Klicken Sie einmal auf die Komponente [Label], um das Eigenschaftenfenster in der unteren rechten Ecke zu öffnen:

Der Leser ist eingeladen, die Eigenschaften des [Label]-Objekts zu überprüfen. Zwei davon sind hier von Interesse:
- Text: Dies ist der Text, den das Label anzeigen soll – wir setzen die Zeichenfolge auf leer (d. h. nichts)
- ID: Dies ist seine Kennung – wir setzen sie auf lblHeure
Die Registerkarte [Design] sieht nun wie folgt aus:

und der Code für [All] lautet nun:
<%@ Page Language="VB" %>
<html>
<head>
<title>webforms</title>
</head>
<body>
<asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>
Das Layout der Seite ist fertig. Wir müssen noch den Steuerungscode schreiben, der für die Einstellung der Uhrzeit in der Eigenschaft [Text] von [lblHeure] zuständig ist. Wir fügen den folgenden Code auf der Registerkarte [All] ein:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
' sets time in lblHeure
lblHeure.Text = "Il est " + Date.Now.ToString("T")
End Sub
</script>
<html>
<head>
<title>webforms</title>
</head>
<body>
<asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>
Beachten Sie, dass im Controller-Code das Objekt [lblHeure] nicht wie zuvor deklariert ist:
Protected lblHeure As New System.Web.UI.WebControls.Label
Tatsächlich werden alle <asp:>-Serverkomponenten in der Präsentationsschicht implizit im Controller-Code deklariert. Eine explizite Deklaration führt zu einem Kompilierungsfehler, der darauf hinweist, dass das Objekt bereits deklariert ist. Wir sind bereit, den Code auszuführen. Wir wählen die Option [Ansicht/Start] oder drücken die Tastenkombination [F5]. Cassini wird automatisch mit den folgenden Parametern gestartet:

Wir akzeptieren diese Werte. Der Standardbrowser des Systems öffnet sich automatisch, um die URL [http://localhost/form2.aspx] aufzurufen. Wir erhalten das folgende Ergebnis:

Von nun an werden wir hauptsächlich das WebMatrix-Tool verwenden, um die Entwicklung und das Testen der kurzen Programme zu erleichtern, die wir schreiben werden.
7.4. Die Literal-Komponente
7.4.1. Verwendung
Mit dem <asp:literal>-Tag können Sie dynamischen Text in den Präsentationscode einer Seite einfügen, ähnlich wie mit dem <asp:label>-Tag. Sein Hauptattribut ist [Text], das den Text darstellt, der unverändert in den HTML-Fluss der Seite eingefügt wird. Dieses Tag ist ausreichend, wenn Sie den Text, den Sie in den HTML-Fluss einfügen möchten, nicht formatieren wollen. Während die [Label]-Klasse die Formatierung mithilfe von Attributen wie [BorderColor, BorderWidth, Font, ...] ermöglicht, verfügt die [Literal]-Klasse über keines dieser Attribute. Der Leser kann das vorherige Beispiel vollständig nachstellen, indem er die [Label]-Komponente durch eine [Literal]-Komponente ersetzt.
7.5. Die Button-Komponente
7.5.1. Verwendung
Mit dem <asp:Button>-Tag können Sie eine Schaltfläche vom Typ [Submit] in ein Formular einfügen, was eine Ereignisbehandlung mit sich bringt, die der in Windows-Anwendungen ähnelt. Dies ist der Punkt, den wir hier näher untersuchen möchten. Wir erstellen die folgende Seite [form3.aspx]:

Diese mit WebMatrix erstellte Seite enthält drei Komponenten:
Nr. | Name | Typ | Eigenschaften | Rolle |
1 | Schaltfläche | text=Button1 | Absenden-Schaltfläche | |
2 | Schaltfläche | text=Schaltfläche2 | Senden-Schaltfläche | |
3 | Beschriftung | text= | Informationsmeldung |
Der von WebMatrix für diesen Abschnitt generierte Code lautet wie folgt:
<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form runat="server">
<p>
<asp:Button id="Button1" runat="server" Text="Bouton1"></asp:Button>
<asp:Button id="Button2" runat="server" Text="Bouton2"></asp:Button>
</p>
<p>
<asp:Label id="lblInfo" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
Wir sehen die Komponenten [Button] und [Label], die im grafischen Design der Seite innerhalb von <asp:>-Tags verwendet werden. Beachten Sie das <form runat="server">-Tag, das automatisch generiert wurde. Dies ist ein serverseitiges HTML-Tag – also ein Standard-HTML-Tag, das durch ein Objekt repräsentiert wird, das vom Controller manipuliert werden kann. Der HTML-Code für das <form>-Tag wird basierend auf dem Wert generiert, den der Controller diesem Objekt zuweist.
Fügen wir die Prozedur [Page_Init] zum Controller-Abschnitt des Codes hinzu, der das [Init]-Ereignis der Seite verarbeitet. Dort platzieren wir den Code, der die Anfrage des Clients in der Datei [request.txt] speichert. Dies benötigen wir, um zu verstehen, wie die Schaltflächen funktionieren.
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' saves the current request in request.txt of the page folder
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
</script>
Beachten Sie, dass wir die Klausel [Handles MyBase.Init] nach der Prozedurdeklaration [Page_Init] nicht eingefügt haben. Dies liegt daran, dass das [Init]-Ereignis des [Page]-Objekts einen Standard-Handler namens [Page_Init] hat. Wenn wir diesen Handlernamen verwenden, wird die Klausel [Handles Page.Init] überflüssig. Das Einfügen dieser Klausel führt jedoch nicht zu einem Fehler.
7.5.2. Testen
Wir führen die Anwendung in WebMatrix aus, indem wir [F5] drücken. Wir erhalten die folgende Seite:

Der vom Browser empfangene HTML-Code lautet wie folgt:
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />
<p>
<input type="submit" name="Button1" value="Bouton1" id="Button1" />
<input type="submit" name="Button2" value="Bouton2" id="Button2" />
</p>
<p>
<span id="lblInfo"></span>
</p>
</form>
</body>
</html>
Beachten Sie folgende Punkte:
- Das <form runat="server">-Tag wurde in ein HTML-Tag umgewandelt
Es wurden zwei Attribute festgelegt: [method="post"] und [action="form3.aspx"]. Können wir diesen Attributen andere Werte zuweisen? Wir werden versuchen, diesen Punkt etwas später zu klären. Beachten Sie hier, dass das Formular an die URL [form3.aspx] gesendet wird. Zwei weitere Attribute [name, id] wurden ebenfalls festgelegt. Meistens werden sie ignoriert. Wenn die Seite jedoch browserspezifischen JavaScript-Code enthält, ist das Attribut [name] des Tags <form> nützlich.
- Die <asp:button>-Tags sind zu HTML-[submit]-Button-Tags geworden. Ein Klick auf einen dieser Buttons löst daher einen „Post“ des Formulars [_ctl10] an die URL [form3.aspx] aus.
- Das <asp:label>-Tag ist zu einem HTML-Tag <span> geworden
- Ein verstecktes Feld [__VIEWSTATE] wurde mit einem seltsamen Wert generiert:
Dieses Feld stellt in verschlüsselter Form den Status des an den Client gesendeten Formulars dar. Dieser Status repräsentiert den Wert aller seiner Komponenten. Da [__VIEWSTATE] Teil des Formulars ist, wird sein Wert zusammen mit dem Rest an den Server gesendet. Dadurch kann der Server erkennen, welche Formularkomponente ihren Wert geändert hat, und gegebenenfalls Entscheidungen treffen. Diese Entscheidungen erfolgen in Form von Ereignissen wie „Das Textfeld mit dem Namen soundso hat seinen Wert geändert.“
7.5.3. Client-Anfragen
Nachdem die Seite [form3.aspx] im Browser geladen wurde, rufen wir sie erneut auf und untersuchen die vom Browser gesendete Anfrage, um sie abzurufen. Erinnern Sie sich daran, dass unsere Anwendung dies in der Datei [request.txt] im Anwendungsordner speichert:
GET /form3.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
Dies ist eine Standard-GET-Anfrage. Klicken wir nun im Browser auf die Schaltfläche [Button1] auf der Seite. Es scheint nichts zu passieren. Wir wissen jedoch, dass das Formular übermittelt wurde. Der HTML-Code der Seite bestätigt dies. Dies wird durch den neuen Inhalt von [request.txt] bestätigt:
POST /form3.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Content-Length: 80
Content-Type: application/x-www-form-urlencoded
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
Referer: http://localhost/form3.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__VIEWSTATE=dDwxNTY0NjIwMjUwOzs%2B2mcnJczeuvF2PEfvmtv7uiUhWUw%3D&Button1=Bouton1
Der erste HTTP-Header zeigt eindeutig an, dass der Client eine POST-Anfrage an die URL [/form3.aspx] gesendet hat. Die letzte Zeile zeigt die übermittelten Werte:
- den Wert des versteckten Feldes __VIEWSTATE
- den Wert der angeklickten Schaltfläche
Wenn wir auf [Button2] klicken, lauten die vom Browser gesendeten Werte wie folgt:
Der Wert des versteckten Feldes ist immer derselbe, aber es ist der Wert von [Button2], der übermittelt wurde. Der Server kann daher feststellen, welche Schaltfläche verwendet wurde. Er nutzt dies, um ein Ereignis auszulösen, das von der Seite verarbeitet werden kann, sobald sie geladen ist.
7.5.4. Behandlung des Klick-Ereignisses eines Button-Objekts
Schauen wir uns noch einmal an, wie eine .aspx-Seite funktioniert. Es handelt sich um ein Objekt, das von der Klasse [Page] abgeleitet ist. Nennen wir die abgeleitete Klasse [aPage]. Wenn der Server eine Anfrage für eine solche Seite erhält, wird ein Objekt vom Typ [unePage] über die Operation new unePage(...) instanziiert. Der Server generiert dann zwei Ereignisse namens [Init] und [Load] in dieser Reihenfolge. Das [unePage]-Objekt kann diese Ereignisse verarbeiten, indem es die Ereignisbehandler [Page_Init] und [Page_Load] bereitstellt. Weitere Ereignisse werden später generiert. Wir werden Gelegenheit haben, darauf zurückzukommen. Wenn die Anfrage des Clients ein POST ist, generiert der Server das [Click]-Ereignis für die Schaltfläche, die diesen POST ausgelöst hat. Wenn die [unePage]-Klasse einen Handler für dieses Ereignis bereitgestellt hat, wird dieser aufgerufen. Lassen Sie uns diesen Mechanismus mit WebMatrix untersuchen. Doppelklicken Sie auf der Registerkarte [Design] von [form3.aspx] auf die Schaltfläche [Button1]. Wir gelangen dann automatisch zur Registerkarte [Code] innerhalb des Hauptteils einer Prozedur namens [Button1_Click]. Um dies besser zu verstehen, wechseln wir zur Registerkarte [All] und sehen uns den gesamten Code an. Die folgenden Änderungen wurden vorgenommen:
<%@ Page Language="VB" %>
<script runat="server">
...
Sub Button1_Click(sender As Object, e As EventArgs)
End Sub
</script>
<html>
...
<body>
<form runat="server">
...
<asp:Button id="Button1" onclick="Button1_Click" runat="server" Text="Bouton1"></asp:Button>
</form>
</body>
</html>
Dem <asp:Button>-Tag für [Button1] wurde ein neues Attribut [onclick="Button1_Click"] hinzugefügt. Dieses Attribut gibt die Prozedur an, die für die Behandlung des [Click]-Ereignisses des Objekts [Button1] zuständig ist, in diesem Fall die Prozedur [Button1_Click]. Jetzt muss diese nur noch geschrieben werden:
Sub Button1_Click(sender As Object, e As EventArgs)
' click on button 1
lblInfo.Text="Vous avez cliqué sur [Bouton1]"
End Sub
Die Prozedur zeigt eine Informationsmeldung im Label [lblInfo] an. Wir verfahren auf die gleiche Weise für die Schaltfläche [Button2], um die folgende neue Seite [form3.aspx] zu erhalten:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' sauve la requte courante dans request.txt du dossier de la page
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
Sub Button1_Click(sender As Object, e As EventArgs)
' clic sur bouton 1
lblInfo.Text="Vous avez cliqué sur [Bouton1]"
End Sub
Sub Button2_Click(sender As Object, e As EventArgs)
' clic sur bouton 2
lblInfo.Text="Vous avez cliqué sur [Bouton2]"
End Sub
</script>
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form runat="server">
<p>
<asp:Button id="Button1" onclick="Button1_Click" runat="server" Text="Bouton1"></asp:Button>
<asp:Button id="Button2" onclick="Button2_Click" runat="server" Text="Bouton2" BorderStyle="None"></asp:Button>
</p>
<p>
<asp:Label id="lblInfo" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
Wir drücken [F5], um den Code auszuführen, und erhalten die folgende Seite:

Wenn wir uns den empfangenen HTML-Code ansehen, stellen wir fest, dass er sich gegenüber der vorherigen Version der Seite nicht geändert hat:
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />
<p>
<input type="submit" name="Button1" value="Bouton1" id="Button1" />
<input type="submit" name="Button2" value="Bouton2" id="Button2" />
</p>
<p>
<span id="lblInfo"></span>
</p>
</form>
</body>
</html>
Wenn wir auf [Button1] klicken, erhalten wir folgende Antwort:

Der für diese Antwort empfangene HTML-Code lautet wie folgt:
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwO3Q8O2w8aTwxPjs+O2w8dDw7bDxpPDU+Oz47bDx0PHA8cDxsPFRleHQ7PjtsPFZvdXMgYXZleiBjbGlxdcOpIHN1ciBbQm91dG9uMV07Pj47Pjs7Pjs+Pjs+Pjs+4oO98Vd244kj0lPMXReWOwJ1WW0=" />
<p>
<input type="submit" name="Button1" value="Bouton1" id="Button1" />
<input type="submit" name="Button2" value="Bouton2" id="Button2" />
</p>
<p>
<span id="lblInfo">Vous avez cliqué sur [Bouton1]</span>
</p>
</form>
</body>
</html>
Wir können sehen, dass das versteckte Feld [__VIEWSTATE] seinen Wert geändert hat. Dies spiegelt die Wertänderung der Komponente [lblInfo] wider.
7.5.5. Ereignisse im Lebenszyklus einer ASP.NET-Anwendung
Die ASP.NET-Dokumentation listet die Ereignisse auf, die vom Server während des Lebenszyklus einer Anwendung generiert werden:
- Wenn die Anwendung ihre allererste Anfrage erhält, wird das [Start]-Ereignis auf dem [HttpApplication]-Objekt der Anwendung generiert. Dieses Ereignis kann von der [Application_Start]-Prozedur in der [global.asax]-Datei der Anwendung abgehandelt werden.
Als Nächstes folgt eine Reihe von Ereignissen, die sich bei jeder empfangenen Anfrage wiederholen:
- Wenn die Anfrage kein Sitzungstoken gesendet hat, wird eine neue Sitzung gestartet und ein [Start]-Ereignis auf dem mit der Anfrage verbundenen [Session]-Objekt generiert. Dieses Ereignis kann durch die [Session_Start]-Prozedur in der [global.asax]-Datei der Anwendung abgehandelt werden.
- Der Server generiert das [BeginRequest]-Ereignis auf dem [HttpApplication]-Objekt. Es kann von der Prozedur [Application_BeginRequest] in der Datei [global.asax] der Anwendung verarbeitet werden.
- Der Server lädt die durch die Anfrage angeforderte Seite. Er instanziiert ein [Page]-Objekt und generiert anschließend zwei Ereignisse für dieses Objekt: [Init] und dann [Load]. Diese beiden Ereignisse können durch die Prozeduren [Page_Init] und [Page_Load] der Seite verarbeitet werden.
- Basierend auf den empfangenen übermittelten Werten generiert der Server weitere Ereignisse: [TextChanged] für eine [TextBox]-Komponente, deren Wert sich geändert hat, [CheckedChanged] für ein Optionsfeld, dessen Wert sich geändert hat, [SelectedIndexChanged] für eine Liste, deren ausgewähltes Element sich geändert hat, ... Wir werden Gelegenheit haben, die wichtigsten Ereignisse für jede der vorgestellten Serverkomponenten zu erwähnen. Jedes Ereignis E eines Objekts namens O kann von einer Prozedur namens O_E behandelt werden.
- Die Reihenfolge, in der die vorgenannten Ereignisse verarbeitet werden, ist nicht garantiert. Daher dürfen Handler keine Annahmen über diese Reihenfolge treffen. Wir können jedoch sicher sein, dass das [Click]-Ereignis der Schaltfläche, die den POST-Befehl ausgelöst hat, als letztes verarbeitet wird.
- Sobald die Seite fertig ist, sendet der Server sie an den Client. Zuvor generiert er das [PreRender]-Ereignis, das von der Prozedur [Page_PreRender] der Seite behandelt werden kann.
- Sobald die HTML-Antwort an den Client gesendet wurde, wird die Seite aus dem Speicher entladen. Zu diesem Zeitpunkt werden zwei Ereignisse generiert: [Unload] und [Disposed]. Die Seite kann diese Ereignisse nutzen, um Ressourcen freizugeben.
Die Anwendung kann auch Ereignisse außerhalb einer Client-Anfrage empfangen:
- Das [End]-Ereignis eines [Session]-Objekts der Anwendung tritt ein, wenn eine Sitzung endet. Dies kann entweder auf explizite Anforderung durch den Code einer Seite geschehen oder weil die Lebensdauer der Sitzung abgelaufen ist. Die Prozedur [Session_End] in der Datei [global.asax] verarbeitet dieses Ereignis. Sie gibt in der Regel Ressourcen frei, die in [Session_Start] angefordert wurden.
- Das [End]-Ereignis des [HttpApplication]-Objekts der Anwendung tritt ein, wenn die Anwendung beendet wird. Dies geschieht beispielsweise, wenn der Webserver heruntergefahren wird. Die Prozedur [Application_End] in der Datei [global.asax] verarbeitet dieses Ereignis. Sie wird in der Regel verwendet, um Ressourcen freizugeben, die in [Application_Start] angefordert wurden.
Folgende Punkte sind zu beachten:
- Das bisherige Ereignismodell basiert auf dem Standard-HTTP-Austausch zwischen Client und Server. Dies wird deutlich, wenn man die ausgetauschten HTTP-Header untersucht.
- Die Verarbeitung der bisherigen Ereignisse findet immer auf der Serverseite statt. Das Klicken auf eine Schaltfläche kann natürlich durch ein serverseitiges JavaScript-Skript verarbeitet werden. Dies ist jedoch kein Serverereignis, und wir haben es hier mit einer von ASP.NET unabhängigen Technologie zu tun.
Wann werden Ereignisse verarbeitet, sei es auf der Serverseite (Ereignisse im Zusammenhang mit Serverkomponenten) oder auf der Browserseite durch JavaScript-Skripte?
Nehmen wir das Beispiel einer Dropdown-Liste. Wenn der Benutzer den ausgewählten Eintrag darin ändert, wird das Ereignis (Änderung des ausgewählten Eintrags) möglicherweise verarbeitet oder auch nicht, und wenn es verarbeitet wird, kann dies zu unterschiedlichen Zeitpunkten geschehen.
- Wenn Sie es sofort verarbeiten möchten, gibt es zwei Lösungen:
- Es kann vom Browser mithilfe eines JavaScript-Skripts abgewickelt werden. Der Server greift in diesem Fall nicht ein. Damit dies möglich ist, muss die Seite anhand der auf der Seite vorhandenen Werte rekonstruiert werden können.
- Es kann vom Server verarbeitet werden. Hierfür gibt es nur eine Lösung: Das Formular muss zur Verarbeitung an den Server gesendet werden. Wir haben es also mit einem [submit]-Vorgang zu tun. Wir werden sehen, dass wir in diesem Fall eine Serverkomponente namens [DropDownList] verwenden und deren [AutoPostBack]-Attribut auf [true] setzen. Das bedeutet, dass das Formular sofort an den Server gesendet werden muss, wenn sich der ausgewählte Eintrag in der Dropdown-Liste ändert. In diesem Fall generiert der Server HTML-Code für das [DropDownList]-Objekt, der mit einer JavaScript-Funktion verknüpft ist, die dafür zuständig ist, einen [submit]-Befehl auszuführen, sobald das Ereignis „selected item changed“ eintritt. Dieser [submit]-Befehl sendet das Formular an den Server, und versteckte Felder werden in die Übermittlung einbezogen, um anzuzeigen, dass der [post]-Befehl durch eine Änderung der Auswahl in der Dropdown-Liste ausgelöst wurde. Der Server generiert daraufhin das [SelectedIndexChanged]-Ereignis, das von der Seite verarbeitet werden kann.
- Wenn Sie die Verarbeitung vornehmen möchten, jedoch nicht sofort, setzen Sie das Attribut [AutoPostBack] der Serverkomponente [DropDownList] auf [false]. In diesem Fall generiert der Server den Standard-HTML-Code für eine <select>-Liste für das [DropDownList]-Objekt, ohne dass eine zugehörige JavaScript-Funktion vorhanden ist. Es passiert nichts, wenn der Benutzer die Auswahl in der Dropdown-Liste ändert. Wenn der Benutzer das Formular jedoch beispielsweise über eine [submit]-Schaltfläche übermittelt, kann der Server erkennen, dass eine Änderung der Auswahl stattgefunden hat. Wir haben ja gesehen, dass das an den Server gesendete Formular ein verstecktes Feld [__VIEWSTATE] enthält, das in verschlüsselter Form den Zustand aller Elemente im übermittelten Formular darstellt. Wenn der Server das vom Client gesendete neue Formular empfängt, kann er überprüfen, ob sich das ausgewählte Element in der Dropdown-Liste geändert hat. Ist dies der Fall, löst er das [SelectedIndexChanged]-Ereignis aus, das die Seite dann verarbeiten kann. Um diesen Mechanismus vom vorherigen zu unterscheiden, sagen einige Autoren, dass das Ereignis „selection change“ im Browser „zwischengespeichert“ wird, wenn es auftritt. Es wird vom Server erst verarbeitet, wenn der Browser das Formular an ihn sendet, oft nach einem Klick auf eine [submit]-Schaltfläche.
- Wenn Sie das Ereignis nicht verarbeiten möchten, setzen Sie schließlich das [AutoPostBack]-Attribut der [DropDownList]-Serverkomponente auf [false] und schreiben Sie keinen Handler für das [SelectedIndexChanged]-Ereignis.
Sobald der Mechanismus der Ereignisbehandlung verstanden ist, wird der Entwickler eine Webanwendung nicht mehr wie eine Windows-Anwendung entwerfen. Während eine Auswahländerung in einem Kombinationsfeld innerhalb einer Windows-Anwendung genutzt werden kann, um das Erscheinungsbild des Formulars, das es enthält, sofort zu aktualisieren, zögert man in einer Webanwendung eher, dieses Ereignis sofort zu behandeln, wenn es ein „Post“ des Formulars an den Server – und damit einen Hin- und Rückweg zwischen Client und Server – beinhaltet. Aus diesem Grund ist die Eigenschaft [AutoPostBack] serverseitiger Komponenten standardmäßig auf [false] gesetzt. Darüber hinaus kann der [AutoPostBack]-Mechanismus, der auf JavaScript-Skripten basiert, die vom Webserver automatisch in dem an den Client gesendeten Formular generiert werden, nur verwendet werden, wenn man sicher ist, dass der Browser des Clients die Ausführung von JavaScript-Skripten zugelassen hat. Formulare werden daher oft wie folgt aufgebaut:
- Die serverseitigen Komponenten des Formulars haben die Eigenschaft [AutoPostBack] auf [false] gesetzt
- das Formular verfügt über eine oder mehrere Schaltflächen, die für die Ausführung des [POST]-Vorgangs zuständig sind
- im Controller-Code der Seite schreiben Sie Handler nur für die Ereignisse, die Sie behandeln möchten, meist das [Click]-Ereignis auf einer der Schaltflächen.
7.6. Die TextBox-Komponente
7.6.1. Verwendung
Mit dem <asp:TextBox>-Tag können Sie ein Eingabefeld in den Präsentationscode einer Seite einfügen. Wir erstellen eine Seite [form4.aspx], um das folgende Layout zu erzielen:

1234Diese mit WebMatrix erstellte Seite enthält die folgenden Komponenten:
Nr. | Name | Typ | Eigenschaften | Rolle |
1 | TextBox | AutoPostback=true Text= | Eingabefeld | |
2 | Textfeld | AutoPostback=false Text= | Eingabefeld | |
3 | Beschriftung | text= | Informationsmeldung zum Inhalt von [TextBox1] | |
3 | Beschriftung | text= | Informationsmeldung zum Inhalt von [TextBox2] |
Der von WebMatrix für diesen Abschnitt generierte Code lautet wie folgt:
<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form runat="server">
<p>
Texte 1 :
<asp:TextBox id="TextBox1" runat="server" AutoPostBack="True"></asp:TextBox>
</p>
<p>
Texte 2 :
<asp:TextBox id="TextBox2" runat="server"></asp:TextBox>
</p>
<p>
<asp:Label id="lblInfo1" runat="server"></asp:Label>
</p>
<p>
<asp:Label id="lblInfo2" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
Doppelklicken Sie auf der Registerkarte [Design] auf die Komponente [TextBox1]. Das Grundgerüst des Ereignishandlers [TextChanged] für dieses Objekt wird daraufhin generiert (auf der Registerkarte [All]):
<%@ Page Language="VB" %>
<script runat="server">
Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
End Sub
</script>
<html>
...
<body>
...
<asp:TextBox id="TextBox1" runat="server" AutoPostBack="True" OnTextChanged="TextBox1_TextChanged"></asp:TextBox>
</p>
....
</form>
</body>
</html>
Das Attribut [OnTextChanged="TextBox1_TextChanged"] wurde dem Tag <asp:TextBox id="TextBox1"> hinzugefügt, um den Handler für das [TextChanged]-Ereignis von [TextBox1] festzulegen. Wir schreiben nun die Prozedur [TextBox1_Changed].
Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
' text change
lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
End Sub
In der Prozedur schreiben wir eine Meldung in das Label [lblInfo1], die das Ereignis angibt und den Inhalt von [TextBox1] anzeigt. Dasselbe tun wir für [TextBox2]. Wir fügen außerdem die Uhrzeit hinzu, um die Ereignisbehandlung besser nachverfolgen zu können. Der endgültige Code für [form4.aspx] lautet wie folgt:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' sauve la requte courante dans request.txt du dossier de la page
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
' changement de texte
lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
End Sub
Sub TextBox2_TextChanged(sender As Object, e As EventArgs)
' changement de texte
lblInfo2.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox2]. Texte 2=["+textbox2.Text+"]"
End Sub
</script>
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form runat="server">
<p>
Texte 1 :
<asp:TextBox id="TextBox1" runat="server" AutoPostBack="True" OnTextChanged="TextBox1_TextChanged"></asp:TextBox>
</p>
<p>
Texte 2 :
<asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged"></asp:TextBox>
</p>
<p>
<asp:Label id="lblInfo1" runat="server"></asp:Label>
</p>
<p>
<asp:Label id="lblInfo2" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
Wir haben die Prozedur [Page_Init] hinzugefügt, um die Anfrage des Clients zu speichern, wie im vorherigen Beispiel.
7.6.2. Testen
Wir starten die Anwendung in WebMatrix durch Drücken der Taste [F5]. Wir erhalten die folgende Seite:

Der vom Browser empfangene HTML-Code lautet wie folgt:
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">
<input type="hidden" name="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE" value="dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u+/OrTD" />
<script language="javascript">
<!--
function __doPostBack(eventTarget, eventArgument) {
var theform = document._ctl0;
theform.__EVENTTARGET.value = eventTarget;
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
</script>
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
</form>
</body>
</html>
Dieser Code enthält viele Elemente, die automatisch vom Server generiert wurden. Beachten Sie folgende Punkte:
- Es gibt drei versteckte Felder: [__VIEWSTATE], das wir bereits kennengelernt haben, [__EventTarget] und [__EventArgument]. Die beiden letztgenannten Felder dienen dazu, das „change“-Ereignis des Browsers im Eingabefeld [TextBox1] zu verarbeiten
- Die <asp:textbox>-Server-Tags haben die HTML-Tags <input type="text" ...> generiert, die den Eingabefeldern entsprechen
- Das Server-Tag <asp:textbox id="TextBox1" AutoPostBack="true" ...> hat ein <input type="text" ...>-Tag mit dem Attribut [onchange="__doPostBack('TextBox1','')"] generiert. Dieses Attribut legt fest, dass bei einer Änderung des Inhalts von [TextBox1] die JavaScript-Funktion [_doPostBack(...)] ausgeführt werden muss. Sie lautet wie folgt:
<script language="javascript">
<!--
function __doPostBack(eventTarget, eventArgument) {
var theform = document._ctl0;
theform.__EVENTTARGET.value = eventTarget;
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
</script>
Was macht die obige Funktion? Sie weist jedem der beiden versteckten Felder [__EventTarget] und [__EventArgument] einen Wert zu und sendet dann das Formular ab. Das Formular wird somit an den Server gesendet. Dies ist der [AutoPostBack]-Effekt. Das „change“-Ereignis des Browsers löst die Ausführung des Codes „__doPostBack('TextBox1','')“ aus. Daraus lässt sich ableiten, dass im übermittelten Formular das versteckte Feld [__EventTarget] den Wert 'TextBox1' und das versteckte Feld [__EventArgument] den Wert '' haben wird. Dadurch kann der Server die Komponente identifizieren, die den POST-Befehl ausgelöst hat.
- Das Server-Tag <asp:textbox id="TextBox2"...> hat ein Standard-Tag <input type="text" ...> generiert, da sein [AutoPostBack]-Attribut nicht auf [true] gesetzt war.
- Das Tag <form> gibt an, dass das Formular an [form4.aspx] gesendet wird:
Führen wir unseren ersten Test durch. Geben Sie etwas Text in das erste Eingabefeld ein:

und bewegen Sie den Cursor dann in das zweite Eingabefeld. Sofort erscheint eine neue Seite:

Was ist passiert? Als der Cursor das erste Eingabefeld verließ, prüfte der Browser, ob sich dessen Inhalt geändert hatte. Das war der Fall. Daher löste er auf der Browserseite das [change]-Ereignis für das HTML-Feld [TextBox1] aus. Wir haben dann gesehen, dass eine JavaScript-Funktion ausgeführt wurde und das Formular an [form4.aspx] gesendet hat. Diese Seite wurde daraufhin vom Server neu geladen. Anhand der vom Formular übermittelten Werte konnte der Server erkennen, dass sich der Inhalt des serverseitigen [TextBox1]-Tags geändert hatte. Die Prozedur [TextBox1_Changed] wurde daher auf der Serverseite ausgeführt. Sie fügte eine Meldung in das Label [lblInfo1] ein. Sobald diese Prozedur beendet war, wurde [form4.aspx] an den Browser zurückgesendet. Deshalb haben wir nun Text in [lblInfo1]. Allerdings mag es überraschend erscheinen, dass sich etwas im Eingabefeld [TextBox1] befindet. Tatsächlich weist keine serverseitige Prozedur diesem Feld einen Wert zu. Dies ist ein allgemeiner Mechanismus von ASP.NET-Webformularen: Der Server gibt das Formular in dem Zustand zurück, in dem er es empfangen hat. Dazu weist er den Komponenten den Wert zu, der vom Client für sie gesendet wurde. Für einige Komponenten sendet der Client keinen Wert. Dies ist beispielsweise bei <asp:label>-Komponenten der Fall, die als HTML-Tags <span> gerendert werden. Erinnern Sie sich daran, dass das Formular ein verstecktes Feld [__VIEWSTATE] enthält, das den Zustand des Formulars beim Senden an den Client repräsentiert. Dieser Zustand ist die Summe der Zustände aller Komponenten des Formulars, einschließlich aller <asp:label>-Komponenten. Da das versteckte Feld [__VIEWSTATE] vom Client-Browser gesendet wird, kann der Server den vorherigen Zustand aller Komponenten des Formulars wiederherstellen. Es müssen nur noch diejenigen Komponenten angepasst werden, deren Werte durch die POST-Anfrage geändert wurden.
Sehen wir uns nun in [request.txt] die vom Browser gesendete Anfrage an:
POST /form4.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Content-Length: 137
Content-Type: application/x-www-form-urlencoded
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
Referer: http://localhost/form4.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u%2B%2FOrTD&TextBox1=premier+texte&TextBox2=
Wir können die POST-Anfrage und die gesendeten Parameter deutlich erkennen. Kehren wir zu unserem Browser zurück und geben wir etwas Text in das zweite Eingabefeld ein:

Kehren wir zum Eingabefeld Nr. 1 zurück, um neuen Text einzugeben: Diesmal passiert nichts. Warum? Weil für das Eingabefeld [TextBox2] die Eigenschaft [AutoPostBack] nicht auf [true] gesetzt ist, sodass das dafür generierte <input type="text"...>-Tag das [Change]-Ereignis nicht verarbeitet, wie im HTML-Code zu sehen ist:
Daher tritt kein Ereignis ein, wenn Sie das Eingabefeld Nr. 2 verlassen. Geben wir nun neuen Text in Feld Nr. 1 ein:

Verlassen wir nun das Eingabefeld Nr. 1. Sofort wird das [Change]-Ereignis für dieses Feld erkannt, und das Formular wird an den Server gesendet, der die folgende Seite zurückgibt:

Was ist passiert? Zunächst hat der Browser das Formular übermittelt. Dies spiegelt sich in der in [request.txt] gespeicherten Client-Anfrage wider:
POST /form4.aspx HTTP/1.1
....
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTt0PDtsPGk8MT47PjtsPHQ8O2w8aTwxPjtpPDU%2BOz47bDx0PHA8cDxsPFRleHQ7PjtsPHByZW1pZXIgdGV4dGU7Pj47Pjs7Pjt0PHA8cDxsPFRleHQ7PjtsPDE4OjQyOjI5OiBldnQgW1RleHRDaGFuZ2VkXSBzdXIgW1RleHRCb3gxXS4gVGV4dGUgMT1bcHJlbWllciB0ZXh0ZV07Pj47Pjs7Pjs%2BPjs%2BPjs%2BxLOermpUUUz5rTAa%2FFsjda6lVmo%3D&TextBox1=troisi%C3%A8me+texte&TextBox2=second+texte
Der Server beginnt damit, die Komponenten mithilfe des vom Client gesendeten versteckten Felds [__VIEWSTATE] auf ihre vorherigen Werte zurückzusetzen. Anhand der übermittelten Felder [TextBox1] und [TextBox2] weist er den Komponenten [TextBox1] und [TextBox2] die übermittelten Werte zu. Durch diesen Mechanismus ruft der Client das Formular so ab, wie es übermittelt wurde. Anschließend erkennt der Server erneut mithilfe der übermittelten Felder [__VIEWSTATE], [TextBox1] und [TextBox2], dass sich die Werte der Eingabefelder [TextBox1] und [TextBox2] geändert haben. Er löst daher die [TextChanged]-Ereignisse für diese beiden Objekte aus. Die Prozeduren [TextBox1_TextChanged] und [TextBox2_TextChanged] werden ausgeführt, und die Beschriftungen [labelInfo1] und [labelInfo2] erhalten neue Werte. Anschließend wird die geänderte Seite [form4.aspx] an den Client zurückgesendet.
Ändern wir nun erneut das Eingabefeld Nr. 1:

Wenn wir den Cursor aus Feld 1 bewegen, tritt im Browser das [Change]-Ereignis ein. Dann findet die bereits erläuterte Abfolge von Ereignissen statt (Post vom Browser an den Server, ..., Senden der Serverantwort). Wir erhalten die folgende Antwort:

Anhand des für jede Nachricht angezeigten Zeitstempels können wir erkennen, dass auf dem Server nur die Prozedur [TextBox1_Changed] ausgeführt wurde. Die Prozedur [TextBox2_TextChanged] wurde nicht ausgeführt, da sich der Wert von [TextBox2] nicht geändert hat. Geben wir schließlich neuen Text in Feld Nr. 2 ein:

Setzen Sie dann den Cursor auf Feld Nr. 1 und bewegen Sie ihn zurück zu Feld Nr. 2. Die Seite ändert sich nicht. Warum? Da wir den Wert von Feld Nr. 1 nicht ändern, tritt das Browser-Ereignis [Change] nicht ein, wenn wir dieses Feld verlassen. Folglich wird das Formular nicht an den Server gesendet. Daher ändert sich auf der Seite nichts. Die Tatsache, dass sich der Inhalt von [lblInfo2] nicht ändert, zeigt uns, dass kein POST-Aufruf erfolgt. Wäre dies der Fall, würde der Server erkennen, dass sich der Inhalt von [TextBox2] geändert hat, und dies in [lblInfo2] widerspiegeln.
Die Lehre aus diesem Beispiel ist, dass es keinen Sinn macht, die [AutoPostBack]-Eigenschaft eines [TextBox] auf [true] zu setzen. Dies verursacht in den meisten Fällen einen unnötigen Client-Server-Roundtrip.
7.6.3. Die Rolle des Feldes __VIEWSTATE
Wir haben gesehen, dass der Server systematisch ein verstecktes Feld namens __VIEWSTATE in das von ihm generierte Formular einfügt. Wir haben festgestellt, dass dieses Feld den Zustand des Formulars repräsentiert und dass der Server, wenn dieses versteckte Feld an ihn zurückgesendet wird, den vorherigen Wert des Formulars rekonstruieren kann. Der Zustand eines Formulars ist die Summe der Zustände seiner Komponenten. Jede Komponente verfügt über eine [EnableViewState]-Eigenschaft mit einem booleschen Wert, der angibt, ob der Zustand der Komponente in das versteckte [__VIEWSTATE]-Feld gesetzt werden soll oder nicht. Standardmäßig ist diese Eigenschaft auf [true] gesetzt, was bedeutet, dass der Zustand aller Komponenten in einem Formular in [__VIEWSTATE] gespeichert wird. Manchmal ist dies jedoch nicht erwünscht.
Führen wir einige Tests durch, um die Rolle der Eigenschaft [EnableViewState] besser zu verstehen. Setzen wir diese Eigenschaft für beide Eingabefelder auf [false]:
...
<asp:TextBox id="TextBox1" runat="server" OnTextChanged="TextBox1_TextChanged" AutoPostBack="True" EnableViewState="False"></asp:TextBox>
...
<asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged" EnableViewState="False"></asp:TextBox>
...
Starten wir nun die Anwendung und geben wir etwas Text in Feld Nr. 1 ein, bevor wir zu Feld Nr. 2 wechseln. Daraufhin wird eine POST-Anfrage an den Server gesendet, und wir erhalten die folgende Antwort:

Geben Sie etwas Text in Feld Nr. 2 ein, ändern Sie den Text in Feld Nr. 1 und kehren Sie dann zu Feld Nr. 2 zurück (in dieser Reihenfolge). Eine neue POST-Anfrage wird an den Server gesendet, und wir erhalten die folgende Antwort:

Bislang ist alles wie zuvor. Ändern wir nun den Inhalt von Feld Nr. 1 und wechseln dann zu Feld Nr. 2. Eine neue POST-Anfrage wird gesendet. Die Antwort des Servers lautet wie folgt:

Diesmal gibt es eine Änderung. Der Server hat ein [TextChanged]-Ereignis in Feld Nr. 2 erkannt, da der Wert von [lblInfo2] geändert wurde. Es gab jedoch keine tatsächliche Änderung. Dies liegt an der Eigenschaft [EnableViewState=false] von [TextBox2]. Dadurch speichert der Server den vorherigen Zustand von [TextBox2] nicht im Feld [__VIEWSTATE] des Formulars. Dies entspricht der Zuweisung der leeren Zeichenfolge als vorherigen Zustand. Als der durch die Änderung des Inhalts von [TextBox1] ausgelöste POST-Aufruf erfolgte, verglich der Server den aktuellen Wert von [TextBox2], nämlich [two], mit seinem vorherigen Wert (der leeren Zeichenfolge). Er kam zu dem Schluss, dass [TextBox2] seinen Wert geändert hatte, und generierte das [TextChanged]-Ereignis für [TextBox2]. Wir können dieses Verhalten überprüfen, indem wir eine leere Zeichenfolge in [TextBox2] eingeben. Basierend auf dem soeben Erläuterten sollte der Server das [TextChanged]-Ereignis für [TextBox2] nicht generieren. Probieren wir es aus:

Genau das ist passiert. Die Zeitangabe und der Inhalt von [lblInfo2] zeigen, dass die Prozedur [TextBox2_TextChanged] nicht ausgeführt wurde. Vor diesem Hintergrund wollen wir nun die Eigenschaft [EnableViewState] der vier Formularkomponenten untersuchen:
Wir möchten den Zustand dieser Komponente beibehalten, damit der Server weiß, ob sie sich geändert hat oder nicht | |
gleich | |
Wir möchten den Status dieser Komponente nicht beibehalten. Der Text soll bei jedem neuen POST neu berechnet werden. Wird er nicht neu berechnet, muss er leer sein. All dies wird mit [EnableViewState=false] erreicht | |
gleich |
Unsere Startseite sieht nun wie folgt aus:
...
<asp:TextBox id="TextBox1" runat="server" OnTextChanged="TextBox1_TextChanged" AutoPostBack="True"></asp:TextBox>
...
<asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged"></asp:TextBox>
....
<asp:Label id="lblInfo1" runat="server" enableviewstate="False"></asp:Label>
...
<asp:Label id="lblInfo2" runat="server" enableviewstate="False"></asp:Label>
...
Führen wir dieselbe Testreihe wie zuvor durch. An der Stelle, an der wir den Bildschirm erhalten haben
Version 1

erhalten wir nun:
Version 2

In diesem Schritt haben wir den Inhalt von Feld 1 geändert, ohne den von Feld 2 zu ändern, wodurch sichergestellt wurde, dass die Prozedur [TextBox2_TextChanged] auf der Serverseite nicht ausgeführt wurde, was bedeutete, dass das Feld [lblInfo2] keinen neuen Wert erhielt. Es wird daher mit seinem vorherigen Wert angezeigt. In Version 1 [EnableViewState=true] war dieser vorherige Wert der eingegebene Wert. In Version 2 [EnableViewState=false] ist dieser vorherige Wert die leere Zeichenfolge.
Manchmal ist es nicht notwendig, den vorherigen Zustand von Komponenten beizubehalten. Anstatt für jede einzelne [EnableViewState=false] festzulegen, können Sie angeben, dass die Seite ihren Zustand nicht beibehalten soll. Dies geschieht in der [Page]-Anweisung des Präsentationscodes:
In diesem Fall wird der Zustand einer Komponente unabhängig vom Wert ihrer [EnableViewState]-Eigenschaft nicht im versteckten Feld [__VIEWSTATE] gespeichert. Alles verhält sich dann so, als wäre der vorherige Zustand die leere Zeichenfolge.
Verwenden wir nun den [curl]-Client, um weitere Mechanismen zu veranschaulichen. Zunächst rufen wir die URL [http://localhost/form4.aspx] auf:
dos>curl --include --url http://localhost/form4.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Sun, 04 Apr 2004 17:51:14 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 1077
Connection: Close
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">
<input type="hidden" name="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE" value="dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u+/OrTD" />
<script language="javascript">
<!--
function __doPostBack(eventTarget, eventArgument) {
var theform = document._ctl0;
theform.__EVENTTARGET.value = eventTarget;
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
</script>
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
</form>
</body>
</html>
Wir erhalten den HTML-Code für [form4.aspx] vom Server. Er unterscheidet sich nicht von dem, den der Browser erhalten hat. Erinnern Sie sich hier an die Anfrage, die der Browser beim Absenden des Formulars gestellt hat:
POST /form4.aspx HTTP/1.1
Connection: keep-alive
...
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u%2B%2FOrTD&TextBox1=premier+texte&TextBox2=
Führen wir denselben POST-Befehl aus, jedoch ohne das Feld [__VIEWSTATE] zu senden:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=premier+texte --data TextBox2=
...................
<p>
Texte 1 :
<input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1">19:57:48: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
..............
Beachten Sie folgende Punkte:
- Der Server hat ein [TextChanged]-Ereignis für [TextBox1] erkannt, da er den Text [lblInfo1] generiert hat. Das Fehlen von [__VIEWSTATE] hat dies nicht verhindert. In dessen Abwesenheit geht er davon aus, dass der vorherige Wert eines Eingabefeldes die leere Zeichenfolge ist.
- Er konnte den für [TextBox1] gesendeten Text im Attribut [value] des Tags [TextBox1] wiederherstellen, sodass das Feld [TextBox1] mit dem eingegebenen Wert wieder erscheint. Dazu benötigt er nicht [__VIEWSTATE], sondern nur den für [TextBox1] gesendeten Wert
Nun senden wir dieselbe Anfrage erneut, ohne etwas zu ändern. Wir erhalten die folgende neue Antwort:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=premier+texte --data TextBox2=
<p>
Texte 1 :
<input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1">20:05:47: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
Ohne [__VIEWSTATE] konnte der Server nicht erkennen, dass sich der Wert des Feldes [TextBox1] nicht geändert hatte. Er verhält sich daher so, als wäre der vorherige Wert die leere Zeichenfolge. Folglich hat er hier das [TextChanged]-Ereignis für [TextBox1] ausgelöst. Führen wir dieselbe Anfrage noch einmal aus, diesmal mit leerem Feld [TextBox1] und nicht leerem Feld [TextBox2]:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox2=second+texte --data TextBox1=
......
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" value="second texte" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2">20:11:54: evt [TextChanged] sur [TextBox2]. Texte 2=[second texte]</span>
</p>
......
Da [__VIEWSTATE] fehlte, wurde der vorherige Wert von [TextBox1] als leere Zeichenfolge behandelt. Da der gesendete Wert von [TextBox1] ebenfalls eine leere Zeichenfolge war, wurde das [TextChanged]-Ereignis für [TextBox1] nicht ausgelöst. Die Prozedur [TextBox1_TextChanged] wurde nicht ausgeführt, weshalb das Feld [lblInfo1] keinen neuen Wert erhielt. Wir wissen, dass die Komponente in diesem Fall ihren alten Wert beibehält. Das ist hier jedoch nicht der Fall; [lblInfo1] hat seinen vorherigen Wert verloren. Das liegt daran, dass dieser Wert aus [__VIEWSTATE] abgerufen wird. Da dieses Feld fehlt, wurde [lblInfo1] die leere Zeichenfolge zugewiesen. Für [TextBox2] verglich der Server dessen gesendeten Wert [second text] mit seinem vorherigen Wert. Da [__VIEWSTATE] fehlt, entspricht dieser vorherige Wert der leeren Zeichenfolge. Da sich der übermittelte Wert von [TextBox2] von der leeren Zeichenfolge unterscheidet, wurde das [TextChanged]-Ereignis für [TextBox2] ausgelöst. Die Prozedur [TextBox2_TextChanged] wurde ausgeführt, und das Feld [lblInfo2] erhielt einen neuen Wert.
Man könnte sich fragen, ob die Parameter [__EVENTTARGET] und [__EVENTARGUMENT] tatsächlich nützlich sind. Wenn diese Parameter nicht gesendet werden, weiß der Server nicht, welches Ereignis den [submit] ausgelöst hat. Probieren wir es aus:
dos>curl --include --url http://localhost/form4.aspx --data TextBox2=second+texte --data TextBox1=premier+texte
..............................
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
</form>
.....................
Wir sehen, dass kein [TextChanged]-Ereignis verarbeitet wurde. Außerdem rufen die gesendeten Felder [TextBox1] und [TextBox2] ihre gesendeten Werte nicht ab. Tatsächlich verhält sich alles so, als wäre eine GET-Anfrage gestellt worden. Alles kehrt zum Normalzustand zurück, wenn das Feld [__EVENTTARGET] in den gesendeten Feldern enthalten ist, selbst wenn es keinen Wert hat:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET= --data TextBox2=second+texte --data TextBox1=premier+texte
.......
<p>
Texte 1 :
<input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" value="second texte" id="TextBox2" />
</p>
<p>
<span id="lblInfo1">20:34:14: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
</p>
<p>
<span id="lblInfo2">20:34:14: evt [TextChanged] sur [TextBox2]. Texte 2=[second texte]</span>
</p>
........
7.6.4. Weitere Eigenschaften der TextBox-Komponente
Die Serverkomponente [TextBox] ermöglicht es Ihnen außerdem, die HTML-Tags <input type="password"..> und <textarea>..</textarea> zu generieren, d. h. die Tags, die jeweils einem Passwortfeld und einem mehrzeiligen Eingabefeld entsprechen. Diese Generierung wird durch die Eigenschaft [TextMode] der Komponente [TextBox] gesteuert. Sie kann drei Werte annehmen:
Wert | Generierter HTML-Tag |
<input type="text" ...> | |
<textarea>...</textarea> | |
<input type="password ...> |
Wir werden die Verwendung dieser Eigenschaften anhand des folgenden Beispiels [form5.aspx] untersuchen:

Nr. | name | type | Eigenschaften | Rolle |
1 | Schaltfläche | [submit]-Schaltfläche – dient dazu, den Inhalt von [TextBox1] zu dem von [TextBox2] hinzuzufügen | ||
2 | Textfeld | TextMode=Passwort Text= | Passwortgeschütztes Eingabefeld | |
3 | TextBox | TextMode=Mehrzeilig Text= | kombiniert die in [TextBox1] vorgenommenen Eingaben |
Die Eigenschaft [EnableViewState] der Seite ist auf [false] gesetzt. Auf der Serverseite behandeln wir das Klickereignis auf die Schaltfläche [btnAjouter]:
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' the contents of [textBox1] are added to those of [TextBox2]
textbox2.text=textbox2.text + textbox1.text+controlchars.crlf
End Sub
Um diesen Code zu verstehen, müssen Sie sich vergegenwärtigen, wie die POST-Anfrage eines Formulars verarbeitet wird. Zunächst werden die Prozeduren [Page_Init] und [Page_Load] ausgeführt. Als Nächstes folgen alle zwischengespeicherten Ereignisprozeduren. Schließlich wird die Prozedur ausgeführt, die das Ereignis verarbeitet, das den [POST] ausgelöst hat – in diesem Fall die Prozedur [btnAjouter_Click]. Wenn die Ereignisbehandler ausgeführt werden, haben alle Seitenkomponenten, die einen Wert in der POST-Anfrage haben, diesen Wert übernommen. Die anderen sind auf ihren vorherigen Wert zurückgesetzt worden, wenn ihre [EnableViewState]-Eigenschaft auf [true] gesetzt war, oder auf ihren Wert zur Entwurfszeit, wenn ihre [EnableViewState]-Eigenschaft auf [false] gesetzt war. Hier sind die Werte der Felder [TextBox1] und [TextBox2] Teil des vom Client gesendeten POST. Daher hat [textbox1.text] im vorherigen Code den vom Client gesendeten Wert, und dasselbe gilt für [textbox2.text]. Die Prozedur [btnAjouter_Click] setzt den Wert des Feldes [TextBox2] auf den für [TextBox2] gesendeten Wert, addiert dazu den für [TextBox1] gesendeten Wert sowie das Zeilenumbruchzeichen [ControlChars.CrLf], das im Namespace [Microsoft.VisualBasic] definiert ist. Es ist nicht erforderlich, diesen Namespace zu importieren, da der Webserver ihn standardmäßig importiert.
Der endgültige Code für [form5.aspx] lautet wie folgt:
<%@ Page Language="VB" EnableViewState="False" %>
<script runat="server">
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' on ajoute le contenu de [textBox1] à celui de [TextBox2]
textbox2.text+=textbox1.text+controlchars.crlf
End Sub
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:Button id="btnAjouter" onclick="btnAjouter_Click" runat="server" Text="Ajouter" EnableViewState="False"></asp:Button>
<asp:TextBox id="TextBox1" runat="server" TextMode="Password" Width="353px" EnableViewState="False"></asp:TextBox>
</p>
<p>
<asp:TextBox id="TextBox2" runat="server" TextMode="MultiLine" Width="419px" Height="121px" EnableViewState="False"></asp:TextBox>
</p>
</form>
</body>
</html>
Vorhin haben wir einen Screenshot einer Ausführung gezeigt.
7.7. Die DropDownList-Komponente
Mit dem <asp:DropDownList>-Tag können Sie eine Dropdown-Liste in den Präsentationscode einer Seite einfügen. Wir erstellen eine Seite [form6.aspx], um das folgende Layout zu erzeugen:

Nr. | Name | Typ | Eigenschaften | Rolle |
1 | DropDownList | AutoPostback=true EnableViewState=true | Dropdown-Liste | |
2 | Beschriftung | EnableViewState=false | Informationsmeldung |
Der generierte Präsentationscode lautet wie folgt:
Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:DropDownList id="DropDownList1" runat="server" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
</p>
<p>
<asp:Label id="lblInfo" runat="server" enableviewstate="False"></asp:Label>
</p>
</form>
</body>
</html>
Derzeit enthält die Dropdown-Liste keine Elemente. Wir werden sie in der [Page_Load]-Prozedur füllen. Dazu müssen wir einige der Eigenschaften und Methoden der [DropDownList]-Klasse kennen:
Eine Sammlung vom Typ [ListItemCollection], die die Elemente der Dropdown-Liste enthält. Die Elemente dieser Sammlung sind vom Typ [ListItem]. | |
Die Anzahl der Elemente in der [Items]-Sammlung | |
Element Nummer i in der Liste – vom Typ [ListItem] | |
um ein neues Element [ListItem] zur [Items]-Sammlung hinzuzufügen | |
um alle Elemente aus der [Items]-Sammlung zu entfernen | |
um Element Nummer i aus der [Items]-Sammlung zu entfernen | |
Das erste [ListItem] in der [Items]-Sammlung, dessen [Selected]-Eigenschaft auf „true“ gesetzt ist | |
Index des [SelectedItem]-Elements in der [Items]-Sammlung |
Die Elemente in der [Items]-Sammlung der [DropDownList]-Klasse sind vom Typ [ListItem]. Jedes [ListItem] generiert ein HTML-Tag <option>:
Wir beschreiben einige Eigenschaften und Methoden der Klasse [ListItem]:
Konstruktor – erstellt ein [ListItem]-Element mit den Eigenschaften [text] und [value]. Ein ListItem(T,V)-Element generiert das HTML-Tag <option value="V">T</option>. Die Klasse [ListItem] ermöglicht es uns somit, die Elemente einer HTML-Liste zu beschreiben | |
boolescher Wert. Ist dieser Wert „true“, erhält die entsprechende Option in der HTML-Liste das Attribut [selected="selected"]. Dieses Attribut teilt dem Browser mit, dass das entsprechende Element in der HTML-Liste als ausgewählt angezeigt werden soll | |
Der Text T der HTML-Option <option value="V" [selected="selected"]>T</option> | |
der Wert V des Attributs [Value] der HTML-Option <option value="V" [selected="selected"]>T</option> |
Wir haben genügend Informationen, um den Code zum Befüllen der Dropdown-Liste [DropDownList1] in der Prozedur [Page_Load] der Seite zu schreiben:
Sub Page_Load(sender As Object, e As EventArgs)
' fill in the combo if it's the 1st time you've been called
if not IsPostBack then
dim valeurs() as String = {"1","2","3","4"}
dim textes() as String = {"un","deux","trois","quatre"}
dim i as integer
for i=0 to valeurs.length-1
DropDownList1.Items.Add(new ListItem(textes(i),valeurs(i)))
next
end if
end sub
Sobald die Komponente [DropDownList1] initialisiert wurde, sieht ihr HTML-Code wie folgt aus:
<select name="DropDownList1" id="DropDownList1" onchange="__doPostBack('DropDownList1','')" language="javascript">
<option value="1">un</option>
<option value="2">deux</option>
<option value="3">trois</option>
<option value="4">quatre</option>
</select>
Wir wissen, dass die Prozedur [Page_Load] jedes Mal ausgeführt wird, wenn die Seite [form6.aspx] geladen wird. Sie wird beim ersten Mal über eine GET-Anfrage aufgerufen und anschließend über eine POST-Anfrage, sobald der Benutzer ein neues Element aus der Dropdown-Liste auswählt. Sollte der Code zum Befüllen dieser Liste jedes Mal in [Page_Load] ausgeführt werden? Die Antwort hängt vom Attribut [EnableViewState] der Komponente [DropDownList1] ab. Wenn dieses Attribut auf „true“ gesetzt ist, wissen wir, dass der Zustand der Komponente [DropDownList1] über die Anfragen hinweg im versteckten Feld [__VIEWSTATE] erhalten bleibt. Dieser Zustand umfasst zwei Dinge:
- die Liste aller Werte in der Dropdown-Liste
- den Wert des ausgewählten Elements in dieser Liste
Es mag verlockend erscheinen, die Eigenschaft [EnableViewState] der Komponente [DropDownList1] auf [true] zu setzen, um die in die Liste einzufügenden Werte nicht neu berechnen zu müssen. Das Problem ist jedoch, dass diese Werte weiterhin berechnet werden, da die Prozedur [Page_Load] bei jeder Anforderung der Seite [form6.aspx] ausgeführt wird. Das [Page]-Objekt, von dem [form6.aspx] eine Instanz ist, verfügt über ein [IsPostBack]-Attribut mit einem booleschen Wert. Ist dieses Attribut auf „true“ gesetzt, bedeutet dies, dass die Seite über einen POST-Request angefordert wurde. Ist es auf „false“ gesetzt, bedeutet dies, dass die Seite über einen GET-Request angefordert wurde. In unserem Client-Server-Round-Trip-System fordert der Client immer dieselbe Seite [form6.aspx] vom Server an. Beim ersten Mal fordert er sie mit einem GET an; bei den folgenden Malen mit einem POST. Wir schließen daraus, dass die Eigenschaft [IsPostBack] verwendet werden kann, um die erste GET-Anfrage des Clients zu erkennen. Wir generieren die Werte der Dropdown-Liste nur während dieser ersten Anfrage. Bei nachfolgenden Anfragen werden diese Werte durch den [VIEWSTATE]-Mechanismus generiert. In anderen Situationen kann der Inhalt einer Liste von einer Anfrage zur nächsten variieren und muss daher für jede einzelne neu berechnet werden. In diesem Fall setzen wir das Attribut [EnableViewState] der Liste auf [false], um eine unnötige doppelte Berechnung des Listeninhalts zu vermeiden, es sei denn, wir müssen die zuvor ausgewählten Elemente in der Liste kennen, da diese Informationen im [VIEWSTATE] gespeichert sind.
Das Attribut [AutoPostBack] der Liste [DropDownList1] wurde auf „true“ gesetzt. Das bedeutet, dass der Browser das Formular übermittelt, sobald er das Ereignis „selected item changed“ in der Dropdown-Liste erkennt. Der Server erkennt wiederum – mithilfe von [VIEWSTATE] und den übermittelten Werten –, dass sich das ausgewählte Element in der Komponente [DropDownList1] geändert hat. Er löst dann das Ereignis [SelectedIndexChanged] für diese Komponente aus. Wir werden dies mit der folgenden Prozedur behandeln:
Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
' selection change
lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
" valeur=" + dropdownlist1.selecteditem.value + _
" index="+ dropdownlist1.selectedindex.tostring
End Sub
Wenn diese Prozedur ausgeführt wird, hat das [DropDownList1]-Objekt seine [ListItem]-Elemente über [VIEWSTATE] abgerufen. Außerdem ist bei einem dieser [ListItem]-Elemente das Attribut [Selected] auf „true“ gesetzt – nämlich bei dem Element, dessen Wert vom Browser übermittelt wurde. Es gibt mehrere Möglichkeiten, auf dieses Element zuzugreifen:
ist das erste [ListItem] in der Liste, dessen [Selected]-Attribut auf „true“ gesetzt ist | |
entspricht dem [text]-Teil des HTML-Tags für das vom Benutzer ausgewählte <option value="...">text</option>-Element | |
entspricht dem [value]-Teil des HTML-Tags für das vom Benutzer ausgewählte <option value="...">text</option>-Element | |
Der Index in der [DropDownList1.Items]-Sammlung des ersten [ListItem]-Elements, dessen [Selected]-Attribut auf „true“ gesetzt ist |
Der endgültige Code für [form6.aspx] lautet wie folgt:
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
' on remplit le combo si c'est la 1ère fois qu'on est appelé
if not IsPostBack then
dim valeurs() as String = {"1","2","3","4"}
dim textes() as String = {"un","deux","trois","quatre"}
dim i as integer
for i=0 to valeurs.length-1
DropDownList1.Items.Add(new ListItem(textes(i),valeurs(i)))
next
end if
end sub
Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
' changement de sélection
lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
" valeur=" + dropdownlist1.selecteditem.value + _
" index="+ dropdownlist1.selectedindex.tostring
End Sub
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:DropDownList id="DropDownList1" runat="server" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
</p>
<p>
<asp:Label id="lblInfo" runat="server" enableviewstate="False"></asp:Label>
</p>
</form>
</body>
</html>
7.8. Die ListBox-Komponente
Mit dem <asp:ListBox>-Tag können Sie eine Liste in den Präsentationscode einer Seite einfügen. Wir erstellen [form7.aspx], um das folgende Layout zu erhalten:

Nr. | name | Typ | Eigenschaften | Rolle |
1 | Textfeld | EnableViewState=false | Eingabefeld | |
2 | Schaltfläche | [submit]-Schaltfläche, die den Inhalt von txtSaisie an Liste 1 überträgt, sofern er nicht leer ist. | ||
3 | ListBox | EnableViewState=true Auswahlmodus=Einzels | Liste der Werte für die Einzelauswahl | |
4 | ListBox | EnableViewState=true Auswahlmodus=Mehrfach | Liste der Werte für die Mehrfachauswahl | |
5 | Schaltfläche | [Senden]-Schaltfläche, die das ausgewählte Element von [Liste 1] nach [Liste 2] überträgt | ||
6 | Schaltfläche | [Senden]-Schaltfläche, die die ausgewählten Elemente aus [Liste 2] in [Liste 1] überträgt |
Der generierte Präsentationscode lautet wie folgt:
<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
Tapez un texte pour l'inclure dans Liste 1 :
<asp:TextBox id="txtSaisie" runat="server" EnableViewState="False"></asp:TextBox>
</p>
<p>
<asp:Button id="btnAjouter" onclick="btnAjouter_Click" runat="server" Text="Ajouter"></asp:Button>
</p>
<p>
<table>
<tbody>
<tr>
<td>
<p align="center">
Liste 1
</p>
</td>
<td>
</td>
<td>
<p align="center">
Liste 2
</p>
</td>
</tr>
<tr>
<td>
<asp:ListBox id="ListBox1" runat="server"></asp:ListBox>
</td>
<td>
<p>
<asp:Button id="btn1vers2" onclick="btn1vers2_Click" runat="server" Text="-->"></asp:Button>
</p>
<p>
<asp:Button id="btn2vers1" onclick="btn2vers1_Click" runat="server" Text="<--"></asp:Button>
</p>
</td>
<td>
<p>
<asp:ListBox id="ListBox2" runat="server" SelectionMode="Multiple"></asp:ListBox>
</p>
</td>
</tr>
<tr>
<td>
<p align="center">
<asp:Button id="btnRaz1" onclick="btnRaz1_Click" runat="server" Text="Effacer"></asp:Button>
</p>
</td>
<td>
</td>
<td>
<p align="center">
<asp:Button id="btnRaz2" onclick="btnRaz2_Click" runat="server" Text="Effacer"></asp:Button>
</p>
</td>
</tr>
</tbody>
</table>
</p>
</form>
</body>
</html>
Die Klasse [ListBox] leitet sich von derselben Klasse [ListControl] ab wie die zuvor besprochene Klasse [DropDownList]. Sie enthält alle Eigenschaften und Methoden, die bei [DropDownList] zu sehen waren, da diese eigentlich zu [ListControl] gehörten. Eine neue Eigenschaft erscheint:
legt den Auswahlmodus der HTML-Liste <select> fest, die aus der Komponente generiert wird. Wenn SelectionMode=Single ist, kann nur ein einzelnes Element ausgewählt werden. Wenn SelectionMode=Multiple ist, können mehrere Elemente ausgewählt werden. Um dies zu erreichen, wird das Attribut [multiple="multiple"] im <select>-Tag der HTML-Liste generiert. |
Behandeln wir nun die Ereignisse. Ein Klick auf die Schaltfläche [Add] wird von der folgenden Prozedur [btnAdd_Click] verarbeitet:
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' added to list 1
dim texte as string=txtSaisie.text.trim
if texte<> "" then ListBox1.Items.Add(New ListItem(texte))
' raz txtSaisie
txtSaisie.text=""
End Sub
Wenn der in [txtSaisie] eingegebene Text keine leere Zeichenfolge ist, wird der Liste [ListBox1] ein neues Element hinzugefügt. Wir wissen, dass wir ein Element vom Typ [ListItem] hinzufügen müssen. Zuvor haben wir den Konstruktor [ListItem(T as String, V as String)] verwendet, um eine ähnliche Aufgabe auszuführen. Ein solches Element generiert den HTML-Tag [<option value="V">T</option>]. Hier verwenden wir den Konstruktor [ListItem(T as String)], der den HTML-Tag [<option value="T">T</option>] generiert, d. h., der Text [T] der Option wird verwendet, um den Wert der Option zu bilden. Sobald der Inhalt von [txtSaisie] zur Liste [ListBox1] hinzugefügt wurde, wird das Feld [txtSaisie] gelöscht.
Klicks auf die [Clear]-Schaltflächen werden von den folgenden Prozeduren verarbeitet:
Sub btnRaz1_Click(sender As Object, e As EventArgs)
' raz list 1
ListBox1.Items.Clear
End Sub
Sub btnRaz2_Click(sender As Object, e As EventArgs)
' raz list 2
ListBox2.Items.Clear
End Sub
Klicks auf die Schaltflächen zum Wechseln zwischen den Listen werden von den folgenden Prozeduren verarbeitet:
Sub btn1vers2_Click(sender As Object, e As EventArgs)
' transfer the item selected in list 1 to list 2
transfert(ListBox1,ListBox2)
End Sub
Sub btn2vers1_Click(sender As Object, e As EventArgs)
' transfer of item selected in list 2 to list 1
transfert(ListBox2,ListBox1)
End Sub
sub transfert(l1 as listbox, l2 as listbox)
' transfer items selected in l1 to l2
' anything to do?
if l1.selectedindex=-1 then return
dim i as integer
' we start at the end
for i=l1.items.count-1 to 0 step -1
' selected?
if l1.items(i).selected then
' more selected
l1.items(i).selected=false
' transfer to l2
l2.items.add(l1.items(i))
' deletion in l1
l1.items.removeAt(i)
end if
next
end sub
Da beide Schaltflächen dieselbe Aufgabe erfüllen – Elemente von einer Liste in eine andere zu übertragen –, können wir dies auf eine einzige Übertragungsprozedur mit zwei Parametern reduzieren:
- l1 vom Typ [ListBox], das ist die Quellliste
- l2 vom Typ [ListBox], die die Zielliste darstellt
Zunächst prüfen wir, ob in der Liste l1 mindestens ein Element ausgewählt ist; wenn nicht, gibt es nichts zu tun. Dazu untersuchen wir die Eigenschaft [l1.selectedindex], die den Index des ersten ausgewählten Elements in der Liste angibt. Gibt es keine, ist ihr Wert -1. Befindet sich mindestens ein ausgewähltes Element in l1, übertragen wir es nach l2. Dazu durchlaufen wir die gesamte Liste der Elemente in l1 und prüfen bei jedem, ob sein Attribut [selected] wahr ist. Ist dies der Fall, wird sein Attribut [selected] auf [false] gesetzt, dann wird es in die Liste l2 kopiert und schließlich aus der Liste l1 entfernt. Diese Entfernung bewirkt, dass die Elemente in der Liste l1 neu nummeriert werden. Aus diesem Grund wird die Liste der Elemente in l1 in umgekehrter Reihenfolge durchlaufen. Wenn wir sie vorwärts durchlaufen und Element Nr. 10 entfernen, wird Element Nr. 11 zu Nr. 10 und Nr. 12 zu Nr. 11. Nach der Verarbeitung von Element Nr. 10 verarbeitet unsere Vorwärtsschleife Element Nr. 11, das, wie gerade erläutert, das ehemalige Nr. 12 ist. Das Element, das Nr. 11 war und nun Nr. 10 ist, wird übersehen. Indem wir die Elemente der Liste l1 in umgekehrter Richtung durchlaufen, vermeiden wir dieses Problem.
7.9. Die CheckBox- und RadioButton-Komponenten
Mit den Tags <asp:RadioButton> und <asp:CheckBox> können Sie jeweils ein Optionsfeld und ein Kontrollkästchen in den Präsentationscode einer Seite einfügen. Wir erstellen eine Seite [form8.aspx], um das folgende Layout zu erhalten:

Nr. | name | Typ | Eigenschaften | Rolle |
1 | RadioButton | RadioButton1.Checked = true RadioButton1.Text = 1 RadioButton2.Checked = false RadioButton2.Text = 2 RadioButton3.Checked = false RadioButton3.Text = 3 Für alle 3 Schaltflächen: GroupName = radio | Optionsfelder | |
2 | CheckBox | Checked=false für alle CheckBoxA.Text = A CheckBoxB.Text = B CheckBoxC.Text = C | Kontrollkästchen | |
3 | Schaltfläche | [Senden]-Schaltfläche | ||
4 | ListBox | Informationsliste |
Damit der Browser die drei Optionsfelder als sich gegenseitig ausschließend behandelt, müssen sie in einer Optionsfeldgruppe zusammengefasst werden. Dies geschieht mithilfe des Attributs [GroupName] der Klasse [RadioButton]. Der Seitenstatus muss in dieser Anwendung nicht beibehalten werden. Daher setzen wir das Attribut [EnableViewState="false"] auf der Seite. Der Präsentationscode lautet wie folgt:
<html>
<head>
</head>
<body>
<form id="frmControls" runat="server">
<h3>Cases à cocher
</h3>
<p>
<asp:RadioButton id="RadioButton1" runat="server" Checked="True" EnableViewState="False" GroupName="radio" Text="1"></asp:RadioButton>
<asp:RadioButton id="RadioButton2" runat="server" EnableViewState="False" GroupName="radio" Text="2"></asp:RadioButton>
<asp:RadioButton id="RadioButton3" runat="server" EnableViewState="False" GroupName="radio" Text="3"></asp:RadioButton>
</p>
<p>
<asp:CheckBox id="CheckBoxA" runat="server" EnableViewState="False" Text="A"></asp:CheckBox>
<asp:CheckBox id="CheckBoxB" runat="server" EnableViewState="False" Text="B"></asp:CheckBox>
<asp:CheckBox id="CheckBoxC" runat="server" EnableViewState="False" Text="C"></asp:CheckBox>
</p>
<p>
<asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
<asp:Button id="btnTree" onclick="btnTree_Click" runat="server" Text="Contrôles"></asp:Button>
</p>
<p>
<asp:ListBox id="lstInfos" runat="server" EnableViewState="False" Rows="6" Height="131px"></asp:ListBox>
</p>
</form>
</body>
</html>
Wir müssen die Prozedur [btnEnvoyer_Click] schreiben, um das [Click]-Ereignis dieser Schaltfläche zu verarbeiten. Der Status eines Optionsfelds oder eines Kontrollkästchens wird durch das Attribut [Checked] bestimmt, das den Wert „true“ annimmt, wenn das Kontrollkästchen aktiviert ist, und andernfalls den Wert „false“. Wir müssen also lediglich den Wert des Attributs [Checked] für die sechs Optionsfelder und Kontrollkästchen in die Liste [lstInfos] schreiben. Da dies keine besonderen Schwierigkeiten bereitet, versuchen wir etwas anderes:
<script runat="server">
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' place information in the listbox
for each c as control in FindControl("frmControls").controls
' is the control derived from CheckBox?
if TypeOf(c) is CheckBox then
lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
end if
next
End Sub
</script>
Die Seite kann als Baumstruktur aus Steuerelementen betrachtet werden. In unserem Beispiel enthält unsere Seite Text- und Serversteuerelemente. Text wird als spezielles Steuerelement namens [LiteralControl] behandelt. Jeder Text erstellt dieses Steuerelement, sogar eine Folge von Leerzeichen zwischen zwei Steuerelementen. Jedes Steuerelement verfügt über ein ID-Attribut, das es identifiziert. Es ist das ID-Attribut, das in den Tags erscheint:
Wenn wir die [LiteralControl]-Steuerelemente außer Acht lassen, verfügt die betreffende Seite über die folgenden Steuerelemente:
- [HtmlForm], das Formular [ID=frmControls]. Dieses ist wiederum ein Container für Steuerelemente. Es enthält die folgenden Steuerelemente:
-- [ID=RadioButton1] vom Typ [RadioButton]
-- [ID=RadioButton2] vom Typ [RadioButton]
-- [ID=RadioButton3] vom Typ [RadioButton]
-- [ID=CheckBoxA] vom Typ [CheckBox]
-- [ID=CheckBoxA] vom Typ [CheckBox]
-- [ID=CheckBoxA] vom Typ [CheckBox]
-- [ID=btnEnvoyer] vom Typ [Button]
Ein Steuerelement verfügt über die folgenden Eigenschaften:
gibt die Sammlung der untergeordneten Steuerelemente von [Control] zurück, sofern vorhanden | |
gibt das durch ID identifizierte Steuerelement zurück, das sich an der Wurzel des Baums der untergeordneten Steuerelemente von [Control] befindet. Im obigen Beispiel: Page.FindControl("frmControls") bezieht sich auf den [HtmlForm]-Container. Um auf das Optionsfeld [RadioButton1] zuzugreifen, müssen Sie schreiben Page.FindControl("frmControls").FindControl("RadioButton1") | |
[Control]-Bezeichner |
Kehren wir zum Code für die Prozedur [btnEnvoyer_Click] zurück:
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' place information in the listbox
for each c as control in FindControl("frmControls").controls
' is the control derived from CheckBox?
if TypeOf(c) is CheckBox then
lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
end if
next
End Sub
Wir möchten den Status der Optionsfelder und Kontrollkästchen im Formular anzeigen. Wir durchlaufen alle Steuerelemente im Formular. Wenn das aktuelle Steuerelement von einem von [CheckBox] abgeleiteten Typ ist, zeigen wir seine [Checked]-Eigenschaft an. Da die Klasse [RadioButton] von der Klasse [CheckBox] abgeleitet ist, gilt die Prüfung für beide Arten von Steuerelementen. Der oben gezeigte Screenshot zeigt ein Beispiel für die Ausgabe.
7.10. Die Komponenten CheckBoxList und RadioButtonList
Manchmal möchten wir dem Benutzer ermöglichen, zwischen Werten zu wählen, die zum Zeitpunkt der Seitenerstellung noch unbekannt sind. Diese Auswahlmöglichkeiten stammen aus einer Konfigurationsdatei, einer Datenbank usw. und sind erst zur Laufzeit bekannt. Es gibt Lösungen für dieses Problem, und wir sind ihnen bereits begegnet. Die Einfachauswahlliste eignet sich, wenn der Benutzer nur eine Auswahl treffen kann, und die Mehrfachauswahlliste, wenn er mehrere auswählen kann. Aus ästhetischer Sicht und wenn die Anzahl der Auswahlmöglichkeiten nicht groß ist, möchten Sie vielleicht Radiobuttons anstelle der Einfachauswahlliste oder Checkboxen anstelle der Mehrfachauswahlliste verwenden. Dies ist mit den Klassen [CheckBoxList] und [RadioButtonList] möglich.
Die Klassen [CheckBoxList] und [RadioButtonList] leiten sich von derselben Klasse [ListControl] ab wie die zuvor besprochenen Klassen [DropDownList] und [ListBox]. Daher finden wir hier einige der Eigenschaften und Methoden wieder, die wir von diesen Klassen kennen – insbesondere diejenigen, die tatsächlich zu [ListControl] gehören.
Eine Sammlung vom Typ [ListItemCollection], die die Elemente der Dropdown-Liste enthält. Die Elemente dieser Sammlung sind vom Typ [ListItem]. | |
Die Anzahl der Elemente in der [Items]-Sammlung | |
Element Nummer i in der Liste – vom Typ [ListItem] | |
um ein neues Element [ListItem] zur [Items]-Sammlung hinzuzufügen | |
um alle Elemente aus der [Items]-Sammlung zu entfernen | |
um Element Nummer i aus der [Items]-Sammlung zu entfernen | |
Das erste [ListItem] in der [Items]-Sammlung, dessen [Selected]-Eigenschaft auf „true“ gesetzt ist | |
Index des vorherigen Elements in der [Items]-Sammlung |
Einige Eigenschaften sind spezifisch für die Klassen [CheckBoxList] und [RadioButtonList]:
[horizontal] oder [vertical] für horizontale oder vertikale Listen. |
Die Elemente in der [Items]-Sammlung sind vom Typ [ListItem]. Jedes [ListItem]-Element generiert ein anderes Tag, je nachdem, ob es sich um ein [CheckBoxList]- oder ein [RadioButtonList]-Objekt handelt:
Oder
Wir beschreiben einige Eigenschaften und Methoden der Klasse [ListItem]:
Konstruktor – erstellt ein [ListItem]-Element mit den Eigenschaften text und value. Ein ListItem(T,V)-Element generiert je nach Bedarf den HTML-Tag <input type="checkbox" value="V">T oder <input type="radio" value="V">T. | |
Boolean. Wenn true, erhält die entsprechende Option in der HTML-Liste das Attribut [selected="selected"]. Dieses Attribut teilt dem Browser mit, dass das entsprechende Element in der HTML-Liste als ausgewählt angezeigt werden soll | |
Der Text T der HTML-Option <input type=".." value="V" [selected="selected"]>T | |
Der Wert des Value-Attributs der HTML-Option <input type=".." value="V" [selected="selected"]>T |
Wir schlagen vor, die folgende Seite [form8b.aspx] zu erstellen:

Nr. | Name | Typ | Eigenschaften | Rolle |
1 | RadioButtonList | EnableViewState=true Wiederholungsrichtung=horizontal | Liste von Optionsfeldern | |
2 | CheckBoxList | EnableViewState=true Wiederholungsrichtung=horizontal | Checkbox-Liste | |
3 | Schaltfläche | [Senden]-Schaltfläche, die in [4] die Liste der aus beiden Listen ausgewählten Elemente anzeigt | ||
4 | ListBox | EnableViewState=false | Liste der Werte |
Der Code für das Seitenlayout lautet wie folgt:
<%@ Page Language="VB" autoeventwireup="false" %>
<script runat="server">
...
</script>
<html>
<head>
</head>
<body>
<form id="frmControls" runat="server">
<h3>Listes de cases à cocher
</h3>
<p>
<asp:RadioButtonList id="RadioButtonList1" runat="server" RepeatDirection="Horizontal"></asp:RadioButtonList>
</p>
<p>
<asp:CheckBoxList id="CheckBoxList1" runat="server" RepeatDirection="Horizontal"></asp:CheckBoxList>
</p>
<p>
<asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
</p>
<p>
<asp:ListBox id="lstInfos" runat="server" EnableViewState="False" Rows="6"></asp:ListBox>
</p>
</form>
</body>
</html>
Der Steuerelementcode lautet wie folgt:
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs) handles MyBase.Load
' fill in the lists if it's the 1st time you're called
if not IsPostBack then
' texts for the RadioButton list
dim textesRadio() as String = {"1","2","3","4"}
' texts for the CheckBox list
dim textesCheckBox() as String = {"un","deux","trois","quatre"}
' radio list filling
dim i as integer
for i=0 to textesRadio.length-1
RadioButtonList1.Items.Add(new ListItem(textesRadio(i)))
next
' selection item no. 1
RadioButtonList1.SelectedIndex=1
' checkbox list filling
for i=0 to textesCheckBox.length-1
CheckBoxList1.Items.Add(new ListItem(textesCheckBox(i)))
next
end if
end sub
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' place information in the lstinfos listbox
affiche(RadioButtonList1)
affiche(CheckBoxList1)
End Sub
sub affiche(l1 as ListControl)
' displays the values of selected elements of l1
' anything to do?
if l1.selectedindex=-1 then return
dim i as integer
' we start at the end
for i= 0 to l1.items.count-1
' selected?
if l1.items(i).selected then
lstInfos.Items.Add("["+TypeName(l1)+"] ["+l1.items(i).text+"] sélectionné")
end if
next
end sub
</script>
In der Prozedur [Page_Load], die bei jedem Laden der Seite ausgeführt wird, werden die beiden Listen initialisiert. Um zu verhindern, dass sie jedes Mal initialisiert werden, verwenden wir die Eigenschaft [IsPostBack], um dies nur beim ersten Mal zu tun. Bei nachfolgenden Ladevorgängen werden die Listen automatisch durch den [VIEWSTATE]-Mechanismus neu generiert. Sobald die Seite angezeigt wird, markiert der Benutzer bestimmte Kontrollkästchen und klickt auf die Schaltfläche [Submit]. Die Formularwerte werden dann an das Formular selbst gesendet. Nach der Ausführung von [Page_Load] wird die Prozedur [btnEnvoyer_Click] ausgeführt. Diese Prozedur ruft die Prozedur [affiche] auf, um die Liste [lstInfos] zu füllen. Diese Prozedur erhält ein [ListControl]-Objekt als Parameter, wodurch sie entweder ein [RadioButtonList]-Objekt oder ein [CheckBoxList]-Objekt akzeptieren kann – Klassen, die von [ListControl] abgeleitet sind. Das Attribut [EnableViewState] der Liste [lstInfos] kann auf [false] gesetzt werden, da ihr Zustand zwischen den Anfragen nicht beibehalten werden muss.
7.11. Die Komponenten „Panel“ und „LinkButton“
Mit dem <asp:panel>-Tag können Sie einen Container mit Steuerelementen in eine Seite einfügen. Der Vorteil des Containers besteht darin, dass einige seiner Eigenschaften für alle darin enthaltenen Steuerelemente gelten. Dies ist bei der Eigenschaft [Visible] der Fall. Diese Eigenschaft ist bei jedem serverseitigen Steuerelement vorhanden. Wenn ein Container die Eigenschaft [Visible=false] hat, wird jedes seiner Steuerelemente durch seine eigene [Visible]-Eigenschaft gesteuert. Wenn er die Eigenschaft [Visible=false] hat, werden der Container und alles, was er enthält, nicht angezeigt. Dies kann einfacher sein, als die [Visible]-Eigenschaft jedes einzelnen Steuerelements des Containers zu verwalten.
Mit dem <asp:LinkButton>-Tag können Sie einen Link in den Präsentationscode einer Seite einfügen. Es erfüllt einen ähnlichen Zweck wie das [Button]-Steuerelement. Es löst mithilfe einer zugehörigen JavaScript-Funktion einen clientseitigen POST-Request aus. Wir erstellen eine Seite [form9.aspx], um das folgende Layout zu erzeugen:

Nr. | name | Typ | Eigenschaften | Rolle |
1 | Panel | EnableViewState=true | Steuerelement-Container | |
2 | ListBox | EnableViewState=true | eine Liste mit drei Werten | |
3 | LinkButton | EnableViewState=false | Link zum Ausblenden des Containers |
Wenn der Container ausgeblendet ist, erscheint ein neuer Link:

Nr. | name | Typ | Eigenschaften | Rolle |
4 | LinkButton | EnableViewState=false | Link zum Anzeigen des Containers |
Der Code für das Seitenlayout lautet wie folgt:
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:Panel id="Panel1" runat="server" BorderStyle="Ridge" BorderWidth="1px">
<p>
Conteneur
</p>
<p>
<asp:ListBox id="ListBox1" runat="server">
<asp:ListItem Value="1">un</asp:ListItem>
<asp:ListItem Value="2">deux</asp:ListItem>
<asp:ListItem Value="3" Selected="True">trois</asp:ListItem>
</asp:ListBox>
</p>
</asp:Panel>
</p>
<p>
<asp:LinkButton id="lnkVoir" onclick="lnkVoir_Click" runat="server">Voir le conteneur</asp:LinkButton>
</p>
<p>
<asp:LinkButton id="lnkCacher" onclick="lnkCacher_Click" runat="server">Cacher le conteneur</asp:LinkButton>
</p>
</form>
</body>
</html>
Beachten Sie, dass dieser Code die Liste [ListBox1] mit drei Werten initialisiert. Die [Click]-Ereignisbehandler für die beiden Links lauten wie folgt:
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
...
end sub
Sub lnkVoir_Click(sender As Object, e As EventArgs)
' displays container 1
panel1.Visible=true
' changing links
lnkVoir.visible=false
lnkCacher.visible=true
End Sub
Sub lnkCacher_Click(sender As Object, e As EventArgs)
' hides container 1
panel1.Visible=false
' changing links
lnkVoir.visible=true
lnkCacher.visible=false
End Sub
</script>
Wir verwenden die Prozedur [Page_Load], um das Formular zu initialisieren. Dies erfolgt bei der ersten Anfrage (IsPostBack=false):
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
' the 1st time
if not IsPostBack then
' the container is shown
lnkVoir_Click(nothing,nothing)
end if
end sub
.....
</script>
7.12. Weiter...
In den vorangegangenen Abschnitten wurden eine Reihe von serverseitigen Komponenten vorgestellt. In jedem Abschnitt wurden nur einige ihrer Eigenschaften behandelt. Um diese Komponenten eingehender zu untersuchen, stehen dem Leser mehrere Möglichkeiten zur Verfügung:
- die Eigenschaften einer Komponente mithilfe einer IDE wie WebMatrix erkunden. Dieses Tool zeigt die wichtigsten Eigenschaften der verwendeten Komponenten in einer Form an
- die .NET-Dokumentation konsultieren, um alle Klassen zu erkunden, die den einzelnen Serverkomponenten entsprechen. Dies ist die bevorzugte Methode, um die Komponente vollständig zu beherrschen. Dort finden Sie die Klassenhierarchie, die zu den Komponenten führt, sowie die Eigenschaften, Methoden, Konstruktoren und Ereignisse der einzelnen Komponenten. Darüber hinaus enthält die Dokumentation manchmal Beispiele.
In diesem Kapitel haben wir den [WebMatrix]-All-in-One-Ansatz verwendet, d. h. wir haben den Präsentationscode und den Steuerungscode für eine Seite in derselben Datei platziert. Generell empfehlen wir diese Methode nicht, sondern den zuvor verwendeten [Codebehind]-Ansatz, bei dem diese beiden Arten von Code in zwei separaten Dateien untergebracht werden. Es ist anzumerken, dass der Vorteil dieser Trennung darin liegt, dass der Steuerungscode kompiliert werden kann, ohne dass die Webanwendung ausgeführt werden muss. Darüber hinaus hatten unsere Beispiele, wie wir zu Beginn dieses Kapitels erläutert haben, eine sehr spezifische Struktur: Sie bestanden aus einer einzigen Seite, die als Formular diente, das in aufeinanderfolgenden Anfrage-Antwort-Zyklen zwischen dem Client und dem Server ausgetauscht wurde, wobei die erste Client-Anfrage eine GET-Anfrage und die nachfolgenden POST-Anfragen waren.
7.13. Serverkomponenten und Anwendungscontroller
In den vorangegangenen Kapiteln haben wir mehrere Webanwendungen erstellt. Sie wurden alle unter Verwendung der MVC-Architektur (Model-View-Controller) entwickelt, die die Anwendung in separate Blöcke unterteilt und die Wartung erleichtert. Bisher haben wir unsere Benutzeroberflächen mit Standard-HTML-Tags erstellt. Angesichts dessen, was wir gerade behandelt haben, liegt es nahe, nun Serverkomponenten zu verwenden. Kehren wir zu einem Problem zurück, das wir bereits ausführlich behandelt haben: die Steuerberechnung. Die MVC-Architektur sah wie folgt aus:

Die Anwendung verfügt über zwei Ansichten: [form.aspx] und [errors.aspx]. Die Ansicht [form.aspx] wird angezeigt, wenn die URL [main.aspx] zum ersten Mal aufgerufen wird:

Der Benutzer füllt das Formular aus:

und klickt auf die Schaltfläche [Berechnen], um die folgende Antwort zu erhalten:

In einer MVC-Anwendung muss jede Anfrage über den Controller laufen, in diesem Fall [main.aspx]. Das bedeutet, dass das Formular [form.aspx], sobald es vom Benutzer ausgefüllt wurde, an [main.aspx] und nicht an [form.aspx] gesendet werden muss. Dies ist schlichtweg nicht möglich, wenn wir die Benutzeroberfläche von [form.aspx] mit ASP-Serverkomponenten erstellen. Um dies zu veranschaulichen, erstellen wir ein Formular [formtest.aspx] mit einer <asp:button>-Komponente:
<%@ Page Language="VB" EnableViewState="false"%>
<html>
<head>
<title>test</title>
</head>
<body>
<form action="main.aspx" runat="server">
<p>
<asp:Button id="btnTest" runat="server" EnableViewState="false" Text="Test"></asp:Button>
</p>
</form>
</body>
</html>
Beachten Sie das Attribut [action="main.aspx"] des Tags <form>. Starten wir diese Anwendung. Die Anzeigeseite zeigt nur eine Schaltfläche:

Sehen wir uns den vom Server gesendeten HTML-Code an:
<html>
<head>
<title>test</title>
</head>
<body>
<form name="_ctl0" method="post" action="formtest.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwtNTMwNzcxMzI0Ozs+" />
<p>
<input type="submit" name="btnTest" value="Test" id="btnTest" />
</p>
</form>
</body>
</html>
Wir sehen, dass die POST-Anfrage des Formulars auf das Formular selbst abzielt [action="formtest.aspx"], während wir den serverseitigen HTML-Tag in [formtest.aspx] geschrieben hatten:
Das Attribut [runat="server"] des Tags <form> ist bei der Verwendung serverseitiger Komponenten erforderlich. Wenn wir dieses Attribut nicht einfügen, tritt ein Kompilierungsfehler auf. Wenn wir es einfügen, wird das Attribut [action] des Tags <form> ignoriert. Der Server generiert immer ein [action]-Attribut, das auf das Formular selbst verweist. Daraus lässt sich schließen, dass wir in einer MVC-Anwendung keine Formulare verwenden können, die mit dem <form ... runat="server">-Tag erstellt wurden. Dieses Tag ist jedoch für alle ASP-Serverkomponenten unerlässlich, die Benutzereingaben abrufen. Mit anderen Worten: Wir können in einer MVC-Anwendung keine ASP-Serverformulare verwenden. Dies ist eine wichtige Erkenntnis. Tatsächlich ist eines der Hauptverkaufsargumente von ASP.NET, dass man eine Webanwendung wie eine Windows-Anwendung erstellen kann. Dies trifft zu, wenn Ihre Anwendung nicht der MVC-Architektur folgt, ansonsten jedoch weniger. Dennoch scheint die MVC-Architektur ein grundlegendes Konzept in der modernen Webentwicklung zu sein, das schwer zu ignorieren ist.
Es ist möglich, die MVC-Architektur in Verbindung mit ASP-Komponentenformularen für Anwendungen mit wenigen verschiedenen Ansichten zu verwenden, indem man den folgenden Workaround nutzt:
- Die Anwendung besteht aus einer einzigen Seite, die als Controller fungiert
- Die Ansichten werden auf dieser Seite durch verschiedene Container dargestellt, wobei jeder Container einer Ansicht entspricht. Um eine Ansicht anzuzeigen, machen wir ihren Container sichtbar und blenden die anderen aus
Dies ist eine elegante Lösung, die wir nun anhand einiger Beispiele umsetzen werden
7.14. Beispiele für MVC-Anwendungen mit ASP-Serverkomponenten
7.14.1. Beispiel 1
In diesem ersten Beispiel implementieren wir die vorgestellten Serverkomponenten. Die Seite [form10.aspx] sieht wie folgt aus:
![]() | ![]() |
Der Screenshot oben links zeigt das Formular, wie es dem Benutzer angezeigt wird. Der Benutzer füllt es aus und sendet es durch Klicken auf [Submit] ab. Der Server gibt eine Ansicht zurück, die eine Liste der eingegebenen Werte anzeigt (Screenshot rechts). Über einen Link kann der Benutzer zum Formular zurückkehren. Er sieht es genau so, wie er es abgeschickt hat. Der Präsentationscode für [form10.aspx] lautet wie folgt:
<html>
<head>
<title>Exemple</title> <script language="javascript">
function effacer(){
alert("Vous avez cliqué sur [Effacer]")
}
</script>
</head>
<body>
<p>
Gestion d'un formulaire
</p>
<p>
<hr />
</p>
<form runat="server">
<p>
<asp:Panel id="panelinfo" runat="server" EnableViewState="False">
<p>
Liste des valeurs obtenues
</p>
<p>
<asp:ListBox id="lstInfos" runat="server" EnableViewState="False"></asp:ListBox>
</p>
<p>
<asp:LinkButton id="LinkButton1" onclick="LinkButton1_Click" runat="server">Retour au formulaire</asp:LinkButton>
</p>
<p>
<hr />
</p>
</asp:Panel>
</p>
<p>
<asp:Panel id="panelform" runat="server" >
<table>
<tbody>
<tr>
<td>
Etes-vous marié(e)</td>
<td>
<asp:RadioButton id="rdOui" runat="server" GroupName="rdmarie"></asp:RadioButton>
Oui<asp:RadioButton id="rdNon" runat="server" GroupName="rdmarie" Checked="True"></asp:RadioButton>
Non</td>
</tr>
<tr>
<td>
Cases à cocher</td>
<td>
<asp:CheckBox id="chk1" runat="server"></asp:CheckBox>
1<asp:CheckBox id="chk2" runat="server"></asp:CheckBox>
2<asp:CheckBox id="chk3" runat="server"></asp:CheckBox>
3</td>
</tr>
<tr>
<td>
Champ de saisie</td>
<td>
<asp:TextBox id="txtSaisie" runat="server" MaxLength="20" Columns="20"></asp:TextBox>
</td>
</tr>
<tr>
<td>
Mot de passe</td>
<td>
<asp:TextBox id="txtmdp" runat="server" MaxLength="10" Columns="10" TextMode="Password"></asp:TextBox>
</td>
</tr>
<tr>
<td>
Boîte de saisie</td>
<td>
<asp:TextBox id="txtArea" runat="server" Columns="20" TextMode="MultiLine" Rows="3"></asp:TextBox>
</td>
</tr>
<tr>
<td>
Liste déroulante</td>
<td>
<asp:DropDownList id="cmbValeurs" runat="server"></asp:DropDownList>
</td>
</tr>
<tr>
<td>
Liste à choix unique</td>
<td>
<asp:ListBox id="lstSimple" runat="server"></asp:ListBox>
<asp:Button id="btnRazSimple" onclick="btnRazSimple_Click" runat="server" EnableViewState="False" Text="Raz"></asp:Button>
</td>
</tr>
<tr>
<td>
Liste à choix multiple</td>
<td>
<asp:ListBox id="lstMultiple" runat="server" SelectionMode="Multiple"></asp:ListBox>
<asp:Button id="razMultiple" onclick="razMultiple_Click" runat="server" EnableViewState="False" Text="Raz"></asp:Button>
</td>
</tr>
<tr>
<td>
Champ caché</td>
<td>
<asp:Label id="lblSecret" runat="server" visible="False"></asp:Label></td>
</tr>
<tr>
<td>
Bouton simple</td>
<td>
<input id="btnEffacer" onclick="effacer()" type="button" value="Effacer" /></td>
</tr>
<tr>
<td>
Bouton [reset]</td>
<td>
<input id="btnReset" type="reset" value="Rétablir" /></td>
</tr>
<tr>
<td>
Bouton [submit]</td>
<td>
<asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" EnableViewState="False" Text="Envoyer"></asp:Button>
</td>
</tr>
</tbody>
</table>
</asp:Panel>
</p>
</form>
</body>
</html>
Die Seite enthält zwei Container, einen für jede Ansicht: [panelform] für die Formularansicht, [panelinfo] für die Informationsansicht. Die Liste der Komponenten im Container [panelForm] lautet wie folgt:
name | Typ | Eigenschaften | Rolle |
Panel | EnableViewState=true | Formularansicht | |
Optionsfeld | EnableViewState=true Gruppenname=rdmarie | Optionsfelder | |
Kontrollkästchen | EnableViewState=true | Kontrollkästchen | |
Textfeld | EnableViewState=true | Eingabefeld | |
TextBox | EnableViewState=true | geschütztes Eingabefeld | |
TextBox | EnableViewState=true | mehrzeiliges Textfeld | |
DropDownList | EnableViewState=true | Dropdown-Liste | |
ListBox | EnableViewState=true Auswahlmodus=Einzelselektion | Einzelauswahlliste | |
Schaltfläche | EnableViewState=false | Deaktiviert alle Elemente in lstSimple | |
ListBox | EnableViewState=true SelectionMode=Multiple | Liste mit Mehrfachauswahl | |
Schaltfläche | EnableViewState=false | Alle Elemente in lstMultiple abwählen | |
Beschriftung | EnableViewState=true Visible=false | verstecktes Feld | |
Standard-HTML | zeigt eine Meldung an | ||
Schaltfläche | EnableViewState=false | [submit]-Schaltfläche im Formular | |
Standard-HTML | Formular [Reset]-Schaltfläche |
Die Rolle von [VIEWSTATE] für Komponenten ist hier wichtig. Alle Komponenten außer Schaltflächen müssen die Eigenschaft [EnableViewState=true] haben. Um zu verstehen, warum das so ist, müssen wir uns in Erinnerung rufen, wie die Anwendung funktioniert. Angenommen, das Feld [txtSaisie] hat die Eigenschaft [EnableViewState=false]:
- Der Client fordert die Seite [form10.aspx] zum ersten Mal an. Er erhält die Formularansicht
- , füllt sie aus und sendet sie über die Schaltfläche [Submit] ab. Die Eingabefelder werden dann übermittelt, und der Server weist den übermittelten Wert oder deren [VIEWSTATE] den serverseitigen Komponenten zu, sofern diese einen solchen haben. Somit wird dem Feld [txtSaisie] der vom Benutzer eingegebene Wert zugewiesen. Daher hat sein [VIEWSTATE] in diesem Schritt keine Funktion. Als Ergebnis des Vorgangs wird die Ansicht [informations] gesendet – bei der es sich eigentlich immer noch um die Seite [form10.aspx] handelt, jedoch mit einem anderen angezeigten Container.
- Der Benutzer ruft diese neue Ansicht auf und kehrt über den Link [Zurück zum Formular] dorthin zurück. Daraufhin wird eine POST-Anfrage an [form10.aspx] gesendet. Es wird höchstens ein Wert übermittelt: der vom Benutzer aus der Informationsliste ausgewählte Wert, der im weiteren Verlauf nicht verwendet wird. In jedem Fall wird kein Wert für das Feld [txtSaisie] übermittelt.
- Der Server empfängt den POST und weist den übermittelten Wert den Serverkomponenten oder deren [VIEWSTATE] zu, sofern vorhanden. Hier hat [txtNom] keinen übermittelten Wert. Wenn sein Attribut [EnableViewState] auf [false] gesetzt ist, wird ihm die leere Zeichenfolge zugewiesen. Da wir möchten, dass es den vom Benutzer eingegebenen Wert enthält, muss es die Eigenschaft [EnableViewState=true] haben.
Der [panelinfo]-Container enthält die folgenden Steuerelemente:
name | type | Eigenschaften | Rolle |
Panel | EnableViewState=false | Informationsansicht | |
ListBox | EnableViewState=false | Liste mit Informationen, die die vom Benutzer eingegebenen Werte zusammenfassen | |
LinkButton | EnableViewState=false | Link zurück zum Formular |
Wenn wir uns während des Testens den vom obigen Präsentationscode generierten HTML-Code ansehen, werden wir möglicherweise von dem für das versteckte Feld [lblSecret] generierten Code überrascht sein:
Die Komponente [lblSecret] wird nicht als HTML gerendert, da sie die Eigenschaft [Visible=false] besitzt. Da sie jedoch die Eigenschaft [EnableViewState=true] besitzt, wird ihr Wert dennoch im versteckten Feld [__VIEWSATE] gespeichert. Daher können wir ihn abrufen, wie die Tests zeigen werden.
Wir müssen noch die Ereignisbehandler schreiben. In [Page_Load] initialisieren wir das Formular:
Sub page_Load(sender As Object, e As EventArgs)
' the 1st time, we initialize the elements
' subsequent times, they are reset to their values by VIEWSTATE
if IsPostBack then return
' init form
' panelinfo not displayed
panelinfo.visible=false
' paneform displayed
panelform.visible=true
' radio buttons
rdNon.Checked=true
' checkboxes
chk2.Checked=true
' input field
txtSaisie.Text="qqs mots"
' password field
txtMdp.Text="ceciestsecret"
' input box
txtArea.Text="ligne"+ControlChars.CrLf+"ligne2"+ControlChars.CrLf
' combo
dim i as integer
for i=1 to 4
cmbValeurs.Items.Add(new ListItem("choix"+i.ToString,i.ToString))
next
cmbValeurs.SelectedIndex=1
' simple selection list
for i=1 to 7
lstSimple.Items.Add(new ListItem("simple"+i.ToString,i.ToString))
next
lstSimple.SelectedIndex=0
' multiple selection list
for i=1 to 10
lstMultiple.Items.Add(new ListItem("multiple"+i.ToString,i.ToString))
next
lstMultiple.Items(0).Selected=true
lstMultiple.Items(2).Selected=true
' hidden field
lblSecret.Text="secret"
End Sub
Klicken auf die Schaltflächen [lstRazSimple] und [lstMultiple]:
Sub btnRazSimple_Click(sender As Object, e As EventArgs)
' raz single list
lstSimple.SelectedIndex=-1
End Sub
Sub razMultiple_Click(sender As Object, e As EventArgs)
' raz multiple list
lstMultiple.SelectedIndex=-1
End Sub
Klicken auf die Schaltfläche [Senden]:
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' the info panel is made visible and the form panel is hidden
panelinfo.Visible=true
panelform.visible=false
' we retrieve the posted values and put them in lstInfos
' radio buttons
dim info as string="état marital : "+iif(rdoui.checked,"marié"," non marié")
affiche(info)
' checkboxes
info=" cases cochées : "+iif(chk1.checked,"1 oui","1 non")+","+ _
iif(chk2.checked,"2 oui","2 non")+","+iif(chk3.checked,"3 oui","3 non")
affiche(info)
' input field
affiche("champ de saisie : " + txtSaisie.Text.Trim)
' password
affiche("mot de passe : " + txtMdp.Text.Trim)
' input box
dim lignes() as String
lignes=new Regex("\r\n").Split(txtArea.Text.Trim)
dim i as integer
for i=0 to lignes.length-1
lignes(i)="["+lignes(i).Trim+"]"
next
affiche("Boîte de saisie : " + String.Join(",",lignes))
' combo
affiche("éléments sélectionnés dans combo : "+selection(cmbValeurs))
' simple list
affiche("éléments sélectionnés dans liste simple : "+selection(lstSimple))
' multiple list
affiche("éléments sélectionnés dans liste multiple : "+selection(lstMultiple))
' hidden field
affiche ("Champ caché : " + lblSecret.Text)
End Sub
sub affiche(msg as String)
' displays msg in lstInfos
lstInfos.Items.Add(msg)
end sub
function selection(liste as ListControl) as string
' browse list elements
' to find those selected
dim i as integer
dim info as string=""
for i=0 to liste.Items.Count-1
if liste.Items(i).Selected then info+="[" + liste.Items(i).Text + "]"
next
return info
end function
Zum Schluss noch das Klicken auf den Link [Zurück zum Formular]:
Sub LinkButton1_Click(sender As Object, e As EventArgs)
' display the form and hide the info panel
panelform.visible=true
panelinfo.visible=false
End Sub
7.14.2. Beispiel 2
Hier greifen wir eine zuvor behandelte Anwendung wieder auf, die Standard-HTML-Formulare verwendet. Die Anwendung ermöglicht es Benutzern, Steuerberechnungssimulationen durchzuführen. Sie stützt sich auf eine [impot]-Klasse, auf die wir hier nicht näher eingehen werden. Diese Klasse benötigt Daten, die sie aus einer OLEDB-Datenquelle abruft. Für dieses Beispiel verwenden wir eine ACCESS-Datenquelle.
7.14.2.1. Die MVC-Struktur der Anwendung
Die MVC-Struktur der Anwendung sieht wie folgt aus:

Die drei Ansichten werden als Container in den Präsentationscode des Controllers [main.aspx] eingebunden. Daher verfügt diese Anwendung über eine einzige Seite [main.aspx].
7.14.2.2. Die Ansichten der Webanwendung
Die [form]-Ansicht ist das Formular zur Eingabe von Informationen, die zur Berechnung der Steuer eines Benutzers verwendet werden:

Der Benutzer füllt das Formular aus:

Er nutzt die Schaltfläche [Absenden], um eine Steuerberechnung anzufordern. Er sieht die folgende Ansicht [Simulationen]:

Über den Link oben kehrt er zum Formular zurück. Er findet es in dem Zustand vor, in dem er es ausgefüllt hat. Es können Eingabefehler auftreten:

Diese werden in der Ansicht [Fehler] markiert:

Er kehrt über den obigen Link zum Formular zurück. Er findet es in dem Zustand vor, in dem er es verlassen hat. Er kann neue Simulationen durchführen:

Er sieht dann die Ansicht [Simulationen] mit einer zusätzlichen Simulation:

Wenn die Datenquelle nicht verfügbar ist, wird der Benutzer schließlich in der Ansicht [Fehler] benachrichtigt:

7.14.2.3. Der Präsentationscode der Anwendung
Denken Sie daran, dass die Seite [main.aspx] alle Ansichten zusammenführt. Es handelt sich um ein einziges Formular mit drei Containern:
- [panelform] für die Ansicht [form]
- [panelerrors] für die Ansicht [Fehler]
- [panelsimulations] für die Ansicht [simulations]
Wir kehren dazu zurück, den Präsentationscode und den Steuerungscode in zwei separate Dateien aufzuteilen. Die erste befindet sich in [main.aspx] und die zweite in [main.aspx.vb]. Der Code für [main.aspx] lautet wie folgt:
<%@ page codebehind="main.aspx.vb" inherits="vs.main" AutoEventWireUp="false" %>
<HTML>
<HEAD>
<title>Calcul d'impôt </title>
</HEAD>
<body>
<P>Calcul de votre impôt</P>
<HR width="100%" SIZE="1">
<FORM id="Form1" runat="server">
<asp:panel id="panelform" Runat="server">
<TABLE id="Table1" cellSpacing="1" cellPadding="1" border="0">
<TR>
<TD height="19">Etes-vous marié(e)</TD>
<TD height="19">
<asp:RadioButton id="rdOui" runat="server" GroupName="rdMarie"></asp:RadioButton>Oui
<asp:RadioButton id="rdNon" runat="server" GroupName="rdMarie" Checked="True"></asp:RadioButton>Non</TD>
</TR>
<TR>
<TD>Nombre d'enfants</TD>
<TD>
<asp:TextBox id="txtEnfants" runat="server" MaxLength="3" Columns="3"></asp:TextBox></TD>
</TR>
<TR>
<TD>Salaire annuel (euro)</TD>
<TD>
<asp:TextBox id="txtSalaire" runat="server" MaxLength="10" Columns="10"></asp:TextBox></TD>
</TR>
</TABLE>
<P>
<asp:Button id="btnCalculer" runat="server" Text="Calculer"></asp:Button>
<asp:Button id="btnEffacer" runat="server" Text="Effacer"></asp:Button></P>
</asp:panel>
<asp:panel id="panelerreurs" runat="server">
<P>Les erreurs suivantes se sont produites :</P>
<P>
<asp:Literal id="erreursHTML" runat="server"></asp:Literal></P>
<P></P>
<asp:LinkButton id="lnkForm1" runat="server">Retour au formulaire</asp:LinkButton>
</asp:panel>
<asp:panel id="panelsimulations" runat="server">
<P>
<TABLE>
<TR>
<TH>
Marié</TH>
<TH>
Enfants</TH>
<TH>
Salaire annuel</TH>
<TH>
Impôt à payer (euro)</TH></TR>
<asp:Literal id="simulationsHTML" runat="server"></asp:Literal></TABLE>
<asp:LinkButton id="lnkForm2" runat="server">Retour au formulaire</asp:LinkButton></P>
</asp:panel>
</FORM>
</body>
</HTML>
Wir haben die drei Container definiert. Beachten Sie, dass sie sich alle innerhalb des <form runat="server">-Tags befinden. Dies ist zwingend erforderlich, da Serverkomponenten nur dann genutzt werden können, wenn sie innerhalb eines solchen Tags platziert sind. Der entscheidende Punkt ist, dass wir hier ein einziges Formular haben, das zwischen dem Client und dem Webserver ausgetauscht wird. Wir verwenden daher die in diesem Kapitel beschriebene Konfiguration für Serverkomponenten. Schauen wir uns die Komponenten der einzelnen Container genauer an:
[panelform]-Container:
name | Typ | Eigenschaften | Rolle |
Panel | EnableViewState=true | Formularansicht | |
Optionsfeld | EnableViewState=true Gruppenname=rdmarie | Optionsfelder | |
Textfeld | EnableViewState=true | Anzahl der Kinder | |
Textfeld | EnableViewState=true | Jahresgehalt | |
Schaltfläche | [submit]-Schaltfläche im Formular – startet die Steuerberechnung | ||
Schaltfläche | [Senden]-Schaltfläche im Formular – löscht das Formular |
[errorPanel]-Container:
name | Typ | Eigenschaften | Rolle |
Panel | EnableViewState=true | Fehleransicht | |
LinkButton | EnableViewState=true | Link zum Formular | |
Literal | HTML-Code für die Fehlerliste |
[panelsimulations] Container:
name | Typ | Eigenschaften | Rolle |
Panel | EnableViewState=true | Simulationsansicht | |
LinkButton | EnableViewState=true | Link zum Formular | |
Literal | HTML-Code für die Liste der Simulationen in einer HTML-Tabelle |
7.14.2.4. Der Steuerungscode der Anwendung
Der Anwendungssteuerungscode ist auf die Dateien [global.asax.vb] und [main.aspx.vb] verteilt. Die Datei [global.asax] ist wie folgt definiert:
Die Datei [global.asax.vb] sieht wie folgt aus:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Collections
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' create an impot object
Dim objImpot As impot
Try
objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
' put the object in the application
Application("objImpot") = objImpot
' no error
Application("erreur") = False
Catch ex As Exception
'there has been an error, we note it in the application
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' start of session - create a list of empty simulations
Session.Item("simulations") = New ArrayList
End Sub
End Class
Beim Start der Anwendung (erste Anfrage an die Anwendung) wird die Prozedur [Application_Start] ausgeführt. Sie versucht, ein Objekt vom Typ [impot] zu erstellen, indem sie dessen Daten aus einer OLEDB-Quelle abruft. Dem Leser wird empfohlen, Kapitel 5 zu lesen, in dem diese Klasse definiert wurde, falls er dies vergessen hat. Die Erstellung des [impot]-Objekts kann fehlschlagen, wenn die Datenquelle nicht verfügbar ist. In diesem Fall wird der Fehler in der Anwendung gespeichert, damit alle nachfolgenden Anfragen wissen, dass die Initialisierung nicht korrekt durchgeführt werden konnte. Wenn die Erstellung erfolgreich ist, wird das erstellte [impot]-Objekt ebenfalls in der Anwendung gespeichert. Es wird von allen Anfragen zur Steuerberechnung verwendet. Wenn ein Client seine erste Anfrage stellt, wird für ihn durch die Prozedur [Application_Start] eine Sitzung erstellt. Diese Sitzung dient dazu, die verschiedenen Steuerberechnungssimulationen zu speichern, die er durchführen wird. Diese werden in einem [ArrayList]-Objekt gespeichert, das mit dem Sitzungsschlüssel „simulations“ verknüpft ist. Zu Beginn der Sitzung ist dieser Schlüssel mit einem leeren [ArrayList]-Objekt verknüpft. Die von der Anwendung benötigten Informationen werden in ihrer Konfigurationsdatei [wenConfig] abgelegt:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="chaineConnexion" value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\webforms\vs\impots5\impots.mdb" />
</appSettings>
</configuration>
Der Schlüssel [connectionString] gibt die Verbindungszeichenfolge zur OLEDB-Quelle an. Der restliche Steuerungscode befindet sich in [main.aspx.vb]:
Imports System.Collections
Imports Microsoft.VisualBasic
Imports st.istia.univangers.fr
Imports System
Public Class main
Inherits System.Web.UI.Page
Protected WithEvents rdOui As System.Web.UI.WebControls.RadioButton
Protected WithEvents rdNon As System.Web.UI.WebControls.RadioButton
Protected WithEvents txtEnfants As System.Web.UI.WebControls.TextBox
Protected WithEvents txtSalaire As System.Web.UI.WebControls.TextBox
Protected WithEvents btnCalculer As System.Web.UI.WebControls.Button
Protected WithEvents btnEffacer As System.Web.UI.WebControls.Button
Protected WithEvents panelform As System.Web.UI.WebControls.Panel
Protected WithEvents lnkForm1 As System.Web.UI.WebControls.LinkButton
Protected WithEvents lnkForm2 As System.Web.UI.WebControls.LinkButton
Protected WithEvents panelerreurs As System.Web.UI.WebControls.Panel
Protected WithEvents panelsimulations As System.Web.UI.WebControls.Panel
Protected WithEvents simulationsHTML As System.Web.UI.WebControls.Literal
Protected WithEvents erreursHTML As System.Web.UI.WebControls.Literal
' local variables
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
End Sub
Private Sub afficheFormulaire()
...
End Sub
Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
...
End Sub
Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
...
End Sub
Private Function checkData() As ArrayList
...
End Function
Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
....
End Sub
Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
...
End Sub
Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
...
End Sub
Private Sub razForm()
...
End Sub
End Class
Le premier événement traité par le code est [Page_Load] :
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' first, we look at the state of the application
If CType(Application("erreur"), Boolean) Then
' the application failed to initialize
' the error view is displayed
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
afficheErreurs(erreurs, "")
Exit Sub
End If
' no errors - on the 1st request, the form is presented
If Not IsPostBack Then afficheFormulaire()
End Sub
Beachten Sie, dass bei Ausführung der Prozedur [Page_Load] nach einem POST-Request vom Client alle Formularkomponenten einen Wert haben: entweder den vom Client übermittelten Wert (falls vorhanden) oder den vorherigen Wert der Komponente über [VIEWSTATE]. In diesem Formular haben alle Komponenten die Eigenschaft [EnableViewState=true]. Bevor wir die Anfrage bearbeiten, stellen wir sicher, dass die Anwendung korrekt initialisiert wurde. Ist dies nicht der Fall, zeigen wir die Ansicht [errors] mithilfe der Prozedur [displayErrors] an. Handelt es sich um die erste Anfrage (IsPostBack=false), zeigen wir die Ansicht [form] mithilfe von [displayForm] an.
Die Prozedur, die die Ansicht [errors] anzeigt, lautet wie folgt:
Private Sub afficheErreurs(ByRef erreurs As ArrayList, ByRef lien As String)
' displays the error container
panelerreurs.Visible = True
Dim i As Integer
erreursHTML.Text = ""
For i = 0 To erreurs.Count - 1
erreursHTML.Text += "<li>" + erreurs(i).ToString + "</li>" + ControlChars.CrLf
Next
lnkForm1.Text = lien
' the other containers are hidden
panelform.Visible = False
panelsimulations.Visible = False
End Sub
Die Prozedur hat zwei Parameter:
- eine Liste von Fehlermeldungen in [errors]
- einen Linktext in [link]
Der für die Fehlerliste zu generierende HTML-Code wird im Literal [errorsHTML] abgelegt. Der Linktext wird in der Eigenschaft [Text] des Objekts [LinkButton] in der Ansicht abgelegt.
Die Prozedur, die die Ansicht [form] anzeigt, lautet wie folgt:
Private Sub afficheFormulaire()
' displays the form
panelform.Visible = True
' the other containers are hidden
panelerreurs.Visible = False
panelsimulations.Visible = False
End Sub
Diese Prozedur macht lediglich den [panelform]-Container sichtbar. Die Komponenten werden mit ihrem übermittelten oder vorherigen Wert (VIEWSTATE) angezeigt.
Wenn der Benutzer in der Ansicht [form] auf die Schaltfläche [Calculate] klickt, wird eine POST-Anfrage an [main.aspx] gesendet. Die Prozedur [Page_Load] wird ausgeführt, gefolgt von der Prozedur [btnCalculate_Click]:
Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
' check the validity of the data entered
Dim erreurs As ArrayList = checkData()
' if there are errors, we report them
If erreurs.Count <> 0 Then
' the error page is displayed
afficheErreurs(erreurs, "Retour au formulaire")
Exit Sub
End If
' no errors - tax is calculated
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
rdOui.Checked, CType(txtEnfants.Text, Integer), CType(txtSalaire.Text, Long))
' the result is added to existing simulations
Dim simulation() As String = New String() {CType(IIf(rdOui.Checked, "oui", "non"), String), _
txtEnfants.Text.Trim, txtSalaire.Text.Trim, impot.ToString}
' the result is added to existing simulations
Dim simulations As ArrayList = CType(Session.Item("simulations"), ArrayList)
simulations.Add(simulation)
' put simulations in session and context
Session.Item("simulations") = simulations
' the result page is displayed
afficheSimulations(simulations, "Retour au formulaire")
End Sub
Die Prozedur beginnt mit der Validierung der Formularfelder mithilfe der Prozedur [checkData], die eine [ArrayList] mit Fehlermeldungen zurückgibt. Ist die Liste nicht leer, wird die Ansicht [errors] angezeigt und die Prozedur beendet. Sind die eingegebenen Daten gültig, wird der Steuerbetrag mithilfe des Objekts [tax] berechnet, das beim Start in der Anwendung gespeichert wurde. Diese neue Simulation wird der Liste der bereits durchgeführten und in der Sitzung gespeicherten Simulationen hinzugefügt.
Die Funktion [CheckData] überprüft die Gültigkeit der Daten. Sie gibt eine [ArrayList] mit Fehlermeldungen zurück, die leer ist, wenn die Daten gültig sind:
Private Function checkData() As ArrayList
' initially no errors
Dim erreurs As New ArrayList
' no. of children
Try
Dim nbEnfants As Integer = CType(txtEnfants.Text, Integer)
If nbEnfants < 0 Then Throw New Exception
Catch
erreurs.Add("Le nombre d'enfants est incorrect")
End Try
' salary
Try
Dim salaire As Long = CType(txtSalaire.Text, Long)
If salaire < 0 Then Throw New Exception
Catch
erreurs.Add("Le salaire annuel est incorrect")
End Try
' return the list of errors
Return erreurs
End Function
Schließlich wird die Ansicht [simulations] durch die folgende Prozedur [displaySimulations] angezeigt:
Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
' displays the simulations view
panelsimulations.Visible = True
' the other containers are hidden
panelerreurs.Visible = False
panelform.Visible = False
' contents of simulations view
' each simulation is an array of 4 string elements
Dim simulation() As String
Dim i, j As Integer
simulationsHTML.Text = ""
For i = 0 To simulations.Count - 1
simulation = CType(simulations(i), String())
simulationsHTML.Text += "<tr>"
For j = 0 To simulation.Length - 1
simulationsHTML.Text += "<td>" + simulation(j) + "</td>"
Next
simulationsHTML.Text += "</tr>" + ControlChars.CrLf
Next
' link
lnkForm2.Text = lien
End Sub
Die Prozedur hat zwei Parameter:
- eine Liste von Simulationen in [simulations]
- einen Linktext in [link]
Der für die Liste der Simulationen zu generierende HTML-Code ist im Literal [simulationsHTML] gespeichert. Der Linktext ist in der Eigenschaft [Text] des Objekts [LinkButton] in der Ansicht gespeichert.
Wenn der Benutzer in der Ansicht [form] auf die Schaltfläche [Clear] klickt, wird die Prozedur [btnClear_click] ausgeführt (immer nach [Page_Load]):
Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
' displays the empty form
razForm()
afficheFormulaire()
End Sub
Private Sub razForm()
' empty the form
rdOui.Checked = False
rdNon.Checked = True
txtEnfants.Text = ""
txtSalaire.Text = ""
End Sub
Der obige Code ist so einfach, dass er keiner Erläuterung bedarf. Wir müssen noch die Klicks auf die Links [Fehler] und [Simulationen] in der Ansicht behandeln:
Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
' displays the form
afficheFormulaire()
End Sub
Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
' displays the form
afficheFormulaire()
End Sub
Beide Prozeduren zeigen lediglich die [Formular]-Ansicht an. Wir wissen, dass die Felder des Formulars einen Wert erhalten, der entweder der für sie gesendete Wert oder ihr vorheriger Wert ist. Da in diesem Fall die POST-Anfrage des Clients keine Werte für die Formularfelder sendet, werden diese auf ihre vorherigen Werte zurückgesetzt. Das Formular wird daher mit den vom Benutzer eingegebenen Werten angezeigt. Wir erinnern uns, dass wir in der Version ohne Serverkomponenten diese Wiederherstellung selbst durchgeführt haben.
7.14.2.5. Tests
Alle von der Anwendung benötigten Dateien befinden sich in einem Ordner namens <application-path>: ![]() | Der Ordner [bin] enthält die DLL mit den von der Anwendung benötigten Klassen [impot], [impotsData] und [impotsOLEDB]: |
Der Leser kann, falls gewünscht, noch einmal Kapitel 5 aufschlagen, in dem erklärt wird, wie die oben erwähnte Datei [impot.dll] erstellt wird. Sobald dies geschehen ist, wird der Cassini-Server mit den Parametern (<Anwendungspfad>,/impots5) gestartet. Wir rufen die URL [http://impots5/main.aspx] über einen Browser auf:

Wenn wir die ACCESS-Datei [impots.mdb] in [impots1.mdb] umbenennen, wird die folgende Seite angezeigt:

7.14.3. Beispiel 3
Wir haben in diesen beiden Beispielen gezeigt, dass es möglich ist, Webanwendungen nach der MVC-Architektur unter Verwendung serverseitiger Komponenten zu erstellen. Das letzte Beispiel zeigt, dass die Lösung mit serverseitigen Komponenten einfacher ist als die Lösung mit Standard-HTML-Tags. Unsere beiden Beispiele bestanden aus nur einer Seite mit mehreren Ansichten innerhalb derselben Seite. Sie können eine MVC-Architektur mit mehreren serverseitigen ASP-Formularen verwenden, solange es kein Problem darstellt, dass diese Formulare ihre eigenen Werte an sich selbst senden. Dies ist sehr häufig bei Anwendungen der Fall, die über Menüs verfügen. Betrachten wir das folgende Beispiel:

Wir haben Links zu den Anwendungen, die wir bisher geschrieben haben, auf einer einzigen Seite zusammengestellt. Diese Art von Anwendung eignet sich gut für eine MVC-Architektur. Der einzige Unterschied besteht darin, dass es nicht mehr nur einen Controller gibt, sondern mehrere.

Der Controller [main.aspx] fungiert als Hauptcontroller. Er wird von den Links auf der Startseite der Anwendung aufgerufen. Er führt Operationen aus, die allen möglichen Aktionen gemeinsam sind, und führt dann die spezifische Aktion aus, die mit dem verwendeten Link verknüpft ist. Anschließend übergibt er an einen der sekundären Controller, der für die Ausführung der Aktion zuständig ist. Ab diesem Zeitpunkt findet die Kommunikation zwischen dem Client und diesem spezifischen Controller statt. Wir laufen nicht mehr über den Hauptcontroller [main.aspx]. Wir befinden uns daher nicht mehr im MVC-Framework mit einem einzigen Controller, der alle Anfragen filtert. Jeder der oben genannten Controller kann, wie bereits beschrieben, mithilfe des Container-Mechanismus mehrere Ansichten auf einer einzigen Seite präsentieren.
Die Tatsache, dass wir keinen einzigen Controller mehr haben, der entscheidet, welche Ansichten an den Client gesendet werden, hat ihre Nachteile. Nehmen wir zum Beispiel die Fehlerbehandlung. Jede von der Anwendung bereitgestellte Aktion muss möglicherweise eine Fehleransicht anzeigen. Jeder Controller [applix.aspx] verfügt über eine eigene [errors]-Ansicht, da es sich hierbei lediglich um einen spezifischen Container innerhalb der Controller-Seite handelt. Es gibt keine Möglichkeit, eine einzige [errors]-Ansicht zu haben, die von allen einzelnen Anwendungen verwendet würde. Tatsächlich enthält eine solche Ansicht in der Regel einen Link zurück zum Formular mit den Fehlern, und dieses Formular muss in den Zustand zurückversetzt werden, in dem es validiert wurde, damit der Benutzer seine Fehler korrigieren kann. Diese Wiederherstellung erfolgt über den [VIEWSTATE]-Mechanismus, der nicht über verschiedene Controller hinweg funktioniert. Wenn Anwendungen von verschiedenen Personen entwickelt werden, besteht die Gefahr, dass Fehlerseiten je nach der vom Benutzer gewählten Aktion unterschiedlich aussehen, was die Konsistenz der gesamten Anwendung untergräbt. Wir werden etwas später sehen, dass ASP.NET eine Lösung für dieses spezifische Problem der gemeinsamen Nutzung von Ansichten bietet. Diese kann als neue Serverkomponente implementiert werden, die wir selbst erstellen. Die einfache Verwendung dieser Komponente in den verschiedenen Anwendungen gewährleistet die Konsistenz der gesamten Anwendung. Schwieriger zu handhaben ist das Problem der Aktionsreihenfolge. Wenn alle Anfragen über einen einzigen Controller laufen, kann dieser überprüfen, ob die angeforderte Aktion mit der vorherigen kompatibel ist. Dieser Validierungscode befindet sich an einer einzigen Stelle. Hier muss er auf die verschiedenen Controller verteilt werden, was die Wartung der gesamten Anwendung erschwert.
Kehren wir zu unserer obigen Anwendung zurück. Ihre Einstiegsseite ist eine Standard-HTML-Seite [home.htm]:
<html>
<head>
<TITLE>Composants ASP Serveur</TITLE>
<meta name="pragma" content="no-cache">
</head>
<frameset rows="130,*" frameborder="0">
<frame name="banner" src="bandeau.htm" scrolling="no">
<frameset cols="200,*">
<frame name="contents" src="options.htm">
<frame name="main" src="main.htm">
</frameset>
<noframes>
<p id="p1">
Ce jeu de frames HTML affiche plusieurs pages Web. Pour afficher ce jeu de
frames, utilisez un navigateur Web qui prend en charge HTML 4.0 et version
ultérieure.
</p>
</noframes>
</frameset>
</html>
Diese Homepage besteht aus drei Frames namens „banner“, „contents“ und „main“:
![]() |
Die Seite [bandeau.htm], die im [banner]-Frame platziert ist, sieht wie folgt aus:

Ihr HTML-Code lautet wie folgt:
<html>
<head>
<META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE" />
<title>bandeau</title>
</head>
<body>
<P>
<TABLE>
<TR>
<TD><IMG alt="logo université d'angers" src="univ01.gif"></TD>
<TD>Composants serveurs ASP</TD>
</TR>
</TABLE>
</P>
<HR>
</body>
</html>
Die Seite [options.htm] befindet sich im Banner [contents]. Sie enthält eine Reihe von Links:
![]() | |
Die verschiedenen Links verweisen alle auf den Hauptcontroller [main.aspx] mit einem [action]-Parameter, der die auszuführende Aktion angibt. Wir legen fest, dass das Linkziel im [main]-Frame angezeigt werden soll (target="main").
Die erste Seite, die im [main]-Frame angezeigt wird, ist [main.htm]:
|
Der Hauptcontroller [main.aspx, main.aspx.vb] sieht wie folgt aus:
[main.aspx]
[main.aspx.vb]
Public Class main
Inherits System.Web.UI.Page
Private Sub Page\_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' die auszuführende Aktion abrufen
Dim action As String
Wenn Request.QueryString("action") nichts ist, dann
action = "label"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' Aktion ausführen
Select Case action
Case "label"
Server.Transfer("form2.aspx")
Case „button“
Server.Transfer("form3.aspx")
Case „textbox1“
Server.Transfer("form4.aspx")
Feld „textbox2“
Server.Transfer("form5.aspx")
Case "Dropdown-Liste"
Server.Transfer("form6.aspx")
Fall „listbox“
Server.Transfer("form7.aspx")
Case "Checkbox"
Server.Transfer("form8.aspx")
Case "Checkboxliste"
Server.Transfer("form8b.aspx")
Case "Panel"
Server.Transfer("form9.aspx")
Case Else
Server.Transfer("form2.aspx")
End Select
End Sub
End Class
Unser Controller ist einfach. Je nach Wert des Parameters [action] leitet er die Bearbeitung der Anfrage an die entsprechende Seite weiter. Er bietet keinen Mehrwert gegenüber einer HTML-Seite mit Links. Das Hinzufügen einer Authentifizierungsseite würde jedoch seine Nützlichkeit verdeutlichen. Wenn sich der Benutzer für den Zugriff auf die Anwendungen authentifizieren müsste (Benutzername, Passwort), wäre der Controller [main.aspx] ein guter Ort, um zu überprüfen, ob diese Authentifizierung abgeschlossen wurde.




