Skip to content

7. Servercomponenten ASP - 1

7.1. Introduction

In dit hoofdstuk beschrijven we de technologie die in ASP.NET wordt aanbevolen voor het bouwen van de gebruikersinterface. We weten dat er twee duidelijk te onderscheiden fasen zijn bij de verwerking van een .aspx-pagina door de webserver:

  1. eerst wordt de controller van de pagina uitgevoerd. Deze bestaat uit code die zich ofwel in de .aspx-pagina zelf bevindt (oplossing WebMatrix) ofwel in een apart bestand (Visual-oplossing Studio.NET).
  2. Vervolgens wordt de presentatiecode van de .aspx-pagina uitgevoerd om te worden omgezet in HTML-code die naar de client wordt verzonden.

Image

ASP.NET biedt drie tagbibliotheken om de presentatiecode van de pagina te schrijven:

  1. de klassieke HTML-tags. Deze hebben we tot nu toe gebruikt.
  2. de server-tags HTML
  3. de webforms-tags

Ongeacht welke tagbibliotheek wordt gebruikt, blijft de rol van de paginacontroller ongewijzigd. Deze moet de waarde berekenen van de dynamische parameters die in de presentatiecode voorkomen. Tot nu toe waren deze dynamische parameters eenvoudig: het waren objecten van het type [String]. Dus als er in de presentatiecode een tag <%=naam%> staat:

  • declareert de paginacontroller een variabele [nom] van het type [String] en berekent de waarde ervan
  • wanneer de paginacontroller zijn werk heeft voltooid en de presentatiecode wordt uitgevoerd om het antwoord HTML te genereren, wordt de tag <%=naam%> vervangen door de waarde die door de controlecode is berekend

We weten dat de scheiding tussen controller en presentatie willekeurig is en dat we controllercode en presentatiecode op dezelfde pagina kunnen mengen. We hebben uitgelegd waarom deze methode wordt afgeraden en we zullen de scheiding tussen controller en presentatie blijven respecteren.

Met de tags [HTML serveur] en [WebForms] kunnen in de presentatiecode objecten worden opgenomen die complexer zijn dan het eenvoudige object [String]. Dit kan soms echt nuttig zijn. Laten we het voorbeeld nemen van een formulier met een lijst. Deze lijst moet aan de klant worden gepresenteerd met HTML-code die er als volgt uitziet:

<select name="uneListe" size="3">
    <option value="opt1">option1</option>
    <option value="opt2">option2</option>
    <option value="opt3" selected>option3</option>
</select>

De inhoud van de lijst en de te selecteren optie zijn dynamische elementen en moeten daarom door de paginacontroller worden gegenereerd. We zijn dit probleem al eens tegengekomen en hebben het opgelost door in de weergavecode de tag

<%=uneListeHTML%>

Deze tag wordt vervangen door de waarde [String] van de variabele [uneListeHTML]. Deze door de controller berekende waarde moet de code HTML uit de lijst zijn, c.a.d. "<select name=..>...</select>". Dit is niet bijzonder moeilijk om te doen en lijkt een elegante oplossing die voorkomt dat er generatiecode rechtstreeks in het presentatiegedeelte van de pagina wordt geplaatst. Hier zou een lus met controles moeten worden ingevoegd, waardoor deze sterk „vervuild“ zou raken. Deze methode heeft echter een nadeel. De scheiding tussen controller en presentatie van een pagina dient ook om twee bevoegdheidsgebieden af te bakenen:

  • dat van de ontwikkelaar .NET, die zich bezighoudt met de paginacontroller
  • dat van de grafisch ontwerper, die zich bezighoudt met het presentatiegedeelte ervan

Hier zien we dat het genereren van de code HTML voor de lijst is overgebracht naar de controller. De grafisch ontwerper wil mogelijk ingrijpen in deze code HTML om het „visuele” uiterlijk van de lijst aan te passen. Hij zal dan genoodzaakt zijn om in het gedeelte [contrôleur] te werken en dus buiten zijn bevoegdheidsgebied te treden, met alle risico’s van onbedoelde fouten in de code die dit met zich meebrengt.

Server-tagbibliotheken lossen dit probleem op. Ze bieden een object aan dat een lijst HTML vertegenwoordigt. Zo biedt de bibliotheek [WebForms] de volgende tag:

<asp:ListBox id="uneListe" runat="server"></asp:ListBox>

Deze tag vertegenwoordigt een object van het type [ListBox] dat door de paginacontroller kan worden gemanipuleerd. Dit object heeft eigenschappen om de verschillende opties van de lijst HTML weer te geven en de geselecteerde optie aan te duiden. De paginacontroller zal deze eigenschappen dus de juiste waarden toekennen. Wanneer het weergavegedeelte wordt uitgevoerd, zal de tag

<asp:ListBox id="uneListe" runat="server"></asp:ListBox>

vervangen door de code HTML, die het object [uneListe], c.a.d vertegenwoordigt. de code "<select ..>...</select>". Voorlopig is er geen fundamenteel verschil met de vorige methode, behalve dan dat het hier om een objectgeoriënteerde manier van coderen gaat, wat interessant is. Laten we teruggaan naar onze grafisch ontwerper die de "look" van de lijst moet aanpassen. De server-tags hebben stijlattributen (BackColor, Bordercolor, BorderWidth, ...) waarmee het uiterlijk van het bijbehorende object HTML kan worden bepaald. Zo kunnen we schrijven:


                <asp:ListBox id="ListBox1" runat="server" BackColor="#ffff99"></asp:ListBox></P>

Het voordeel is dat de grafisch ontwerper in de presentatiecode blijft om deze wijzigingen aan te brengen. Dit is een duidelijk voordeel ten opzichte van de vorige methode. De bibliotheken met server-tags maken het dus gemakkelijker om het presentatiegedeelte van de pagina’s op te bouwen, wat we in de vorige hoofdstukken de gebruikersinterface hebben genoemd. Dit hoofdstuk heeft tot doel deze bibliotheken te presenteren. We zullen zien dat deze bibliotheken soms complexe objecten bieden, zoals kalenders of tabellen die aan gegevensbronnen zijn gekoppeld. Ze zijn uitbreidbaar, c.a.d, zodat de gebruiker zijn eigen tagbibliotheek kan aanmaken. Zo kan hij bijvoorbeeld een tag maken die een banner op een pagina genereert. Alle pagina’s die deze tag gebruiken, zullen dan dezelfde banner hebben.

De generatie HTML die voor een tag wordt uitgevoerd, past zich aan het type browser van de klant aan. Wanneer de browser een verzoek naar de webserver stuurt, verzendt hij onder zijn headers HTTP een header [User-Agent: xx], waarbij [xx] de client identificeert. Hier volgt een voorbeeld:

User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

Met deze informatie kan de webserver de mogelijkheden van de client vaststellen, met name het type code HTML dat hij kan verwerken. Er zijn namelijk in de loop der tijd verschillende versies van de taal HTML geweest. Recente browsers ondersteunen de nieuwste versies van de taal, wat oudere browsers niet kunnen. Afhankelijk van de header HTTP [User-Agent:] die de client naar de server heeft gestuurd, stuurt de server een versie HTML terug die de client kan begrijpen. Dit is een interessant en nuttig idee, omdat de ontwikkelaar zich dan geen zorgen hoeft te maken over het type browser dat de klant gebruikt.

Ten slotte maken geavanceerde IDE-tools, zoals Visual Studio.NET, WebMatrix, … een “Windows-achtige” vormgeving van de webinterface mogelijk. Deze tools zijn weliswaar niet onmisbaar, maar bieden de ontwikkelaar toch een doorslaggevende hulp. De ontwikkelaar ontwerpt de webinterface met behulp van grafische componenten die hij op de interface plaatst. Hij heeft directe toegang tot de eigenschappen van elke component van de interface, die hij naar eigen inzicht kan instellen. Deze eigenschappen worden in de HTML-code voor de weergave van de interface omgezet in attributen van de <asp:>-tag van de component. Het voordeel voor de ontwikkelaar is dat hij zich noch de lijst, noch de syntaxis van de attributen van elke tag hoeft te onthouden. Dit is een aanzienlijk voordeel wanneer men de door ASP.NET aangeboden bibliotheken met server-tags niet volledig kent. Zodra men deze syntaxis onder de knie heeft, zullen sommige ontwikkelaars er de voorkeur aan geven de tags rechtstreeks in de weergavecode van de pagina te coderen, zonder de grafische ontwerpfase te doorlopen. Een IDE is dan niet meer nodig. Een eenvoudige teksteditor volstaat. Afhankelijk van de werkwijze ligt de nadruk dan op de componenten (gebruik van een IDE) of op de tags (gebruik van een teksteditor). Deze twee termen zijn gelijkwaardig. De component is het object dat door de besturingscode van de pagina wordt gemanipuleerd. De IDE geeft ons toegang tot de eigenschappen ervan in de ontwerpfase. De waarden die hieraan worden toegekend, worden onmiddellijk vertaald naar de attributen van de componenttag in de presentatiecode. Tijdens de uitvoeringsfase zal de besturingscode van de pagina de component bewerken en waarden toekennen aan bepaalde eigenschappen ervan. De presentatiecode genereert vervolgens de HTML-code van de component, waarbij enerzijds gebruik wordt gemaakt van de attributen die tijdens het ontwerp zijn ingesteld voor de bijbehorende server-tag, en anderzijds van de waarden van de eigenschappen van de component die door de besturingscode zijn berekend.

7.2. De uitvoeringscontext van de voorbeelden

We zullen het ontwerp van webinterfaces op basis van servercomponenten illustreren met programma’s waarvan de uitvoeringscontext meestal als volgt zal zijn:

  1. de webapplicatie bestaat uit één enkele pagina P met een formulier F,
  2. de client zal zijn eerste verzoek rechtstreeks aan deze pagina P richten. Dit houdt in dat de URL van pagina P via een browser wordt opgevraagd. Er wordt dus een verzoek GET gedaan naar deze URL P. De server zal pagina P en daarmee het daarin opgenomen formulier F leveren,
  3. de gebruiker vult het formulier in en verstuurt het, c.a.d. Hij voert een actie uit die de browser dwingt het formulier F naar de server te versturen. De bewerking POST van de browser is nog steeds gericht op pagina P. De server zal opnieuw pagina P met formulier F weergeven, waarbij de inhoud van het formulier mogelijk is gewijzigd door de actie van de gebruiker.
  4. Vervolgens worden stap 2 en 3 herhaald.

Dit is een heel specifiek uitvoeringsproces, buiten hetwelk bepaalde hierna uiteengezette concepten niet meer werken. We bevinden ons niet langer in een context van de MVC-architectuur waarin een applicatie met meerdere pagina’s wordt aangestuurd door een specifieke pagina die we de „applicatiecontroller” hebben genoemd. In dit type architectuur zijn de POST-verzoeken van formulieren gericht op de controller en niet op de formulieren zelf. We zullen echter zien dat het bouwen van een formulier met servercomponenten inhoudt dat dit formulier naar zichzelf wordt verzonden.

7.3. De Label-component

7.3.1. Gebruik

Met de tag <asp:label> kun je dynamische tekst invoegen in de weergavecode van een pagina. Deze tag doet dus niet meer dan de tag <%=variable%> die we tot nu toe hebben gebruikt. Door deze eerste tag te bestuderen, kunnen we het mechanisme van servertags ontdekken. We maken een pagina met een controlegedeelte [form1.aspx.vb] en een presentatiegedeelte [form1.aspx]. Het gaat erom de tijd weer te geven:

Image

Dit probleem is al behandeld in hoofdstuk 2 en de lezer wordt verzocht daar naar te verwijzen als hij wil weten hoe het is opgelost. De presentatiecode [form1.aspx] is als volgt:


<%@ page src="form1.aspx.vb" inherits="form1" AutoEventWireup="false" %>
<HTML>
    <HEAD>
        <title>Webforms</title>
    </HEAD>
    <body>
        <asp:Label Runat="server" ID="lblHeure" />
    </body>
</HTML>

We introduceren de tag <asp:label>. In de tagbibliotheken is het attribuut [runat="server"] verplicht. Het attribuut ID identificeert de component. De controller moet ernaar verwijzen met deze identificatiecode. De code van de controller [form1.aspx.vb] is als volgt:


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
        ' de huidige query wordt opgeslagen in request.txt in de map van de pagina
        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
        ' de tijd wordt in lblHeure geplaatst
        lblHeure.Text = "Il est " + Date.Now.ToString("T")
    End Sub

End Class

De controller moet een waarde toekennen aan het object [lblHeure] van het type [System.Web.UI.WebControls.Label]. Alle objecten die door de <asp:>-tags worden weergegeven, behoren tot de naamruimte [System.Web.UI.WebControls]. Daarom kan deze naamruimte systematisch worden geïmporteerd:


Imports System.Web.UI.WebControls

Het object [Label] heeft verschillende eigenschappen, waaronder de eigenschap [Text], die de tekst vertegenwoordigt die door de bijbehorende <asp:label>-tag wordt weergegeven. Hier vullen we deze eigenschap met de huidige tijd. Dit doen we in de procedure [Form_Load] van de controller, die altijd wordt uitgevoerd. In de procedure [Form_Init], die eveneens altijd wordt uitgevoerd maar vóór de procedure [Form_Load], slaan we het verzoek van de client op in een bestand [request.txt] in de applicatiemap. We zullen dit bestand later bekijken om bepaalde aspecten van de werking van pagina’s die gebruikmaken van server-tags te begrijpen.

Het object [Label] beschikt over talrijke eigenschappen, methoden en gebeurtenissen. De lezer wordt verzocht de documentatie over de klasse [Label] te raadplegen om deze te ontdekken. Dit zal in het verdere verloop van de tekst steeds het geval zijn. Voor elke tag presenteren we slechts de enkele eigenschappen die we nodig hebben.

7.3.2. De tests

We plaatsen de bestanden (form1.aspx, form1.aspx.vb) in een map <application-path> en starten Cassini met de parameters (<application-path>,/form1). Vervolgens roepen we de URL [http://localhost/form1/form1.aspx] op. We krijgen het volgende resultaat:

Image

De code HTML die door de browser wordt ontvangen, is als volgt:

<HTML>
    <HEAD>
        <title>Webforms</title>
    </HEAD>
    <body>
        <span id="lblHeure">Il est 19:39:37</span>
    </body>
</HTML>

We zien dat de server-tag


        <asp:Label Runat="server" ID="lblHeure" />

is omgezet in de volgende code: HTML:

        <span id="lblHeure">Il est 19:39:37</span>

Het is de eigenschap [Text] van het object [lblHeure] die tussen de tags <span> en </span> is geplaatst. Het verzoek van de client, dat is opgeslagen in [request.txt], luidt als volgt:

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

Heel normaal allemaal.

7.3.3. De applicatie bouwen met WebMatrix

We hebben de presentatiecode van de pagina [form1.aspx] met de hand opgebouwd. Deze methode is bruikbaar als men de tags kent. Een eenvoudige teksteditor volstaat dan om de gebruikersinterface op te bouwen. Om te beginnen is een grafisch ontwerptool in combinatie met automatische codegeneratie vaak nodig, omdat men de syntaxis van de benodigde tags nog niet kent. We bouwen nu dezelfde applicatie met de tool WebMatrix. Zodra WebMatrix is gestart, kiezen we de optie [File/New File]:

Image

We maken een pagina ASP.NET met de naam [form2.aspx]. Nadat we de vorige wizard hebben bevestigd, verschijnt het ontwerpvenster van de pagina [form2.aspx]:

Image

Image

Ter herinnering: WebMatrix plaatst de besturingscode en de presentatiecode van de pagina in één enkel bestand, in dit geval [form2.aspx]. Het tabblad [All] toont de inhoud van dit tekstbestand. We kunnen nu al zien dat het niet leeg is:

Image

Het nadeel van dit soort tools is dat ze vaak overbodige code genereren. Dat is hier het geval: WebMatrix heeft een tag HTML <form> gegenereerd, terwijl we geen formulier gaan maken... Bovendien valt op dat het document geen <title>-tag bevat. We lossen deze twee problemen meteen op, wat resulteert in de volgende nieuwe versie:

Image

Wat we de controllercode noemen, wordt tussen de tags <script> en </script> ingevoegd, wat zorgt voor een ten minste visuele scheiding tussen de twee soorten code: controle en presentatie. We gaan terug naar het tabblad [Design] om onze interface te ontwerpen. Een lijst met componenten is beschikbaar in een gereedschapsvenster links van het ontwerpvenster:

Image

Het gereedschapsvenster biedt toegang tot twee soorten componenten:

  • de [WebControls]-componenten, die worden omgezet in <asp:>-tags
  • de componenten [HTML Elements] die worden omgezet in klassieke HTML-tags. Aan de attributen van een HTML-tag kan echter het attribuut [runat="server"] worden toegevoegd. In dat geval zijn de HTML-tag en de bijbehorende attributen voor de controller toegankelijk via een object waarvan de eigenschappen overeenkomen met die van de HTML-tag die het vertegenwoordigt. Deze tags werden eerder de HTML-servertags genoemd.

Laten we dubbelklikken op de component [Label] in de lijst met besturingselementen [WebControls]. Op het tabblad [Design] krijgen we het volgende resultaat:

Image

Op het tabblad [All] is de code nu als volgt:

<%@ Page Language="VB" %>
<html>
<head>
    <title>webforms</title>
</head>
<body>
    <asp:Label id="Label1" runat="server">Label</asp:Label>
</body>
</html>

Allereerst valt op dat de tag <script> verdwenen is. Er is een <asp:label>-tag gegenereerd. Deze heeft de naam [Label1] en de waarde [Label]. Laten we teruggaan naar het tabblad [Design] om deze twee waarden aan te passen. We klikken daar één keer op de component [Label] om het eigenschappenvenster van deze component rechtsonder te openen:

Image

De lezer wordt verzocht de eigenschappen van het object [Label] te raadplegen. Twee daarvan zijn hier van belang:

  • Text: dit is de tekst die het label moet weergeven – we vullen de tekenreeks leeg in (c.a.d. niets)
  • ID: dit is de ID – we vullen lblHeure in

Het tabblad [Design] ziet er dan als volgt uit:

Image

en de code van [All] wordt:

<%@ Page Language="VB" %>
<html>
<head>
    <title>webforms</title>
</head>
<body>
    <asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>

Het presentatiegedeelte van de pagina is klaar. Nu moeten we nog de besturingscode schrijven die de tijd in de eigenschap [Text] van [lblHeure] plaatst. We voegen in het tabblad [All] de volgende code toe:


<%@ Page Language="VB" %>

<script runat="server">
    Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' voeg de tijd toe aan 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>

Merk op dat in de controller-code het object [lblHeure] niet wordt gedeclareerd zoals eerder het geval was:


    Protected lblHeure As New System.Web.UI.WebControls.Label

Alle <asp:>-servercomponenten van het presentatiegedeelte worden namelijk impliciet gedeclareerd in het controlegedeelte. Als we ze ook nog eens expliciet declareren, leidt dit tot een compilatiefout, die aangeeft dat het object al is gedeclareerd. We zijn klaar voor de uitvoering. We kiezen de optie [View/Start] of de snelkoppeling [F5]. Cassini wordt automatisch gestart met de volgende parameters:

Image

We accepteren deze waarden. De standaardbrowser van het systeem wordt automatisch gestart om de URL [http://localhost/form2.aspx] op te vragen. We krijgen het volgende resultaat:

Image

Vervolgens zullen we voornamelijk de tool WebMatrix gebruiken om het schrijven en testen van de korte programma's die we gaan maken te vergemakkelijken.

7.4. De Literal-component

7.4.1. Gebruik

Met de tag <asp:literal> kan dynamische tekst in de presentatiecode van een pagina worden ingevoegd, net als met de tag <asp:label>. Het belangrijkste attribuut is [Text], dat de tekst vertegenwoordigt die onveranderd in de HTML-stroom van de pagina wordt ingevoegd. Deze tag volstaat als men niet van plan is de tekst die men in de HTML-stroom wil invoegen, op te maken. Want hoewel de klasse [Label] het mogelijk maakt om de tekst op te maken met behulp van attributen zoals [BorderColor, BorderWidth, Font, ...], beschikt de klasse [Literal] niet over dergelijke attributen. De lezer kan het vorige voorbeeld volledig overnemen door de component [Label] te vervangen door een component [Literal].

7.5. De Button-component

7.5.1. Gebruik

Met de tag <asp:Button> kan een knop van het type [Submit] in een formulier worden ingevoegd, die een gebeurtenisbeheer met zich meebrengt dat vergelijkbaar is met dat in Windows-toepassingen. Dit punt willen we hier nader toelichten. We maken de volgende pagina [form3.aspx]:

Image

Deze pagina, gebouwd met WebMatrix, bestaat uit drie componenten:

nr.
naam
type
eigenschappen
rol
1
Button1
Knop
text=Knop1
verzendknop
2
Button2
Knop
text=Knop2
Verzendknop
3
lblInfo
Label
text=
informatiebericht

De door WebMatrix voor dit gedeelte gegenereerde code is als volgt:

<%@ 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>

We zien de componenten [Button] en [Label], die bij het grafisch ontwerp van de pagina zijn gebruikt, terug in <asp:>-tags. Let op de <form runat="server">-tag die automatisch is gegenereerd. Dit is een server-tag HTML, c.a.d. een klassieke tag HTML die niettemin wordt vertegenwoordigd door een object dat door de controller kan worden gemanipuleerd. De code van de tag <form> wordt gegenereerd op basis van de waarde die de controller aan dit object toekent.

Laten we in het controller-gedeelte van de code de procedure [Page_Init] toevoegen, die de gebeurtenis [Init] van de pagina afhandelt. Daarin plaatsen we de code die het verzoek van de client opslaat in het bestand [request.txt]. Dit hebben we nodig om de werking van de knoppen te begrijpen.

<script runat="server">
    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) 
        ' slaat het huidige verzoek op in request.txt in de paginamap
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub
</script>

Hierboven valt op dat we de clausule [Handles MyBase.Init] niet achter de declaratie van de procedure [Page_Init] hebben geplaatst. De gebeurtenis [Init] van het object [Page] heeft namelijk een standaardhandler met de naam [Page_Init]. Als we deze handlernaam gebruiken, is de clausule [Handles Page.Init] overbodig. Het toevoegen ervan leidt echter niet tot een foutmelding.

7.5.2. Tests

We starten de applicatie onder WebMatrix via [F5]. We krijgen de volgende pagina te zien:

Image

De code HTML die door de browser wordt ontvangen, is als volgt:

<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>

Let op de volgende punten:

  • de tag <form runat="server"> is veranderd in de tag HTML
    <form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">

Er zijn twee attributen vastgelegd: [method="post"] en [action="form3.aspx"]. Kunnen we aan deze attributen verschillende waarden toekennen? We zullen dit punt verderop proberen te verduidelijken. We onthouden hier dat het formulier zal worden verzonden naar de URL [form3.aspx]. Er zijn ook twee andere attributen vastgelegd: [name, id]. Meestal worden deze genegeerd. Als de pagina echter JavaScript-code bevat die in de browser wordt uitgevoerd, is het attribuut [name] van de tag <form> nuttig.

  • De <asp:button>-tags zijn veranderd in HTML-tags voor [submit]-knoppen. Een klik op een van deze knoppen zal dus een "post" van het formulier [_ctl10] naar de URL [form3.aspx] veroorzaken.
  • De tag <asp:label> is veranderd in een tag HTML <span>
  • Er is een verborgen veld [__VIEWSTATE] gegenereerd met een vreemde waarde:
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />

Dit veld geeft in gecodeerde vorm de status weer van het formulier dat naar de client wordt verzonden. Deze status vertegenwoordigt de waarde van alle componenten van het formulier. Aangezien [__VIEWSTATE] deel uitmaakt van het formulier, wordt de waarde ervan samen met de rest naar de server verzonden. Hierdoor weet de server welk onderdeel van het formulier van waarde is veranderd en kan hij eventueel beslissingen nemen. Deze beslissingen nemen de vorm aan van gebeurtenissen van het type "het TextBox-veld is van waarde veranderd".

7.5.3. De verzoeken van de client

Zodra de pagina [form3.aspx] in de browser is geladen, vragen we deze opnieuw op en bekijken we vervolgens het verzoek dat door de browser is verzonden om deze pagina op te halen. We herinneren ons dat onze applicatie dit opslaat in het bestand [request.txt] in de applicatiemap:

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

Dit is een klassieke GET. Laten we nu eens op de knop [Bouton1] op de pagina in de browser klikken. Er lijkt niets te gebeuren. Toch weten we dat het formulier is verzonden. Dat blijkt uit de code HTML van de pagina. Dit wordt bevestigd door de nieuwe inhoud van [request.txt]:

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

De eerste header HTTP geeft inderdaad aan dat de klant een POST heeft verzonden naar de URL [/form3.aspx]. De laatste regel toont de verzonden waarden:

  • de waarde van het verborgen veld __VIEWSTATE
  • de waarde van de knop waarop is geklikt

Als we op [Bouton2] klikken, zijn de door de browser verzonden waarden als volgt:

__VIEWSTATE=dDwxNTY0NjIwMjUwOzs%2B2mcnJczeuvF2PEfvmtv7uiUhWUw%3D&Button2=Bouton2

De waarde van het verborgen veld is nog steeds hetzelfde, maar het is de waarde van [Button2] die is verzonden. De server kan dus vaststellen welke knop is gebruikt. Hij zal dit gebruiken om een gebeurtenis te activeren die door de pagina kan worden verwerkt, zodra deze is geladen.

7.5.4. De Click-gebeurtenis van een Button-object afhandelen

Laten we nog eens kijken hoe een .aspx-pagina werkt. Deze is een object dat is afgeleid van de klasse [Page]. Laten we de afgeleide klasse [unePage] noemen. Wanneer de server een verzoek voor een dergelijke pagina ontvangt, wordt een object van het type [unePage] geïnstantieerd via de bewerking new unePage(...). Vervolgens genereert de server twee gebeurtenissen, genaamd [Init] en [Load], in die volgorde. Het object [unePage] kan deze afhandelen door de gebeurtenishandlers [Page_Init] en [Page_Load] aan te leveren. Vervolgens worden er nog meer gebeurtenissen gegenereerd. We zullen hier later op terugkomen. Als het verzoek van de client een POST is, genereert de server de gebeurtenis [Click] van de knop die deze POST heeft veroorzaakt. Als de klasse [unePage] een handler voor deze gebeurtenis heeft gedefinieerd, wordt deze aangeroepen. Laten we dit mechanisme eens bekijken aan de hand van WebMatrix. In het tabblad [Design] van [form3.aspx] dubbelklikken we op de knop [Bouton1]. We komen dan automatisch terecht in het tabblad [Code], in de hoofdtekst van een procedure met de naam [Button1_Click]. Om dit beter te begrijpen, gaan we naar het tabblad [All] en bekijken we de volledige code. De volgende wijzigingen zijn aangebracht:

<%@ 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>

Er is een nieuw attribuut [onclick="Button1_Click"] toegevoegd aan de tag <asp:Button> van [Button1]. Dit attribuut geeft de procedure aan die verantwoordelijk is voor de afhandeling van de gebeurtenis [Click] op het object [Button1], in dit geval de procedure [Button1_Click]. Nu hoeven we deze alleen nog maar te schrijven:

    Sub Button1_Click(sender As Object, e As EventArgs)
        ' klik op knop 1
        lblInfo.Text="Vous avez cliqué sur [Bouton1]"
    End Sub

De procedure plaatst een informatiemelding in het label [lblInfo]. We gaan op dezelfde manier te werk voor de knop [Bouton2] om de volgende nieuwe pagina [form3.aspx] te verkrijgen:

<%@ Page Language="VB" %>
<script runat="server">

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
        ' slaat de huidige aanvraag op in request.txt in de map van de pagina
        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)
        ' klik op knop 1
        lblInfo.Text="Vous avez cliqué sur [Bouton1]"
    End Sub

    Sub Button2_Click(sender As Object, e As EventArgs)
        ' klik op knop 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>

We starten de uitvoering via [F5] om de volgende pagina te verkrijgen:

Image

Als we de ontvangen code HTML bekijken, zien we dat deze niet is veranderd ten opzichte van die van de vorige versie van de pagina:

<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>

Als we op [Bouton1] klikken, krijgen we het volgende antwoord:

Image

De code HTML die we bij dit antwoord hebben ontvangen, is de volgende:

<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>

We zien dat de waarde van het verborgen veld [__VIEWSTATE] is gewijzigd. Dit weerspiegelt de wijziging in de waarde van de component [lblInfo].

7.5.5. Gebeurtenissen tijdens de levenscyclus van een applicatie ASP.NET

De documentatie ASP.NET geeft een overzicht van de gebeurtenissen die door de server worden gegenereerd tijdens de levenscyclus van een applicatie:

  • bij het allereerste verzoek dat de applicatie ontvangt, wordt de gebeurtenis [Start] op het object [HttpApplication] van de applicatie gegenereerd. Dit gebeurtenis kan worden afgehandeld door de procedure [Application_Start] in het bestand [global.asax] van de applicatie.

Vervolgens volgt er een reeks gebeurtenissen die zich voor elk ontvangen verzoek herhalen:

  • als het verzoek geen sessietoken heeft verzonden, wordt een nieuwe sessie gestart en wordt een gebeurtenis [Start] gegenereerd op het object [Session] dat aan het verzoek is gekoppeld. Dit gebeurtenis kan worden afgehandeld door de procedure [Session_Start] in het bestand [global.asax] van de applicatie.
  • De server genereert de gebeurtenis [BeginRequest] op het object [HttpApplication]. Deze kan worden afgehandeld door de procedure [Application_BeginRequest] in het bestand [global.asax] van de applicatie.
  • De server laadt de pagina die via het verzoek is opgevraagd. Hij maakt een instantie van het object [Page] en genereert vervolgens twee gebeurtenissen op dit object: [Init] en vervolgens [Load]. Deze twee gebeurtenissen kunnen worden afgehandeld door de procedures [Page_Init] en [Page_Load] van de pagina.
  • Op basis van de ontvangen geposte waarden genereert de server andere gebeurtenissen: [TextChanged] voor een component [TextBox] waarvan de waarde is gewijzigd, [CheckedChanged] voor een keuzerondje waarvan de waarde is gewijzigd, [SelectedIndexChanged] voor een lijst waarvan het geselecteerde element is gewijzigd, ... We zullen de belangrijkste gebeurtenissen voor elk van de servercomponenten die we gaan presenteren, nader toelichten. Elke gebeurtenis E op een object met de naam O kan worden afgehandeld door een procedure met de naam O_E.
  • De volgorde waarin de voorgaande gebeurtenissen worden afgehandeld, is niet gegarandeerd. Daarom mogen de handlers geen aannames doen over deze volgorde. We weten echter wel zeker dat de gebeurtenis [Click] van de knop die de gebeurtenis POST heeft veroorzaakt, als laatste wordt verwerkt.
  • Zodra de pagina klaar is, stuurt de server deze naar de client. Vooraf genereert hij de gebeurtenis [PreRender], die kan worden afgehandeld door de procedure [Page_PreRender] van de pagina.
  • Zodra het antwoord HTML naar de klant is verzonden, wordt de pagina uit het geheugen verwijderd. Bij deze gelegenheid worden twee gebeurtenissen gegenereerd: [Unload] en [Disposed]. De pagina kan deze gebeurtenissen gebruiken om bronnen vrij te maken.

De applicatie kan ook gebeurtenissen ontvangen buiten een verzoek van de klant om:

  • de gebeurtenis [End] op een object [Session] van de applicatie vindt plaats wanneer een sessie wordt beëindigd. Dit kan gebeuren op expliciet verzoek van de code van een pagina of omdat de aan de sessie toegekende levensduur is overschreden. De procedure [Session_End] in het bestand [global.asax] verwerkt deze gebeurtenis. Hierin worden doorgaans de in [Session_Start] verkregen bronnen vrijgegeven.
  • De gebeurtenis [End] op het object [HttpApplication] van de toepassing vindt plaats wanneer de toepassing wordt beëindigd. Dit gebeurt met name wanneer de webserver wordt gestopt. De procedure [Application_End] in het bestand [global.asax] beheert deze gebeurtenis. Hierin worden doorgaans bronnen vrijgegeven die zijn verkregen in [Application_Start].

De volgende punten zijn belangrijk:

  • het vorige gebeurtenismodel is gebaseerd op klassieke client-server-uitwisselingen via HTTP. Dit wordt duidelijk wanneer men de uitgewisselde HTTP-headers bekijkt.
  • de verwerking van de voorgaande gebeurtenissen vindt altijd plaats aan de serverzijde. Het klikken op een knop kan uiteraard worden verwerkt door een JavaScript-script aan de serverzijde. Maar dan is er geen sprake van een servergebeurtenis en hebben we hier te maken met een technologie die onafhankelijk is van ASP.NET.

Op welk moment worden de gebeurtenissen verwerkt, of ze nu aan de serverzijde (gebeurtenissen die verband houden met servercomponenten) of aan de browserzijde door JavaScript-scripts worden verwerkt?

Laten we het voorbeeld van een dropdownlijst nemen. Wanneer de gebruiker het geselecteerde item daarin wijzigt, kan de gebeurtenis (wijziging van het geselecteerde item) al dan niet worden verwerkt, en als deze wordt verwerkt, kan dat op verschillende momenten gebeuren.

  • Als men het onmiddellijk wil verwerken, zijn er twee oplossingen:
    • het kan door de browser worden verwerkt met behulp van een JavaScript-script. De server komt dan niet tussenbeide. Om dit mogelijk te maken, moet de pagina opnieuw kunnen worden opgebouwd met waarden die in de pagina aanwezig zijn.
    • Het kan door de server worden verwerkt. Hiervoor is er maar één oplossing: het formulier moet ter verwerking naar de server worden verzonden. We hebben dus te maken met een bewerking [submit]. We zullen zien dat we in dit geval een servercomponent gebruiken met de naam [DropDownList] en dat we het attribuut [AutoPostBack] instellen op [true]. Dit betekent dat wanneer het geselecteerde element in de vervolgkeuzelijst verandert, het formulier onmiddellijk naar de server moet worden verzonden. De server genereert in dit geval voor het object [DropDownList] een code HTML die gekoppeld is aan een JavaScript-functie die een [submit] moet genereren zodra de gebeurtenis „wijziging van het geselecteerde element” plaatsvindt. Deze [submit] verstuurt het formulier naar de server en daarin worden verborgen velden geplaatst om aan te geven dat de [post] het gevolg is van een wijziging in de selectie in de vervolgkeuzelijst. De server genereert vervolgens de gebeurtenis [SelectedIndexChanged], die door de pagina kan worden verwerkt.
    • Als men dit wil verwerken, maar niet onmiddellijk, stelt men het attribuut [AutoPostBack] van de servercomponent [DropDownList] in op [false]. In dit geval genereert de server voor het object [DropDownList] de standaardcode HTML van een <select>-lijst zonder bijbehorende JavaScript-functie. Er gebeurt dan niets wanneer de gebruiker de selectie in de vervolgkeuzelijst wijzigt. Wanneer de gebruiker het formulier echter verzendt via bijvoorbeeld een knop met de code [submit], kan de server herkennen dat er een wijziging in de selectie heeft plaatsgevonden. We hebben namelijk gezien dat het naar de server verzonden formulier een verborgen veld [__VIEWSTATE] bevatte, dat in gecodeerde vorm de status van alle elementen van het verzonden formulier weergeeft. Wanneer de server het nieuwe formulier ontvangt dat door de klant is verzonden, kan deze controleren of het geselecteerde item in de keuzelijst al dan niet is gewijzigd. Zo ja, dan genereert de server de gebeurtenis [SelectedIndexChanged], die vervolgens door de pagina kan worden verwerkt. Om dit mechanisme van het vorige te onderscheiden, zeggen sommige auteurs dat de gebeurtenis „wijziging van selectie” in de cache is opgeslagen toen deze zich in de browser voordeed. Deze wordt pas door de server verwerkt wanneer de browser het formulier naar de server verstuurt, vaak na een klik op een knop met de code [submit].
    • Als men de gebeurtenis ten slotte niet wil verwerken, stellen we het attribuut [AutoPostBack] van de servercomponent [DropDownList] in op [false] en schrijven we de handler voor de bijbehorende gebeurtenis [SelectedIndexChanged] niet.

Zodra de ontwikkelaar het mechanisme van de gebeurtenisverwerking begrijpt, zal hij een webapplicatie niet meer ontwerpen als een Windows-applicatie. Als een wijziging in de selectie van een combobox in een Windows-toepassing namelijk kan worden gebruikt om het uiterlijk van het formulier waarin deze zich bevindt onmiddellijk te wijzigen, zal men in een webtoepassing meer aarzelen om deze gebeurtenis onmiddellijk te verwerken als dit een "post" van het formulier naar de server inhoudt, en dus een netwerkuitwisseling tussen de client en de server. Daarom is de eigenschap [AutoPostBack] van de servercomponenten standaard ingesteld op [false]. Bovendien kan het mechanisme [AutoPostBack], dat gebruikmaakt van JavaScript-scripts die automatisch door de webserver worden gegenereerd in het naar de client verzonden formulier, alleen worden gebruikt als men er zeker van is dat de browser van de client de uitvoering van JavaScript-scripts heeft toegestaan. Formulieren worden daarom vaak als volgt opgebouwd:

  • de servercomponenten van het formulier hebben de eigenschap [AutoPostBack] tot en met [false]
  • het formulier heeft een of meer knoppen die verantwoordelijk zijn voor het uitvoeren van de [POST] van het formulier
  • in de controllercode van de pagina worden alleen de handlers geschreven voor de gebeurtenissen die men wil afhandelen, meestal de gebeurtenis [Click] op een van de knoppen.

7.6. De component TextBox

7.6.1. Gebruik

Met de tag <asp:TextBox> kun je een invoerveld invoegen in de weergavecode van een pagina. We maken een pagina [form4.aspx] om de volgende weergave te verkrijgen:

Image

4321Deze pagina, gemaakt met WebMatrix, bevat de volgende onderdelen:

nr.
naam
type
eigenschappen
rol
1
TextBox1
TextBox
AutoPostback=true
Text=
invoerveld
2
TextBox2
TextBox
AutoPostback=false
Text=
invoerveld
3
lblInfo1
Label
text=
informatiebericht over de inhoud van [TextBox1]
3
lblInfo2
Label
text=
informatiebericht over de inhoud van [TextBox2]

De door WebMatrix voor dit deel gegenereerde code is als volgt:

<%@ 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>

Dubbelklik in het tabblad [Design] op de component [TextBox1]. Het skelet van de gebeurtenisverwerker [TextChanged] van dit object wordt vervolgens gegenereerd (tabblad [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>

Het attribuut [OnTextChanged="TextBox1_TextChanged"] is toegevoegd aan de tag <asp:TextBox id="TextBox1"> toegevoegd om de gebeurtenishandler [TextChanged] op [TextBox1] aan te duiden. Dit is de procedure [TextBox1_Changed] die we nu gaan schrijven.

    Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
      ' tekst wijzigen
      lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
    End Sub

In de procedure schrijven we in het label [lblInfo1] een bericht waarin de gebeurtenis wordt gemeld en de inhoud van [TextBox1] wordt aangegeven. Hetzelfde doen we voor [TextBox2]. We vermelden ook het tijdstip om de verwerking van de gebeurtenissen beter te kunnen volgen. De uiteindelijke code van [form4.aspx] is als volgt:

<%@ Page Language="VB" %>
<script runat="server">

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
        ' slaat de huidige aanvraag op in request.txt in de map van de pagina
        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)
      ' tekst wijzigen
      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)
      ' tekstwijziging
      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>

We hebben de procedure [Page_Init] toegevoegd om de aanvraag van de klant op te slaan, zoals in het vorige voorbeeld.

7.6.2. Tests

We starten de applicatie onder WebMatrix via [F5]. We krijgen de volgende pagina te zien:

Image

De code HTML die door de browser wordt ontvangen, is als volgt:

<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>

Er zijn veel elementen in deze code die automatisch door de server zijn gegenereerd. Let op de volgende punten:

  • er zijn drie verborgen velden: [__VIEWSTATE], dat we al eerder zijn tegengekomen, [__EventTarget] en [__EventArgument]. Deze laatste twee velden worden gebruikt om de browsergebeurtenis "change" te verwerken in het invoerveld [TextBox1]
  • de servertags <asp:textbox> hebben geleid tot de tags HTML <input type="text" ...>, die overeenkomen met de invoervelden
  • de server-tag <asp:textbox id="TextBox1" AutoPostBack="true" ...> heeft geleid tot een <input type="text" ...>-tag met een [onchange="__doPostBack('TextBox1','')"]-attribuut. Dit attribuut geeft aan dat bij een wijziging van de inhoud van [TextBox1] de JavaScript-functie [_doPostBack(...)] moet worden uitgevoerd. Deze luidt als volgt:
<script language="javascript">
<!--
    function __doPostBack(eventTarget, eventArgument) {
        var theform = document._ctl0;
        theform.__EVENTTARGET.value = eventTarget;
        theform.__EVENTARGUMENT.value = eventArgument;
        theform.submit();
    }
// -->
</script>

Wat doet de bovenstaande functie? Ze wijst een waarde toe aan elk van de twee verborgen velden [__EventTarget] en [__EventArgument] en verstuurt vervolgens het formulier. Dit wordt dus naar de server verzonden. Dit is het effect van [AutoPostBack]. De browsergebeurtenis „change“ zorgt ervoor dat de code „__doPostBack('TextBox1','')“ wordt uitgevoerd. Hieruit volgt dat in het verzonden formulier het verborgen veld [__EventTarget] de waarde 'TextBox1' zal hebben en het verborgen veld [__EventArgument] de waarde ''. Hierdoor kan de server achterhalen welke component de POST heeft veroorzaakt.

  • De server-tag <asp:textbox id="TextBox2"...> heeft een standaard <input type="text" ...>-tag gegenereerd, omdat het attribuut [AutoPostBack] niet was ingesteld op [true].
  • De tag <form> geeft aan dat het formulier naar [form4.aspx] wordt verzonden:
    <form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">

Laten we onze eerste test doen. Typ een tekst in het eerste invoerveld:

Image

en plaats vervolgens de cursor in het tweede invoerveld. Meteen verschijnt er een nieuwe pagina:

Image

Wat is er gebeurd? Toen de cursor het eerste invoerveld verliet, heeft de browser gecontroleerd of de inhoud ervan was gewijzigd. Dat was het geval. Daarom heeft de browser aan de clientzijde de gebeurtenis [change] gegenereerd voor het veld HTML [TextBox1]. We zagen toen dat er een JavaScript-functie werd uitgevoerd die het formulier naar [form4.aspx] verstuurde. Deze pagina werd vervolgens door de server opnieuw geladen. Aan de hand van de door het formulier verzonden waarden kon de server op zijn beurt vaststellen dat de inhoud van de server-tag [TextBox1] was gewijzigd. De procedure [TextBox1_Changed] werd dus aan de serverzijde uitgevoerd. Deze plaatste een bericht in het label [lblInfo1]. Zodra deze procedure was voltooid, werd [form4.aspx] naar de browser verzonden. Daarom staat er nu tekst in [lblInfo1]. Dat gezegd hebbende, is het opmerkelijk dat er iets in het invoerveld [TextBox1] staat. Er is namelijk geen enkele procedure aan de serverzijde die een waarde aan dit veld toekent. Dit is een algemeen mechanisme van webformulieren ASP.NET: de server stuurt het formulier terug in de staat waarin hij het heeft ontvangen. Hiervoor kent hij aan de componenten opnieuw de waarde toe die door de client voor hen is verzonden. Voor sommige componenten verstuurt de client geen waarde. Dit is bijvoorbeeld het geval bij <asp:label>-componenten, die worden omgezet in <span>-tags. We herinneren ons dat het formulier een verborgen veld heeft dat de status van het formulier weergeeft op het moment dat het naar de client wordt verzonden. Deze status is de som van de statussen van alle formuliercomponenten, inclusief de <asp:label>-componenten, indien aanwezig. Aangezien het verborgen veld [__VIEWSTATE] door de browser van de klant wordt verzonden, kan de server de vorige status van alle formuliercomponenten herstellen. De server hoeft nu alleen nog maar de componenten aan te passen waarvan de waarde door POST is gewijzigd.

Laten we nu in [request.txt] eens kijken naar het verzoek dat door de browser is gedaan:

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=

We zien duidelijk de POST en de verzonden parameters. Laten we teruggaan naar onze browser en wat tekst invoeren in het tweede invoerveld:

Image

Laten we teruggaan naar invoerveld nr. 1 om een nieuwe tekst in te voeren: er gebeurt deze keer niets. Waarom? Omdat het invoerveld [TextBox2] de eigenschap [AutoPostBack] tot [true] niet heeft, en de daarvoor gegenereerde tag <input type="text"...> die ervoor wordt gegenereerd, de gebeurtenis [Change] niet ondersteunt, zoals blijkt uit de code HTML:

            <input name="TextBox2" type="text" id="TextBox2" />

Er vindt dus geen gebeurtenis plaats wanneer het invoerveld nr. 2 wordt verlaten. Laten we nu een nieuwe tekst invoeren in veld nr. 1:

Image

Laten we invoerveld nr. 1 verlaten. Onmiddellijk wordt de gebeurtenis [Change] voor dit veld gedetecteerd en wordt het formulier naar de server verzonden, die vervolgens de volgende pagina terugstuurt:

Image

Wat is er gebeurd? Allereerst heeft de browser het formulier verzonden. Dit blijkt uit het verzoek van de client dat is opgeslagen in [request.txt]:

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

De server begint met het terugzetten van de eerdere waarden van de componenten aan de hand van het verborgen veld [__VIEWSTATE] dat de client naar de server heeft verzonden. Met behulp van de verzonden velden [TextBox1] en [TextBox2] wijst de server aan de componenten [TextBox1] en [TextBox2] de waarden toe die naar de server zijn verzonden. Via dit mechanisme krijgt de client het formulier terug zoals het is verzonden. Vervolgens, nog steeds dankzij de verzonden velden [__VIEWSTATE], [TextBox1] en [TextBox2], dat de waarden van de invoervelden [TextBox1] en [TextBox2] zijn gewijzigd. De server genereert daarom de gebeurtenissen [TextChanged] voor deze twee objecten. De procedures [TextBox1_TextChanged] en [TextBox2_TextChanged] worden uitgevoerd en de labels [labelInfo1] en [labelInfo2] krijgen een nieuwe waarde. Vervolgens wordt de aldus gewijzigde pagina [form4.aspx] teruggestuurd naar de klant.

Nu wijzigen we het invoerveld nr. 1 opnieuw:

Image

Wanneer we de invoercursor uit veld 1 verplaatsen, vindt de gebeurtenis [Change] plaats in de browser. Vervolgens verloopt de reeds uitgelegde reeks gebeurtenissen (verzoek van de browser naar de server, ..., verzending van het antwoord van de server). We krijgen het volgende antwoord:

Image

Aan de hand van de tijd die bij elk bericht wordt weergegeven, zien we dat alleen de procedure [TextBox1_Changed] op de server is uitgevoerd. De procedure [TextBox2_TextChanged] is niet uitgevoerd omdat de waarde van [TextBox2] juist niet is gewijzigd. Laten we ten slotte een nieuwe tekst in veld nr. 2 invoeren:

Image

Vervolgens plaatsen we de cursor op veld nr. 1 en verplaatsen we deze weer naar veld nr. 2. De pagina verandert niet. Waarom? Omdat we de waarde van veld nr. 1 niet wijzigen, vindt de browsergebeurtenis [Change] niet plaats wanneer we dit veld verlaten. Daardoor wordt het formulier niet naar de server verzonden. Er verandert dus niets op de pagina. Het feit dat de inhoud van [lblInfo2] niet verandert, toont aan dat er geen POST is. Als er wel een zou zijn, zou de server detecteren dat de inhoud van [TextBox2] is gewijzigd en zou dit moeten weerspiegelen in [lblInfo2].

Uit dit voorbeeld blijkt dat het geen zin heeft om de eigenschap [AutoPostBack] van een [TextBox] naar [true] te verplaatsen. Dit leidt in de meeste gevallen tot onnodig heen-en-weer-verkeer tussen client en server.

7.6.3. De rol van het veld __VIEWSTATE

We hebben gezien dat de server systematisch een verborgen veld met de naam __VIEWSTATE in het door hem gegenereerde formulier opnam. We hebben gezegd dat dit veld de status van het formulier weergeeft en dat, als dit verborgen veld aan de server werd teruggestuurd, de server in staat was de vorige waarde van het formulier te reconstrueren. De status van een formulier is de som van de statussen van de componenten ervan. Elk onderdeel heeft een eigenschap [EnableViewState] met een booleaanse waarde die aangeeft of de status van het onderdeel al dan niet in het verborgen veld [__VIEWSATE] moet worden geplaatst. Standaard heeft deze eigenschap de waarde [true], waardoor de status van alle componenten van een formulier in [__VIEWSTATE] wordt geplaatst. Soms is dit niet wenselijk.

Laten we wat experimenteren om de rol van de eigenschap [EnableViewState] beter te begrijpen. Laten we deze eigenschap instellen op [false] voor beide invoervelden:

...
            <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>
...

Laten we nu de applicatie uitvoeren en een eerste tekst in veld nr. 1 invoeren, voordat we naar veld nr. 2 gaan. Er wordt dan een POST naar de server verzonden en we krijgen het volgende antwoord:

Image

Typ een tekst in veld nr. 2, wijzig de tekst in veld nr. 1 en ga vervolgens terug naar veld nr. 2 (in deze volgorde). Er wordt een nieuwe POST naar de server verzonden en we krijgen het volgende antwoord:

Image

Voorlopig is alles nog zoals voorheen. Laten we nu de inhoud van veld nr. 1 wijzigen en vervolgens naar veld nr. 2 gaan. Er wordt een nieuwe POST verzonden. Het antwoord van de server is als volgt:

Image

Deze keer is er een verandering. De server heeft een gebeurtenis [TextChanged] gedetecteerd in veld nr. 2, omdat de tijd van [lblInfo2] is gewijzigd. Er was echter geen wijziging. Dit komt door de eigenschap [EnableViewState=false] van [TextBox2]. Hierdoor heeft de server de vorige status van [TextBox2] niet in het veld [__VIEWSTATE] van het formulier geplaatst. Dit komt erop neer dat de lege tekenreeks als vorige status is toegewezen. Toen POST ontstond als gevolg van de wijziging in de inhoud van [TextBox1], vergeleek de server de huidige waarde van [TextBox2] – die [deux] was – met de vorige waarde (de lege tekenreeks). Hieruit concludeerde de server dat de waarde van [TextBox2] was gewijzigd en genereerde hij de gebeurtenis [TextChanged] voor [TextBox2]. We kunnen deze werking controleren door de lege tekenreeks in [TextBox2] in te voeren. Op basis van wat zojuist is uitgelegd, zou de server de gebeurtenis [TextChanged] niet moeten genereren voor [TextBox2]. Laten we het eens proberen:

Image

Dat is inderdaad wat er is gebeurd. Uit het tijdstip en de inhoud van [lblInfo2] blijkt dat de procedure [TextBox2_TextChanged] niet is uitgevoerd. Nu we dit begrijpen, gaan we de eigenschap [EnableViewState] van de vier formuliercomponenten bekijken:

TextBox1
we willen de status van deze component bijhouden, zodat de server weet of deze al dan niet is gewijzigd
TextBox2
idem
lblInfo1
we willen de status van deze component niet bijhouden. We willen dat de tekst bij elke nieuwe POST opnieuw wordt berekend. Als deze niet opnieuw wordt berekend, moet deze leeg zijn. Dit alles wordt goed bereikt met [EnableViewState=false]
lblInfo2 
idem

Onze presentatiepagina ziet er dan als volgt uit:

...
            <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>
...

Laten we dezelfde reeks tests uitvoeren als eerder. Waar we het scherm kregen

version 1

Image

krijgen we nu:

version 2

Image

In deze stap wijzigden we de inhoud van veld 1 zonder die van veld 2 te wijzigen, waardoor de procedure [TextBox2_TextChanged] niet aan de serverzijde werd uitgevoerd. Dit betekende dat het veld [lblInfo2] geen nieuwe waarde kreeg. Het wordt dus weergegeven met de vorige waarde. In versie 1 ([EnableViewState=true]) was deze vorige waarde de ingevoerde waarde. In versie 2 ([EnableViewState=false]) is deze vorige waarde de lege tekenreeks.

Soms is het niet nodig om de vorige status van de componenten te behouden. In plaats van voor elk van hen [EnableViewState=false] in te stellen, kan worden aangegeven dat de pagina haar status niet moet behouden. Dit gebeurt in de richtlijn [Page] van de presentatiecode:

<%@ Page Language="VB" EnableViewState="False" %>

In dit geval wordt de status van een component, ongeacht de waarde van de eigenschap [EnableViewState], niet opgeslagen in het verborgen veld [__VIEWSTATE]. Het is dan alsof de vorige status de lege tekenreeks was.

Laten we nu de client [curl] gebruiken om andere mechanismen te belichten. We vragen eerst de URL [http://localhost/form4.aspx] op:

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>

We ontvangen van de server de code HTML van [form4.aspx]. Deze verschilt niet van de code die de browser had ontvangen. Laten we hier nog eens terugkomen op het verzoek dat de browser deed toen hij het formulier verstuurde:

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=

Laten we hetzelfde doen met POST, maar zonder het veld [__VIEWSTATE] te verzenden:

dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=eerste+tekst --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>
..............

Let op de volgende punten:

  • de server heeft een gebeurtenis [TextChanged] gedetecteerd op [TextBox1], aangezien deze de tekst [lblInfo1] heeft gegenereerd. Het ontbreken van [__VIEWSTATE] vormde geen belemmering. Bij afwezigheid ervan gaat hij ervan uit dat de vorige waarde van een invoerveld de lege tekenreeks is.
  • Hij was in staat om de voor [TextBox1] geplaatste tekst terug te plaatsen in het attribuut [value] van de tag [TextBox1], zodat het veld [TextBox1] weer verschijnt met de ingevoerde waarde. Hiervoor heeft hij [__VIEWSTATE] niet nodig, maar alleen de waarde die voor [TextBox1] is gepost

Laten we nu dezelfde query opnieuw uitvoeren zonder iets te wijzigen. We krijgen dan het volgende nieuwe antwoord:

dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=eerste+tekst --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>

Omdat [__VIEWSTATE] ontbrak, kon de server niet vaststellen dat de waarde van het veld [TextBox1] niet was gewijzigd. De server gaat er dan vanuit dat de vorige waarde de lege tekenreeks was. Daarom heeft hij hier de gebeurtenis [TextChanged] gegenereerd op basis van [TextBox1]. Laten we hetzelfde verzoek nogmaals uitvoeren, maar dit keer met het veld [TextBox1] leeg en [TextBox2] niet leeg:

dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox2=tweede+tekst --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>
......

Bij gebrek aan [__VIEWSTATE] werd de vorige waarde van [TextBox1] beschouwd als de lege tekenreeks. Aangezien de bijgewerkte waarde van [TextBox1] eveneens de lege tekenreeks was, is de gebeurtenis [TextChanged] op [TextBox1] niet gegenereerd. De procedure [TextBox1_TextChanged] is niet uitgevoerd en daarom heeft het veld [lblInfo1] geen nieuwe waarde gekregen. We weten dat de component in dit geval zijn oude waarde behoudt. Maar hier is dat niet het geval: [lblInfo1] heeft zijn vorige waarde verloren. Dit komt doordat deze waarde wordt opgezocht in [__VIEWSTATE]. Aangezien dit veld ontbreekt, is de lege tekenreeks toegewezen aan [lblInfo1]. Voor [TextBox2] heeft de server de geposte waarde [second texte] vergeleken met de vorige waarde. Aangezien [__VIEWSTATE] ontbreekt, is deze vorige waarde gelijk aan de lege tekenreeks. Aangezien de geposte waarde van [TextBox2] verschilt van de lege tekenreeks, is de gebeurtenis [TextChanged] op [TextBox2] gegenereerd. De procedure [TextBox2_TextChanged] is uitgevoerd en het veld [lblInfo2] heeft een nieuwe waarde gekregen.

Men kan zich afvragen of de parameters [__EVENTTARGET] en [__EVENTARGUMENT] wel nuttig zijn. Als deze parameters niet worden verzonden, weet de server niet door welke gebeurtenis de [submit] is veroorzaakt. Laten we het eens proberen:

dos>curl --include --url http://localhost/form4.aspx --data TextBox2=tweede+tekst --data TextBox1=eerste+tekst
..............................
        <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>
.....................

We zien dat er geen gebeurtenis [TextChanged] is verwerkt. Bovendien worden de verzonden velden [TextBox1] en [TextBox2] niet teruggevonden met hun verzonden waarden. Het lijkt in feite alsof er een GET is uitgevoerd. Alles wordt weer normaal als het veld [__EVENTTARGET] in de verzonden velden voorkomt, zelfs als het geen waarde heeft:

dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET= --data TextBox2=tweede+tekst --data TextBox1=eerste+tekst
.......
        <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. Overige eigenschappen van de component TextBox

Met de servercomponent [TextBox] kunnen ook de tags HTML <input type="password"..> en <textarea>..</textarea>, c.a.d worden gegenereerd. Deze tags komen respectievelijk overeen met een beveiligd invoerveld en een invoerveld voor meerdere regels. Deze generatie wordt geregeld door de eigenschap [TextMode] van de component [TextBox]. Deze eigenschap kent drie mogelijke waarden:

waarde
gegenereerde tag HTML
SingleLine
<input type="text" ...>
MultiLine
<textarea>...</textarea>
Password
<input type="password ...>

We bekijken het gebruik van deze eigenschappen aan de hand van het volgende voorbeeld: [form5.aspx]:

Image

nr.
naam
type
eigenschappen
rol
1
btnAjouter
Knop
 
knop [submit] - dient om de inhoud van [TextBox1] toe te voegen aan die van [TextBox2]
2
TextBox1
TextBox
TextMode=Password
Text=
beveiligd invoerveld
3
TextBox2
TextBox
TextMode=Meerdere regels
Text=
voegt de invoer uit [TextBox1] samen

De eigenschap [EnableViewState] van de pagina wordt ingesteld op [false]. Aan de serverzijde verwerken we de klik op de knop [btnAjouter]:

Sub btnAjouter_Click(sender As Object, e As EventArgs)
  ' de inhoud van [textBox1] wordt samengevoegd met die van [TextBox2]
  textbox2.text=textbox2.text + textbox1.text+controlchars.crlf
End Sub

Om deze code te begrijpen, moet je je herinneren hoe de POST van een formulier wordt verwerkt. De procedures [Page_Init] en [Page_Load] worden als eerste uitgevoerd. Daarna volgen alle in de cache opgeslagen gebeurtenisprocedures. Ten slotte wordt de procedure uitgevoerd die de gebeurtenis afhandelt die de [POST] heeft veroorzaakt, in dit geval de procedure [btnAjouter_Click]. Wanneer de gebeurtenishandlers worden uitgevoerd, hebben alle componenten van de pagina die een waarde hebben in de POST deze waarde aangenomen. De overige componenten hebben hun vorige waarde teruggekregen als hun eigenschap [EnableViewState] op [true] stond, of hun ontwerpwaarde als hun eigenschap [EnableViewState] op [false] stond. Hier zullen de waarden van de velden [TextBox1] en [TextBox2] deel uitmaken van de door de klant ingevulde waarde POST. Ook in de voorgaande code krijgt [textbox1.text] de waarde die door de klant is ingevoerd, en hetzelfde geldt voor [textbox2.text]. De procedure [btnAjouter_Click] vult het veld [TextBox2] met de waarde die is gepost voor [TextBox2], opgeteld bij de waarde die is gepost voor [TextBox1], opgeteld bij het regeleinde-teken [ControlChars.CrLf], gedefinieerd in de naamruimte [Microsoft.VisualBasic]. Het is niet nodig om deze naamruimte te importeren, aangezien de webserver deze standaard importeert.

De uiteindelijke code van [form5.aspx] is als volgt:

<%@ Page Language="VB" EnableViewState="False" %>
<script runat="server">

    Sub btnAjouter_Click(sender As Object, e As EventArgs)
      ' de inhoud van [textBox1] wordt toegevoegd aan die van [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>

Iets hierboven is een schermafbeelding van een uitvoering weergegeven.

7.7. De component DropDownList

Met de tag <asp:DropDownList> kun je een vervolgkeuzelijst invoegen in de weergavecode van een pagina. We maken een pagina [form6.aspx] om de volgende weergave te krijgen:

Image

nr.
naam
type
eigenschappen
rol
1
DropDownList1
DropDownList
AutoPostback=true
EnableViewState=true
keuzelijst
2
lblInfo
Label
EnableViewState=false
informatiebericht

De gegenereerde weergavecode is als volgt:

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>

Op dit moment bevat de keuzelijst geen items. We gaan deze vullen in de procedure [Page_Load]. Hiervoor moeten we enkele eigenschappen en methoden van de klasse [DropDownList] kennen:

Items
collectie van het type [ListItemCollection] met de elementen van de vervolgkeuzelijst. De leden van deze collectie zijn van het type [ListItem].
Items.Count
aantal elementen in de verzameling [Items]
Items(i)
element nr. i van de lijst – van het type [ListItem]
Items.Add
om een nieuw element [ListItem] toe te voegen aan de verzameling [Items]
Items.Clear
om alle elementen uit de verzameling [Items] te verwijderen
Items.RemoveAt(i)
om element nr. i uit de verzameling [Items] te verwijderen
SelectedItem
het eerste element [ListItem] van de verzameling [Items] waarvan de eigenschap [Selected] op 'waar' staat
SelectedIndex
elementnummer [SelectedItem] in de verzameling [Items]

De elementen van de collectie [Items] van de klasse [DropDownList] zijn van het type [ListItem]. Elk element [ListItem] genereert een tag HTML <option>:

<option value="V" [selected="selected"]>T</option>

We beschrijven enkele eigenschappen en methoden van de klasse [ListItem]:

ListItem(String texte, String value)
constructor – maakt een element [ListItem] aan met de eigenschappen [texte] en [value]. Een element ListItem(T,V) leidt tot de tag HTML <option value="V">T</option>. Met de klasse [ListITem] kunnen dus de elementen van een lijst HTML worden beschreven
Selected
booleaanse waarde. Indien waar, krijgt de bijbehorende optie in de lijst HTML het attribuut [selected="selected"]. Dit attribuut geeft aan de browser door dat het bijbehorende element geselecteerd moet worden weergegeven in de lijst HTML
Text
de tekst T van de optie HTML <option value="V" [selected="selected"]>T</option>
Value
de waarde V van het attribuut [Value] van de optie HTML <option value="V" [selected="selected"]>T</option>

We hebben voldoende informatie om in de procedure [Page_Load] van de pagina de code voor het vullen van de vervolgkeuzelijst [DropDownList1] te schrijven:

    Sub Page_Load(sender As Object, e As EventArgs)
        ' de combo wordt ingevuld als dit de eerste keer is dat deze wordt aangeroepen
        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

Als de component [DropDownList1] op deze manier is geïnitialiseerd, zal de vertaling HTML als volgt zijn:

<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>

We weten dat de procedure [Page_Load] wordt uitgevoerd telkens wanneer de pagina [form6.aspx] wordt uitgevoerd. Deze wordt de eerste keer aangeroepen door een GET, en vervolgens door een POST telkens wanneer de gebruiker een nieuw item in de vervolgkeuzelijst selecteert. Moet de code voor het vullen van deze lijst in [Page_Load] telkens opnieuw worden uitgevoerd? Het antwoord hangt af van het attribuut [EnableViewState] van de component [DropDownList1]. Als dit attribuut op ‘waar’ staat, weten we dat de status van de component [DropDownList1] bij elke verzoek in het verborgen veld [__VIEWSTATE] wordt bewaard. Deze status omvat twee zaken:

  • de lijst met alle waarden van de vervolgkeuzelijst
  • de waarde van het geselecteerde item in deze lijst

Het lijkt dan verleidelijk om de eigenschap [EnableViewState] van de component [DropDownList1] in te stellen op [true], zodat de waarden die in de lijst moeten worden geplaatst niet opnieuw hoeven te worden berekend. Het probleem is echter dat de procedure [Page_Load] telkens wordt uitgevoerd wanneer de pagina [form6.aspx] wordt opgevraagd, waardoor deze waarden toch opnieuw worden berekend. Het object [Page], waarvan [form6.aspx] een instantie is, heeft een attribuut [IsPostBack] met een booleaanse waarde. Als dit attribuut op ‘waar’ staat, betekent dit dat de pagina wordt aangeroepen door een POST. Als de waarde ‘false’ is, betekent dit dat de pagina is opgeroepen door een GET. In ons client-server-systeem vraagt de client altijd dezelfde pagina [form6.aspx] op bij de server. De eerste keer vraagt hij deze op met een GET, de volgende keren met een POST. Hieruit kunnen we concluderen dat de eigenschap [IsPostBack] ons kan helpen om de eerste aanroep GET van de client te detecteren. De waarden van de vervolgkeuzelijst worden alleen bij deze eerste aanroep gegenereerd. Bij volgende verzoeken worden deze waarden gegenereerd door het mechanisme van de [VIEWSTATE]. In andere situaties kan de inhoud van een lijst per verzoek variëren en moet deze dan bij elk verzoek opnieuw worden berekend. In dat geval wordt het attribuut [EnableViewState] van de lijst ingesteld op [false] om een onnodige dubbele berekening van de inhoud van de lijst te voorkomen, tenzij men de eerder geselecteerde elementen in de lijst moet kennen, aangezien deze informatie wordt bewaard in het [VIEWSTATE].

Het attribuut [AutoPostBack] van de lijst [DropDownList1] is op ‘waar’ gezet. Dit betekent dat de browser het formulier zal verzenden zodra hij de gebeurtenis ‘wijziging van het geselecteerde element’ in de vervolgkeuzelijst detecteert. De server zal op zijn beurt aan de hand van [VIEWSTATE] en de verzonden waarden detecteren dat het geselecteerde element in de component [DropDownList1] is gewijzigd. Vervolgens zal de server de gebeurtenis [SelectedIndexChanged] op deze component activeren. We zullen dit verwerken met de volgende procedure:

    Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
      ' selectie wijzigen
      lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
        " valeur=" + dropdownlist1.selecteditem.value + _
        " index="+ dropdownlist1.selectedindex.tostring
    End Sub

Wanneer deze procedure wordt uitgevoerd, heeft het object [DropDownList1] zijn elementen van het type [ListItem] teruggevonden dankzij [VIEWSTATE]. Bovendien heeft een van deze elementen van het type [ListItem] het attribuut [Selected] ingesteld op ‘waar’; dit is het element waarvan de waarde door de browser is verzonden. Er is op verschillende manieren toegang tot dit element:

DropDownList1.SelectedItem
is het eerste element [ListItem] in de lijst waarvan het attribuut [Selected] op ‘waar’ staat
DropDownList1.SelectedItem.Text
komt overeen met het gedeelte [texte] van de tag HTML van het element <option value="...">tekst</option> dat door de gebruiker is geselecteerd
DropDownList1.SelectedItem.Value
komt overeen met het gedeelte [value] van de tag HTML van het element <option value="...">tekst</option> dat door de gebruiker is geselecteerd
DropdDownList1.SelectedIndex
nummer in de verzameling [DropDownList1.Items] van het eerste element [ListItem] waarvan het attribuut [Selected] op 'waar' staat

De uiteindelijke code van [form6.aspx] is als volgt:

<%@ Page Language="VB" %>
<script runat="server">

    Sub Page_Load(sender As Object, e As EventArgs)
        ' vul het keuzemenu in als dit de eerste keer is dat je wordt opgeroepen
        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)
      ' selectie gewijzigd
      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. De component ListBox

Met de tag <asp:ListBox> kun je een lijst invoegen in de opmaakcode van een pagina. We maken [form7.aspx] aan om de volgende weergave te krijgen:

Image

nr.
naam
type
eigenschappen
rol
1
txtSaisie
TextBox
EnableViewState=false
invoerveld
2
btnAjouter
Knop
 
knop [submit] die de inhoud van txtSaisie naar Lijst 1 doorstuurt, mits deze niet leeg is.
3
ListBox1
ListBox
EnableViewState=true
SelectionMode=Single
lijst met waarden voor enkele selectie
4
ListBox2
ListBox
EnableViewState=true
SelectionMode=Multiple
lijst met waarden voor meervoudige selectie
5
btn1vers2
Knop
 
knop [submit] die het geselecteerde element uit [liste 1] doorgeeft aan [liste 2]
6
btn2vers1
Knop
 
knop [submit] die de geselecteerde elementen van [liste 2] naar [liste 1] overbrengt

De gegenereerde weergavecode is als volgt:

<%@ 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>

De klasse [ListBox] is afgeleid van dezelfde klasse [ListControl] als de eerder besproken klasse [DropDownList]. We vinden hierin alle eigenschappen en methoden terug die we bij [DropDownList] hebben gezien, omdat deze in feite tot [ListControl] behoorden. Er verschijnt een nieuwe eigenschap:

SelectionMode
bepaalt de selectiemodus van de lijst HTML <select> die op basis van de component wordt gegenereerd. Als SelectionMode=Single is, kan slechts één element worden geselecteerd. Als SelectionMode=Multiple is, kunnen meerdere elementen worden geselecteerd. Hiervoor wordt het attribuut [multiple="multiple"] gegenereerd in de tag <select> van de lijst HTML.

Laten we de gebeurtenissen verwerken. Een klik op de knop [Ajouter] wordt verwerkt door de volgende procedure [btnAjouter_Click]:

    Sub btnAjouter_Click(sender As Object, e As EventArgs)
      ' toevoeging aan lijst 1
      dim texte as string=txtSaisie.text.trim
      if texte<> "" then ListBox1.Items.Add(New ListItem(texte))
      ' txtSaisie wissen
      txtSaisie.text=""
    End Sub

Als de tekst die in [txtSaisie] is ingevoerd niet de lege of witte tekenreeks is, wordt er een nieuw element toegevoegd aan de lijst [ListBox1]. We weten dat we een element van het type [ListItem] moeten toevoegen. Eerder hadden we de constructor [ListItem(T as String, V as String)] gebruikt om een soortgelijke taak uit te voeren. Zo’n element leidt tot de tag HTML [<option value="V">T</option>]. Hier gebruiken we de constructor [ListItem(T as String)], die de tags HTML, [<option value="T">T</option>] en c.a.d genereert. waarbij de tekst [T] van de optie wordt gebruikt om de waarde van de optie te vormen. Zodra de inhoud van [txtSaisie] is toegevoegd aan de lijst [ListBox1], wordt het veld [txTSaisie] leeggemaakt.

Klikken op de knoppen [Effacer] worden verwerkt door de volgende procedures:

    Sub btnRaz1_Click(sender As Object, e As EventArgs)
      ' lijst 1 wissen
      ListBox1.Items.Clear
    End Sub

    Sub btnRaz2_Click(sender As Object, e As EventArgs)
      ' lijst 2 wissen
      ListBox2.Items.Clear
    End Sub

Klikken op de knoppen voor het wisselen tussen lijsten worden verwerkt door de volgende procedures:

    Sub btn1vers2_Click(sender As Object, e As EventArgs)
      ' het geselecteerde element verplaatsen van lijst 1 naar lijst 2
      transfert(ListBox1,ListBox2)
    End Sub

    Sub btn2vers1_Click(sender As Object, e As EventArgs)
      ' het geselecteerde element verplaatsen van lijst 2 naar lijst 1
      transfert(ListBox2,ListBox1)
    End Sub

    sub transfert(l1 as listbox, l2 as listbox)
      ' de geselecteerde elementen van l1 naar l2 verplaatsen
       ' moet er iets gebeuren?
      if l1.selectedindex=-1 then return
      dim i as integer
      ' we beginnen bij het einde
      for i=l1.items.count-1 to 0 step -1
        ' geselecteerd?
        if l1.items(i).selected then
            ' niet meer geselecteerd
            l1.items(i).selected=false
          ' overzetten naar l2
          l2.items.add(l1.items(i))
          ' verwijderen uit l1
          l1.items.removeAt(i)
        end if
      next
    end sub

Omdat beide knoppen dezelfde functie hebben, namelijk het overzetten van items van de ene lijst naar de andere, kunnen we dit terugbrengen tot één enkele overdrachtsprocedure met twee parameters:

  • l1 van het type [ListBox], de bronlijst
  • l2 van het type [ListBox], de doellijst

Allereerst controleren we of er ten minste één element is geselecteerd in lijst l1; zo niet, dan hoeft er niets te gebeuren. Hiervoor bekijken we de eigenschap [l1.selectedindex], die het nummer vertegenwoordigt van het eerste geselecteerde element in de lijst. Als er geen is, is de waarde -1. Als er ten minste één element is geselecteerd in l1, voeren we de overdracht naar l2 uit. Hiervoor doorlopen we de volledige lijst met elementen van l1 en kijken we voor elk element of het attribuut [selected] op ‘waar’ staat. Zo ja, dan wordt het attribuut [selected] gewijzigd in [false], vervolgens wordt het element overgenomen in de lijst l2 en ten slotte verwijderd uit de lijst l1. Deze verwijdering leidt tot een hernummering van de elementen in de lijst l1. Daarom wordt de lijst met elementen van l1 in omgekeerde volgorde doorlopen. Als we deze in de juiste volgorde doorlopen en element nr. 10 verwijderen, wordt element nr. 11 nummer 10 en nummer 12 element nr. 11. Nadat element nr. 10 is verwerkt, gaat onze voorwaartse lus verder met element nr. 11, dat volgens de zojuist gegeven uitleg het voormalige nr. 12 is. Het element dat nr. 11 had en nu nr. 10 draagt, wordt over het hoofd gezien. Door de elementen van lijst l1 in de tegenovergestelde richting te doorlopen, wordt dit probleem vermeden.

7.9. De componenten CheckBox, RadioButton

Met de tags <asp:RadioButton> en <asp:CheckBox> kunnen we respectievelijk een keuzerondje en een selectievakje invoegen in de weergavecode van een pagina. We maken een pagina [form8.aspx] om de volgende weergave te verkrijgen:

Image

nr.
naam
type
eigenschappen
rol
1
RadioButton1
RadioButton2
RadioButton3
RadioButton
RadioButton1.Checked=true
RadioButton1.Text=1
RadioButton2.Checked=false
RadioButton2.Text=2
RadioButton3.Checked=false
RadioButton3.Text=3
voor de 3 knoppen: GroupName=radio
keuzeknoppen
2
CheckBoxA
CheckBoxB
CheckBoxC
CheckBox
Checked=false voor alle
CheckBoxA.Text=A
CheckBoxB.Text=B CheckBoxC.Text=C
selectievakjes
3
btnEnvoyer
Knop
 
knop [submit]
4
lstInfos
ListBox
 
informatielijst

Om ervoor te zorgen dat de browser de drie keuzerondjes als elkaar uitsluitende knoppen behandelt, moeten ze in een groep keuzerondjes worden ondergebracht. Dit gebeurt met behulp van het attribuut [GroupName] van de klasse [RadioButton]. De status van de pagina hoeft in deze toepassing niet te worden bijgehouden. Daarom voegen we het attribuut [EnableViewState="false"] toe aan de pagina. De opmaakcode is als volgt:

<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>
            &nbsp;<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>

We moeten de procedure [btnEnvoyer_Click] schrijven om de gebeurtenis [Click] op deze knop te verwerken. De status van een keuzerondje of een selectievakje wordt bepaald door het attribuut [Checked], een booleaanse waarde die ‘waar’ is als het vakje is aangevinkt en ‘onwaar’ anders. We hoeven dus alleen maar in de lijst [lstInfos] de waarde van het attribuut [Checked] van de zes keuzerondjes en selectievakjes op te nemen. Aangezien dit geen bijzondere moeilijkheden oplevert, gaan we een stapje verder:

<script runat="server">

    Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
      ' informatie in de keuzelijst plaatsen
      for each c as control in FindControl("frmControls").controls
          ' is het besturingselement afgeleid van CheckBox
          if TypeOf(c) is CheckBox then
              lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
          end if
      next
    End Sub

</script>

De pagina kan worden gezien als een boomstructuur van besturingselementen. In ons voorbeeld bevat onze pagina tekst en serverbesturingselementen. De tekst wordt gezien als een specifiek besturingselement met de naam [LiteralControl]. Elke tekst geeft aanleiding tot dit besturingselement, zelfs een reeks spaties tussen twee besturingselementen. Elk besturingselement heeft een attribuut ID waarmee het wordt geïdentificeerd. Dit is het attribuut ID dat in de tags verschijnt:

            <asp:CheckBox id="CheckBoxA" runat="server" ></asp:CheckBox>

Als we de besturingselementen [LiteralControl] buiten beschouwing laten, bevat de onderzochte pagina de volgende besturingselementen:

  • [HtmlForm], wat het formulier [ID=frmControls] is. Dit formulier is op zijn beurt een container voor besturingselementen. Het bevat de volgende besturingselementen:

-- [ID=RadioButton1] van het type [RadioButton]

-- [ID=RadioButton2] van het type [RadioButton]

-- [ID=RadioButton3] van het type [RadioButton]

-- [ID=CheckBoxA] van het type [CheckBox]

-- [ID=CheckBoxA] van het type [CheckBox]

-- [ID= CheckBoxA] van het type [CheckBox]

-- [ID=btnVerzenden] van het type [Button]

Een besturingselement heeft de volgende eigenschappen:

[Control].Controls
geeft de verzameling van onderliggende controles van [Control] weer, indien aanwezig
[Control].FindControl(ID)
geeft het besturingselement weer dat wordt geïdentificeerd door ID en dat zich in de root van de boomstructuur van de onderliggende besturingselementen van [Control] bevindt. In het bovenstaande voorbeeld: Page.FindControl("frmControls") verwijst naar de container [HtmlForm]. Om de keuzeknop [RadioButton1] te bereiken, moet je het volgende schrijven
Page.FindControl("frmControls").FindControl("RadioButton1")
[Control].ID
ID van [Control]

Laten we teruggaan naar de procedurecode [btnEnvoyer_Click]:

    Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
      ' informatie wordt in de keuzelijst geplaatst
      for each c as control in FindControl("frmControls").controls
          ' is het besturingselement afgeleid van CheckBox?
          if TypeOf(c) is CheckBox then
              lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
          end if
      next
    End Sub

We willen de status van de keuzerondjes en selectievakjes in het formulier weergeven. We doorlopen alle besturingselementen in het formulier. Als het huidige besturingselement van een type is dat is afgeleid van [CheckBox], geven we de eigenschap [Checked] weer. Aangezien de klasse [RadioButton] is afgeleid van de klasse [CheckBox], geldt de toetsing voor beide soorten besturingselementen. De hierboven getoonde schermafbeelding toont een voorbeeld van de uitvoering.

7.10. De componenten CheckBoxList, RadioButtonList

Soms wil men de gebruiker keuzes laten maken uit waarden die op het moment van het ontwerpen van de pagina nog niet bekend zijn. Deze keuzes zijn afkomstig uit een configuratiebestand, een database, ... en worden pas bij de uitvoering bekend. Er bestaan oplossingen voor dit probleem en we zijn ze tegengekomen. De enkelvoudige keuzelijst is geschikt wanneer de gebruiker slechts één keuze kan maken en de meervoudige keuzelijst wanneer hij meerdere keuzes kan maken. Vanuit esthetisch oogpunt, en als het aantal keuzemogelijkheden niet groot is, kan men ervoor kiezen om keuzerondjes te gebruiken in plaats van de enkelvoudige keuzelijst of selectievakjes in plaats van de meervoudige keuzelijst. Dit is mogelijk met de componenten [CheckBoxList] en [RadioButtonList].

De klassen [CheckBoxList] en [RadioButtonList] zijn afgeleid van dezelfde klasse [ListControl] als de eerder besproken klassen [DropDownList] en [ListBox]. We zullen dan ook enkele van de eigenschappen en methoden terugvinden die we bij deze klassen hebben gezien, namelijk die welke in feite tot [ListControl] behoorden.

Items
verzameling van het type [ListItemCollection] van de elementen in de vervolgkeuzelijst. De leden van deze verzameling zijn van het type [ListItem].
Items.Count
aantal elementen in de verzameling [Items]
Items(i)
element nr. i van de lijst – van het type [ListItem]
Items.Add
om een nieuw element [ListItem] toe te voegen aan de verzameling [Items]
Items.Clear
om alle elementen uit de verzameling [Items] te verwijderen
Items.RemoveAt(i)
om element nr. i uit de verzameling [Items] te verwijderen
SelectedItem
het eerste element [ListItem] van de verzameling [Items] waarvan de eigenschap [Selected] op 'waar' staat
SelectedIndex
nummer van het vorige element in de verzameling [Items]

Sommige eigenschappen zijn specifiek voor de klassen [CheckBoxList] en [RadioButtonList]:

RepeatDirection
[horizontal] of [vertical] voor horizontale of verticale lijsten.

De elementen van de collectie [Items] zijn van het type [ListItem]. Elk element van het type [ListItem] genereert een andere tag, afhankelijk van of het om een object van het type [CheckBoxList] of [RadioButtonList] gaat:

<input type="checkbox" [selected="selected"] value="V">Texte

Of

<input type="radio" [selected="selected"] value="V">Texte

We beschrijven enkele eigenschappen en methoden van de klasse [ListItem]:

ListItem(String texte, String value)
constructor – maakt een element [ListItem] aan met de eigenschappen tekst en waarde. Een element ListItem(T,V) genereert de tag HTML <input type="checkbox" value="V">T of <input type="radio" value="V">T, afhankelijk van het geval.
Selected
booleaanse waarde. Indien waar, krijgt de bijbehorende optie in de lijst HTML het attribuut [selected="selected"]. Dit attribuut geeft aan de browser door dat het bijbehorende element geselecteerd moet worden weergegeven in de lijst HTML
Text
de tekst T van de optie HTML <input type=".." value="V" [selected="selected"]>T
Value
de waarde van het Value-attribuut van de optie HTML <input type=".." value="V" [selected="selected"]>T

We willen de volgende pagina [form8b.aspx] opbouwen:

Image

nr.
naam
type
eigenschappen
rol
1
RadioButtonList1
RadioButtonList
EnableViewState=true
RepeatDirection=horizontal
lijst met keuzerondjes
2
CheckBoxList1
CheckBoxList
EnableViewState=true
RepeatDirection=horizontal
lijst met selectievakjes
3
btnEnvoyer
Knop
 
knop [submit] die in [4] de lijst weergeeft met de geselecteerde items uit beide lijsten
4
lstInfos
ListBox
EnableViewState=false
lijst met waarden

De opmaakcode van de pagina is als volgt:

<%@ 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>

De controlecode is als volgt:

<script runat="server">

    Sub Page_Load(sender As Object, e As EventArgs) handles MyBase.Load
        ' de lijsten worden gevuld als dit de eerste keer is dat de functie wordt aangeroepen
        if not IsPostBack then
            ' teksten voor de lijst RadioButton
          dim textesRadio() as String = {"1","2","3","4"}
          ' teksten voor de lijst RadioButton
          dim textesCheckBox() as  String = {"un","deux","trois","quatre"}
                 ' invullen keuzelijst
            dim i as integer
            for i=0 to textesRadio.length-1
                RadioButtonList1.Items.Add(new ListItem(textesRadio(i)))
            next
             ' selectie van element nr. 1
            RadioButtonList1.SelectedIndex=1
            ' vullen van de selectievakjeslijst
            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)
      ' informatie in de keuzelijst lstinfos plaatsen
      affiche(RadioButtonList1)
      affiche(CheckBoxList1)
    End Sub

    sub affiche(l1 as ListControl)
      ' de waarden van de geselecteerde elementen uit l1 weergeven
       ' moet er iets gebeuren?
      if l1.selectedindex=-1 then return
      dim i as integer
      ' we beginnen bij het einde
      for i= 0 to l1.items.count-1
        ' geselecteerd?
        if l1.items(i).selected then
          lstInfos.Items.Add("["+TypeName(l1)+"] ["+l1.items(i).text+"] sélectionné")
        end if
      next
    end sub

</script>

In de procedure [Page_Load], die bij elke weergave van de pagina wordt uitgevoerd, worden de twee lijsten geïnitialiseerd. Om te voorkomen dat dit telkens opnieuw gebeurt, wordt de eigenschap [IsPostBack] gebruikt om dit slechts de eerste keer te doen. De volgende keren worden de lijsten automatisch opnieuw gegenereerd via het mechanisme van [VIEWSTATE]. Zodra de pagina is weergegeven, vinkt de gebruiker bepaalde selectievakjes aan en klikt hij op de knop [Envoyer]. De waarden van het formulier worden vervolgens naar het formulier zelf verzonden. Na uitvoering van [Page_Load] wordt de procedure [btnEnvoyer_Click] uitgevoerd. Deze roept de procedure [affiche] aan om de lijst [lstInfos] te vullen. Deze procedure ontvangt als parameter een object van het type [ListControl], waardoor er zowel een object van het type [RadioButtonList] als een object van het type [CheckBoxList] naar kan worden verzonden; dit zijn klassen die zijn afgeleid van [ListControl]. De lijst [lstInfos] kan het attribuut [EnableViewState] instellen op [false], aangezien de status ervan niet tussen de verschillende verzoeken hoeft te worden behouden.

7.11. De componenten Panel, LinkButton

Met de tag <asp:panel> kan een container met besturingselementen in een pagina worden ingevoegd. Het voordeel van de container is dat sommige van zijn eigenschappen van toepassing zijn op alle besturingselementen die hij bevat. Dit geldt ook voor de eigenschap [Visible]. Deze eigenschap bestaat voor elk serverbesturingselement. Als een container de eigenschap [Visible=false] heeft, wordt elk van de besturingselementen daarin beheerd door zijn eigen eigenschap [Visible]. Als de container de eigenschap [Visible=false] heeft, worden de container en alles wat deze bevat niet weergegeven. Dit kan eenvoudiger zijn dan het beheren van de eigenschap [Visible] voor elk afzonderlijk besturingselement in de container.

Met de tag <asp:LinkButton> kun je een link invoegen in de presentatiecode van een pagina. Deze tag heeft een vergelijkbare functie als de knop [Button]. Hij veroorzaakt namelijk een client-side POST via een bijbehorende JavaScript-functie. We maken een [form9.aspx]-pagina om de volgende weergave te verkrijgen:

Image

nr.
naam
type
eigenschappen
rol
1
Panel1
Paneel
EnableViewState=true
container met besturingselementen
2
ListBox1
ListBox
EnableViewState=true
een lijst met drie waarden
3
lnkCacher
LinkButton
EnableViewState=false
link om de container te verbergen

Wanneer de container is verborgen, verschijnt er een nieuwe link:

Image

nr.
naam
type
eigenschappen
rol
4
lnkVoir
LinkButton
EnableViewState=false
link om de container weer te geven

De opmaakcode van de pagina is als volgt:

<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>

Merk op dat deze code de lijst [ListBox1] met drie waarden initialiseert. De gebeurtenishandlers [Clic] voor beide koppelingen zijn als volgt:

<%@ 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)
        ' geeft container 1 weer
        panel1.Visible=true
         ' wijzig de links
        lnkVoir.visible=false
        lnkCacher.visible=true
    End Sub

    Sub lnkCacher_Click(sender As Object, e As EventArgs)
        ' verbergt container 1
        panel1.Visible=false
         ' wijzigt links
        lnkVoir.visible=true
        lnkCacher.visible=false
    End Sub
</script>

We gebruiken de procedure [Page_Load] om het formulier te initialiseren. Dit doen we bij de eerste aanvraag (IsPostBack=false):

<%@ Page Language="VB" %>
<script runat="server">

    Sub Page_Load(sender As Object, e As EventArgs)
        ' de eerste keer
        if not IsPostBack then
            ' de container wordt weergegeven
            lnkVoir_Click(nothing,nothing)
        end if
    end sub
.....
</script>

7.12. Om verder te gaan...

In de voorgaande paragrafen zijn een aantal servercomponenten besproken. Telkens zijn slechts enkele eigenschappen daarvan aan bod gekomen. Om deze componenten nader te bestuderen, kan de lezer op verschillende manieren te werk gaan:

  • de eigenschappen van een component ontdekken met behulp van een IDE, zoals WebMatrix. Hierin worden namelijk de belangrijkste eigenschappen van de componenten die in een formulier worden gebruikt, weergegeven
  • de documentatie van .NET raadplegen om alle klassen te ontdekken die bij elk van de servercomponenten horen. Dit is de aanbevolen methode om de component volledig onder de knie te krijgen. Hierin vindt men de boomstructuur van de klassen die naar de componenten leiden, evenals de eigenschappen, methoden, constructors en gebeurtenissen van elk van deze klassen. Bovendien bevat de documentatie soms voorbeelden.

In dit hoofdstuk hebben we de alles-in-één-techniek [WebMatrix] gebruikt, waarbij we de presentatiecode en de besturingscode van een pagina in hetzelfde bestand hebben geplaatst. Over het algemeen raden we deze methode niet aan, maar wel de eerder gebruikte [codebehind]-methode, waarbij deze twee soorten code in twee afzonderlijke bestanden worden geplaatst. We herinneren eraan dat het voordeel van deze scheiding ligt in het feit dat de besturingscode kan worden gecompileerd zonder dat de webapplicatie hoeft te worden uitgevoerd. Bovendien hadden onze voorbeelden – zoals we al aan het begin van dit hoofdstuk hebben uitgelegd – een heel specifiek profiel: ze bestonden uit één enkele pagina in de vorm van een formulier dat de client en de server in opeenvolgende vraag-antwoordcycli uitwisselden, waarbij het eerste verzoek van de client een GET was en de volgende verzoeken POST.

7.13. Servercomponenten en applicatiecontroller

In de voorgaande hoofdstukken hebben we verschillende webapplicaties gebouwd. Ze zijn allemaal opgebouwd volgens de MVC-architectuur (Model-View-Controller), die de applicatie in duidelijk gescheiden blokken opsplitst en het onderhoud ervan vergemakkelijkt. We bouwden onze gebruikersinterfaces toen met standaard HTML-tags. Gezien hetgeen we zojuist hebben gezien, is het logisch dat we nu servercomponenten willen gebruiken. Laten we terugkomen op een probleem dat we al uitgebreid hebben bestudeerd, namelijk het berekenen van een belasting. De MVC-architectuur daarvan was als volgt:

Image

De applicatie heeft twee weergaven: [formulaire.aspx] en [erreurs.aspx]. De weergave [formulaire.aspx] wordt getoond wanneer de URL [main.aspx] voor het eerst wordt opgevraagd:

Image

De gebruiker vult het formulier in:

Image

en klikt op de knop [Calculer] om het volgende antwoord te krijgen:

Image

In een applicatie MVC moet elk verzoek via de controller lopen, in dit geval [main.aspx]. Dit betekent dat wanneer het formulier [formulaire.aspx] door de gebruiker is ingevuld, het moet worden verzonden naar [main.aspx] en niet naar [formulaire.aspx]. Dit is simpelweg niet mogelijk als we de gebruikersinterface [formulaire.aspx] bouwen met servercomponenten van het type ASP. Om dit te illustreren, bouwen we een formulier [formtest.aspx] met een <asp:button>-component:

<%@ 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>

Let op het attribuut [action="main.aspx"] van de tag <form..>. Laten we deze toepassing uitvoeren. De presentatiepagina bevat slechts één knop:

Image

Laten we eens kijken naar de code HTML die door de server is verzonden:

<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>

We zien dat de POST van het formulier verwijst naar het formulier zelf, [action="formtest.aspx"], terwijl we in [formtest.aspx] de tag HTML server hadden geschreven:

    <form action="main.aspx" runat="server">

Het attribuut [runat="server"] van de tag <form> wordt ons opgelegd door het gebruik van servercomponenten. Er treedt een compilatiefout op als we dit attribuut niet opgeven. Wanneer we het wel opgeven, wordt het attribuut [action] van de tag <form> genegeerd. De server genereert altijd een attribuut [action] dat naar het formulier zelf verwijst. Hieruit volgt dat we in een MVC-toepassing geen formulieren kunnen gebruiken die zijn opgebouwd met de tag <form ... runat="server">. Deze tag is echter onmisbaar voor alle ASP-servercomponenten die invoer van de gebruiker ophalen. Met andere woorden: men kan geen ASP-serverformulieren gebruiken in een MVC-toepassing. Dat is een belangrijke ontdekking. Een van de sterke punten van de marketing van ASP.NET is immers dat men een webtoepassing kan bouwen zoals een Windows-toepassing. Dat klopt als onze applicatie de MVC-architectuur niet volgt, maar geldt des te meer als dat wel het geval is. De MVC-architectuur lijkt echter een fundamenteel begrip in de huidige webontwikkeling te zijn dat moeilijk te negeren valt.

Het is mogelijk om de MVC-architectuur in combinatie met formulieren met ASP-componenten te gebruiken voor applicaties met weinig verschillende weergaven, dankzij de volgende truc:

  • de applicatie bestaat uit één enkele pagina die als controller fungeert
  • de weergaven worden op deze pagina weergegeven door verschillende containers, één container per weergave. Om een weergave te tonen, wordt de bijbehorende container zichtbaar gemaakt en worden de andere verborgen

Dit is een elegante oplossing die we nu in enkele voorbeelden zullen toepassen

7.14. Voorbeelden van MVC-toepassingen met ASP-servercomponenten

7.14.1. Voorbeeld 1

In dit eerste voorbeeld passen we de servercomponenten toe die we hebben gepresenteerd. De pagina [form10.aspx] ziet er als volgt uit:

De schermafbeelding links hierboven toont het formulier zoals het aan de klant wordt gepresenteerd. De klant vult het in en bevestigt het via [Envoyer]. De server stuurt hem een weergave terug met een lijst van de ingevoerde waarden (schermopname rechts). Via een link kan de gebruiker terugkeren naar het formulier. Hij vindt het formulier terug zoals hij het heeft bevestigd. De weergavecode van [form10.aspx] is als volgt:

<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>

De pagina heeft twee containers, één voor elke weergave: [panelform] voor de formulierweergave, [panelinfo] voor de informatieweergave. De lijst met componenten van de container [panelForm] is als volgt:

naam
type
eigenschappen
rol
panelform
Paneel
EnableViewState=true
formulierweergave
rdOui
rdNon
RadioButton
EnableViewState=true
GroupName=rdmarie
keuzeknoppen
chk1
chk2
chk3
CheckBox
EnableViewState=true
selectievakjes
txtSaisie
TextBox
EnableViewState=true
invoerveld
txtMdp
TextBox
EnableViewState=true
beveiligd invoerveld
txtArea
TextBox
EnableViewState=true
invoervak met meerdere regels
cmbValeurs
DropDownList
EnableViewState=true
uitklaplijst
lstSimple
ListBox
EnableViewState=true
SelectionMode=Single
lijst met enkele selectie
btnRazSimple
Knop
EnableViewState=false
ontkoppelt alle elementen van lstSimple
lstMultiple
ListBox
EnableViewState=true
SelectionMode=Multiple
lijst met meervoudige selectie
btnRazMultiple
Knop
EnableViewState=false
ontkoppelt alle elementen van lstMultiple
lblSecret
Label
EnableViewState=true
Visible=false
verborgen veld
btnEffacer
HTML standaard
 
geeft een waarschuwing weer
btnEnvoyer
Knop
EnableViewState=false
knop [submit] van het formulier
btnReset
HTML standaard
 
knop [reset] van het formulier

De rol van [VIEWSTATE] voor de componenten is hier belangrijk. Alle componenten, behalve de knoppen, moeten de eigenschap [EnableViewState=true] hebben. Om de reden hiervoor te begrijpen, moet men de werking van de applicatie in gedachten houden. Stel dat het veld [txtSaisie] de eigenschap [EnableViewState=false] heeft:

  1. de klant vraagt de pagina [form10.aspx] voor de eerste keer op. Hij krijgt het formulier te zien
  2. vult dit in en verstuurt het via de knop [Envoyer]. De invoervelden worden vervolgens verzonden en de server wijst aan de servercomponenten de verzonden waarde toe, of hun status [VIEWSTATE] als ze die hadden. Zo krijgt het veld [txtSaisie] de door de gebruiker ingevoerde waarde toegewezen. Ook voor deze stap heeft de status [VIEWSTATE] geen functie. Als resultaat van de bewerking wordt de weergave [informations] verzonden; in feite is dit nog steeds de pagina [form10.aspx], maar met een andere weergegeven container.
  3. De gebruiker bekijkt deze nieuwe weergave en gebruikt de link [Retour au formulaire] om hiernaar terug te keren. Er wordt dan een POST uitgevoerd naar [form10.aspx]. Er wordt dan hooguit één waarde verzonden: de waarde die de gebruiker in de informatielijst heeft geselecteerd, informatie die vervolgens niet wordt gebruikt. Feit blijft dat er geen veld [txtSaisie] wordt verzonden.
  4. De server ontvangt de POST en wijst aan de servercomponenten de verzonden waarde toe, of hun status [VIEWSTATE] indien ze die hadden. In dit geval heeft [txtNom] geen verzonden waarde. Als het attribuut [EnableViewState] de waarde [false] heeft, wordt de lege tekenreeks eraan toegewezen. Aangezien we willen dat het de door de gebruiker ingevoerde waarde heeft, moet het de eigenschap [EnableViewState=true] hebben.

De container [panelinfo] heeft de volgende besturingselementen:

naam
type
eigenschappen
rol
panelinfo
Paneel
EnableViewState=false
informatieweergave
lstInfos
ListBox
EnableViewState=false
overzichtslijst met de door de gebruiker ingevoerde waarden
LinkButton1
LinkButton
EnableViewState=false
link terug naar het formulier

Als we tijdens het testen kijken naar de code HTML die door de bovenstaande presentatiecode is gegenereerd, zal de gegenereerde code voor het verborgen veld [lblSecret] wellicht verbazing wekken:

                    <tr>
                        <td>
                            Champ caché</td>
                        <td></td>
                    </tr>

De component [lblSecret] wordt niet omgezet naar de code HTML omdat deze de eigenschap [Visible=false] heeft. Omdat het echter de eigenschap [EnableViewState=true] heeft, wordt de waarde ervan niettemin bewaard in het verborgen veld [__VIEWSATE]. We zullen deze waarde dus kunnen ophalen, zoals uit de tests zal blijken.

Nu moeten we nog de gebeurtenishandlers schrijven. In [Page_Load] initialiseren we het formulier:

Sub page_Load(sender As Object, e As EventArgs)
         ' de eerste keer worden de elementen geïnitialiseerd
          ' de volgende keren krijgen deze hun waarden terug via de VIEWSTATE
         if IsPostBack then return
         ' formulier initialiseren
          ' info-paneel wordt niet weergegeven
         panelinfo.visible=false
         ' formulierpaneel weergegeven
         panelform.visible=true
         ' keuzerondjes
         rdNon.Checked=true
         ' selectievakjes
         chk2.Checked=true
          ' invoerveld
         txtSaisie.Text="qqs mots"
          ' wachtwoordveld
         txtMdp.Text="ceciestsecret"
         ' invoervak
         txtArea.Text="ligne"+ControlChars.CrLf+"ligne2"+ControlChars.CrLf
         ' keuzelijst
         dim i as integer
         for i=1 to 4
             cmbValeurs.Items.Add(new ListItem("choix"+i.ToString,i.ToString))
         next
         cmbValeurs.SelectedIndex=1
         ' enkelvoudige keuzelijst
         for i=1 to 7
             lstSimple.Items.Add(new ListItem("simple"+i.ToString,i.ToString))
         next
         lstSimple.SelectedIndex=0
         ' lijst met meervoudige selectie
         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
         ' verborgen veld
         lblSecret.Text="secret"
End Sub

Klik op de knoppen [lstRazSimple] en [lstMultiple]:

Sub btnRazSimple_Click(sender As Object, e As EventArgs)
    ' wisknop enkele lijst
    lstSimple.SelectedIndex=-1
End Sub

Sub razMultiple_Click(sender As Object, e As EventArgs)
    ' meervoudige lijst leegmaken
    lstMultiple.SelectedIndex=-1
End Sub

Klik op de knop [Envoyer]:

Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
    ' het infopaneel wordt zichtbaar gemaakt en het formulierpaneel wordt verborgen
  panelinfo.Visible=true
  panelform.visible=false
  ' de verzonden waarden worden opgehaald en opgeslagen in lstInfos
   ' keuzerondjes
  dim info as string="état marital : "+iif(rdoui.checked,"marié"," non marié")
  affiche(info)
   ' selectievakjes
  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)
   ' invoerveld
  affiche("champ de saisie : " + txtSaisie.Text.Trim)
   ' wachtwoord
  affiche("mot de passe : " + txtMdp.Text.Trim)
   ' invoervak
  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))
   ' keuzelijst
  affiche("éléments sélectionnés dans combo : "+selection(cmbValeurs))
   ' enkelvoudige lijst
  affiche("éléments sélectionnés dans liste simple : "+selection(lstSimple))
   ' meervoudige lijst
  affiche("éléments sélectionnés dans liste multiple : "+selection(lstMultiple))
   ' verborgen veld
  affiche ("Champ caché : " + lblSecret.Text)
End Sub

sub affiche(msg as String)
    ' geeft bericht weer in lstInfos
    lstInfos.Items.Add(msg)
end sub

       function selection(liste as ListControl) as string
           ' door de lijstitems bladeren
            ' om te bepalen welke zijn geselecteerd
           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

Tot slot de klik op de link [Retour vers le formulaire ]:

Sub LinkButton1_Click(sender As Object, e As EventArgs)
    ' het formulier wordt weergegeven en het infopaneel wordt verborgen
    panelform.visible=true
    panelinfo.visible=false
End Sub

7.14.2. Voorbeeld 2

We nemen hier een applicatie die al eerder is behandeld met standaardformulieren HTML. Met deze applicatie kunnen simulaties van belastingberekeningen worden uitgevoerd. Ze is gebaseerd op een klasse [impot], die we hier niet opnieuw zullen bespreken. Deze klasse heeft gegevens nodig die zij vindt in een gegevensbron OLEDB. Voor dit voorbeeld gebruiken we een bron ACCESS.

7.14.2.1. De structuur MVC van de applicatie

De structuur MVC van de applicatie is als volgt:

Image

De drie weergaven worden als containers opgenomen in de presentatiecode van de controller [main.aspx]. Deze applicatie heeft dus één enkele pagina: [main.aspx].

7.14.2.2. De weergaven van de webapplicatie

De weergave [formulaire] is het formulier voor het invoeren van gegevens waarmee de belasting van een gebruiker kan worden berekend:

Image

De gebruiker vult het formulier in:

Image

Hij gebruikt de knop [Envoyer] om de berekening van zijn belasting aan te vragen. Hij krijgt de volgende weergave [simulations] te zien:

Image

Via de bovenstaande link keert hij terug naar het formulier. Hij vindt het terug in de staat waarin hij het heeft ingevuld. Hij kan invoerfouten maken:

Image

Deze worden hem gemeld via het scherm [erreurs]:

Image

Via de bovenstaande link keert hij terug naar het formulier. Hij vindt het terug in de staat waarin hij het heeft ingevuld. Hij kan nieuwe simulaties uitvoeren:

Image

Hij krijgt dan de weergave [simulations] te zien met nog een simulatie:

Image

Tot slot, als de gegevensbron niet beschikbaar is, wordt dit aan de gebruiker gemeld in de weergave [erreurs]:

Image

7.14.2.3. De weergavecode van de applicatie

Ter herinnering: de pagina [main.aspx] bevat alle weergaven. Het is één enkel formulier met drie containers:

  • [panelform] voor de weergave [formulaire]
  • [panelerreurs] voor de weergave [erreurs]
  • [panelsimulations] voor de weergave [simulations]

We keren terug naar de scheiding tussen presentatiecode en controlecode in twee afzonderlijke bestanden. Het eerste bestand staat in [main.aspx] en het tweede in [main.aspx.vb]. De code van [main.aspx] is als volgt:


<%@ 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>

We hebben de drie containers afgebakend. Merk op dat ze allemaal binnen de tag <form runat="server"> staan. Dit is verplicht, want als we willen profiteren van de voordelen van servercomponenten, moeten deze in een dergelijke tag worden geplaatst. Het belangrijkste om te onthouden is dat we hier te maken hebben met één enkel formulier dat tussen de client en de webserver wordt uitgewisseld. We bevinden ons dus inderdaad in de configuratie die in dit hele hoofdstuk over servercomponenten wordt gebruikt. Laten we de componenten van elke container nader bekijken:

Container [panelform]:

naam
type
eigenschappen
rol
panelform
Paneel
EnableViewState=true
formulierweergave
rdOui
rdNon
RadioButton
EnableViewState=true
GroupName=rdmarie
keuzeknoppen
txtEnfants
TextBox
EnableViewState=true
aantal kinderen
txtSalaire
TextBox
EnableViewState=true
jaarsalaris
btnCalculer
Knop
 
knop [submit] op het formulier – start de belastingberekening
btnEffacer
Knop
 
knop [submit] van het formulier – wist het formulier

Container [panelerreurs]:

naam
type
eigenschappen
rol
panelerreurs
Paneel
EnableViewState=true
foutweergave
lnkForm1
LinkButton
EnableViewState=true
link naar het formulier
erreursHTML
Letterlijke waarde
 
HTML-code uit de foutenlijst

Container [panelsimulations]:

naam
type
eigenschappen
rol
panelsimulations
Paneel
EnableViewState=true
weergave simulaties
lnkForm2
LinkButton
EnableViewState=true
link naar het formulier
simulationsHTML
Letterlijke
 
code HTML uit de lijst met simulaties in een tabel HTML

7.14.2.4. De besturingscode van de applicatie

De controlecode van de applicatie is verdeeld over de bestanden [global.asax.vb] en [main.aspx.vb]. Het bestand [global.asax] is als volgt gedefinieerd:

<%@ Application src="Global.asax.vb" Inherits="Global" %>

Het bestand [global.asax.vb] ziet er als volgt uit:


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)
        ' er wordt een importobject aangemaakt
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
            ' het object wordt in de applicatie geplaatst
            Application("objImpot") = objImpot
            ' geen fout
            Application("erreur") = False
        Catch ex As Exception
            'er is een fout opgetreden; dit wordt in de applicatie genoteerd
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' begin van de sessie – er wordt een lege lijst met simulaties aangemaakt
        Session.Item("simulations") = New ArrayList
    End Sub
End Class

Wanneer de applicatie wordt gestart (eerste verzoek aan de applicatie), wordt de procedure [Application_Start] uitgevoerd. Deze procedure probeert een object van het type [impot] aan te maken, waarbij de gegevens uit een bron van het type OLEDB worden gehaald. De lezer wordt verzocht hoofdstuk 5 te raadplegen, waarin deze klasse is gedefinieerd, mocht hij dit vergeten zijn. Het aanmaken van het object [impot] kan mislukken als de gegevensbron niet beschikbaar is. In dat geval wordt de fout in de applicatie opgeslagen, zodat alle volgende verzoeken weten dat het object niet correct kon worden geïnitialiseerd. Als het aanmaken goed verloopt, wordt het aangemaakte object [impot] eveneens in de applicatie opgeslagen. Dit object zal worden gebruikt door alle verzoeken voor belastingberekeningen. Wanneer een klant zijn eerste verzoek indient, wordt er voor hem een sessie aangemaakt door de procedure [Application_Start]. Deze sessie is bedoeld om de verschillende simulaties van de belastingberekening die hij gaat uitvoeren op te slaan. Deze worden opgeslagen in een object [ArrayList] dat is gekoppeld aan de sessiesleutel "simulaties". Bij het starten van de sessie wordt deze sleutel gekoppeld aan een leeg object [ArrayList]. De informatie die de applicatie nodig heeft, wordt in het configuratiebestand [wenConfig] geplaatst:


<?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>

De sleutel [chaineConnexion] verwijst naar de verbindingsstring met de bron OLEDB. Het andere deel van de controlecode bevindt zich 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

    ' lokale variabelen

    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
        ' Allereerst wordt gekeken in welke status de applicatie zich bevindt
        If CType(Application("erreur"), Boolean) Then
            ' de applicatie kon niet worden geïnitialiseerd
            ' we geven het foutenscherm weer
            Dim erreurs As New ArrayList
            erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
            afficheErreurs(erreurs, "")
            Exit Sub
        End If
        ' geen fouten – bij het eerste verzoek wordt het formulier weergegeven
        If Not IsPostBack Then afficheFormulaire()
    End Sub

Ter herinnering: wanneer de procedure [Page_Load] wordt uitgevoerd op een POST-client, hebben alle componenten van het formulier een waarde: ofwel de door de client verzonden waarde, indien aanwezig, ofwel de vorige waarde van de component dankzij de procedure [VIEWSTATE]. In dit formulier hebben alle componenten de eigenschap [EnableViewState=true]. Voordat we beginnen met het verwerken van de aanvraag, controleren we of de applicatie correct is opgestart. Als dat niet het geval is, geven we de weergave [erreurs] weer met de procedure [afficheErreurs]. Als dit de eerste aanvraag is (IsPostBack=false), laten we de weergave [formulaire] weergeven met [afficheFormulaire].

De procedure die de weergave [erreurs] weergeeft, is als volgt:


    Private Sub afficheErreurs(ByRef erreurs As ArrayList, ByRef lien As String)
        ' de foutcontainer wordt weergegeven
        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
        ' de andere containers zijn verborgen
        panelform.Visible = False
        panelsimulations.Visible = False
    End Sub

De procedure heeft twee parameters:

  • een lijst met foutmeldingen in [erreurs]
  • een linktekst in [lien]

De te genereren code HTML voor de foutenlijst wordt in de letterlijke waarde [erreursHTML] geplaatst. De linktekst wordt op zijn beurt geplaatst in de eigenschap [Text] van het object [LinkButton] van de weergave.

De procedure die de weergave [formulaire] weergeeft, is als volgt:


    Private Sub afficheFormulaire()
        ' het formulier wordt weergegeven
        panelform.Visible = True
        ' de overige containers zijn verborgen
        panelerreurs.Visible = False
        panelsimulations.Visible = False
    End Sub

Deze procedure zorgt er alleen voor dat de container [panelform] zichtbaar wordt gemaakt. De componenten worden weergegeven met hun geposte of vorige waarde (VIEWSTATE).

Wanneer de gebruiker op de knop [Calculer] in de weergave [formulaire] klikt, wordt een overgang van POST naar [main.aspx] uitgevoerd. De procedure [Page_Load] wordt uitgevoerd, gevolgd door de procedure [btnCalculer_Click]:


    Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
        ' de geldigheid van de ingevoerde gegevens wordt gecontroleerd
        Dim erreurs As ArrayList = checkData()
        ' als er fouten zijn, wordt dit gemeld
        If erreurs.Count <> 0 Then
            ' de foutpagina wordt weergegeven
            afficheErreurs(erreurs, "Retour au formulaire")
            Exit Sub
        End If
        ' geen fouten – de belasting wordt berekend
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        rdOui.Checked, CType(txtEnfants.Text, Integer), CType(txtSalaire.Text, Long))
        ' het resultaat wordt toegevoegd aan de bestaande simulaties
        Dim simulation() As String = New String() {CType(IIf(rdOui.Checked, "oui", "non"), String), _
         txtEnfants.Text.Trim, txtSalaire.Text.Trim, impot.ToString}
        ' het resultaat wordt toegevoegd aan de bestaande simulaties
        Dim simulations As ArrayList = CType(Session.Item("simulations"), ArrayList)
        simulations.Add(simulation)
        ' de simulaties worden in de sessie en de context geplaatst
        Session.Item("simulations") = simulations
        ' de resultatenpagina wordt weergegeven
        afficheSimulations(simulations, "Retour au formulaire")
    End Sub

De procedure begint met het controleren van de geldigheid van de velden in het formulier met behulp van de procedure [checkData], die een lijst [ArrayList] met foutmeldingen retourneert. Als de lijst niet leeg is, wordt de weergave [erreurs] getoond en wordt de procedure beëindigd. Als de ingevoerde gegevens geldig zijn, wordt het belastingbedrag berekend met behulp van het object van het type [impot] dat bij het opstarten van de applicatie was opgeslagen. Deze nieuwe simulatie wordt toegevoegd aan de lijst met reeds uitgevoerde simulaties en opgeslagen in de sessie.

De functie [CheckData] controleert de geldigheid van de gegevens. Deze functie retourneert een lijst [ArrayList] met foutmeldingen, die leeg is als de gegevens geldig zijn:


    Private Function checkData() As ArrayList
        ' in eerste instantie geen fouten
        Dim erreurs As New ArrayList
        ' aantal kinderen
        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
        ' salaris
        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
        ' de foutenlijst wordt weergegeven
        Return erreurs
    End Function

Ten slotte wordt de weergave [simulations] weergegeven door de volgende procedure [afficheSimulations]:


    Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
        ' de simulatieweergave wordt weergegeven
        panelsimulations.Visible = True
        ' de andere containers zijn verborgen
        panelerreurs.Visible = False
        panelform.Visible = False
        ' inhoud van de simulatieweergave
        ' elke simulatie is een array met 4 string-elementen
        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

De procedure heeft twee parameters:

  • een lijst met simulaties in [simulations]
  • een linktekst in [lien]

De code HTML die voor de lijst met simulaties moet worden gegenereerd, wordt in de letterlijke waarde [simulationsHTML] geplaatst. De linktekst wordt op zijn beurt in de eigenschap [Text] van het object [LinkButton] van de weergave geplaatst.

Wanneer de gebruiker op de knop [Effacer] in de weergave [formulaire] klikt, wordt de procedure [btnEffacer_click] uitgevoerd (altijd na [Page_Load]):


    Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
        ' toont het lege formulier
        razForm()
        afficheFormulaire()
    End Sub

    Private Sub razForm()
        ' wist het formulier
        rdOui.Checked = False
        rdNon.Checked = True
        txtEnfants.Text = ""
        txtSalaire.Text = ""
    End Sub

De bovenstaande code is eenvoudig genoeg om geen uitleg te behoeven. Nu moeten we nog de klik op de links in de weergaven [erreurs] en [simulations] afhandelen:


    Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
        ' geeft het formulier weer
        afficheFormulaire()
    End Sub

    Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
        ' toont het formulier
        afficheFormulaire()
    End Sub

Beide procedures zorgen er alleen voor dat de weergave [formulaire] wordt weergegeven. We weten dat de velden hierin een waarde krijgen die ofwel de voor hen verzonden waarde is, ofwel hun vorige waarde. Aangezien de POST van de klant in dit geval geen waarden voor de velden van het formulier verzendt, krijgen deze hun vorige waarde terug. Het formulier wordt dus weergegeven met de door de gebruiker ingevoerde waarden. We herinneren ons dat we in de versie zonder servercomponenten dit herstel zelf hadden uitgevoerd.

7.14.2.5. Tests

Alle bestanden die nodig zijn voor de applicatie worden in een map <application-path> geplaatst:
De map [bin] bevat de DLL met de klassen [impot], [impotsData] en [impotsOLEDB] die nodig zijn voor de toepassing:

De lezer kan, indien gewenst, hoofdstuk 5 nog eens doorlezen, waarin wordt uitgelegd hoe het bovenstaande bestand [impot.dll] moet worden aangemaakt. Daarna wordt de Cassini-server gestart met de parameters (<application-path>,/impots5). De URL [http://impots5/main.aspx] wordt opgevraagd met een browser:

Image

Als we het bestand ACCESS [impots.mdb] hernoemen naar [impots1.mdb], krijgen we de volgende pagina te zien:

Image

7.14.3. Voorbeeld 3

Aan de hand van deze twee voorbeelden hebben we laten zien dat het mogelijk is om webapplicaties te bouwen die voldoen aan de MVC-architectuur met servercomponenten. Het laatste voorbeeld laat zien dat de oplossing met servercomponenten eenvoudiger is dan de oplossing met standaard HTML-tags. Onze twee voorbeelden bestonden uit slechts één pagina met meerdere weergaven binnen dezelfde pagina. Een MVC-architectuur met meerdere ASP-serverformulieren is mogelijk, zolang het geen probleem oplevert dat deze de formulierwaarden naar zichzelf verzenden. Dit is heel vaak het geval bij applicaties met een menu. Laten we het volgende voorbeeld nemen:

Image

We hebben op één pagina links verzameld naar de applicaties die we tot nu toe hebben geschreven. Dit type applicatie leent zich goed voor een MVC-architectuur. Er is alleen niet langer één, maar meerdere controllers.

Image

De controller [main.aspx] fungeert als hoofdcontroller. Deze wordt aangeroepen door de links op de startpagina van de applicatie. Deze controller voert eerst de bewerkingen uit die voor alle mogelijke acties gelden en laat vervolgens de specifieke actie uitvoeren die bij de gebruikte link hoort. Daarna draagt hij het stokje over aan een van de secundaire controllers, namelijk degene die verantwoordelijk is voor de uitvoering van de actie. Vanaf dat moment vindt de communicatie plaats tussen de client en deze specifieke controller. De hoofdcontroller [main.aspx] komt niet meer in beeld. We bevinden ons dus niet langer in het MVC-kader met één enkele controller die alle verzoeken filtert. Elk van de bovengenoemde controllers kan meerdere weergaven presenteren met behulp van het containermechanisme binnen één enkele pagina, zoals we hebben uitgelegd.

Het feit dat er niet langer één enkele controller is die bepaalt welke weergaven naar de klant worden verzonden, brengt nadelen met zich mee. Laten we het voorbeeld van foutafhandeling nemen. Elk van de acties die door de applicatie worden aangeboden, kan een foutweergave moeten tonen. Elke controller [applix.aspx] zal zijn eigen weergave [erreurs] hebben, omdat deze weergave simpelweg een specifieke container is van de controllerpagina. Er is geen manier om één enkele weergave [erreurs] te hebben die door alle afzonderlijke applicaties zou worden gebruikt. Een dergelijke weergave bevat namelijk doorgaans een terugkeerkoppeling naar het foutieve formulier, en dit formulier moet worden hersteld in de toestand waarin het was gevalideerd, zodat de gebruiker zijn fouten kan corrigeren. Dit herstel gebeurt via het mechanisme van de [VIEWSTATE], dat niet werkt tussen verschillende controllers. Als de applicaties door verschillende personen worden ontwikkeld, bestaat het risico dat foutpagina’s er anders uitzien, afhankelijk van de door de gebruiker gekozen actie, wat afbreuk doet aan de uniformiteit van de totale applicatie. We zullen straks zien dat ASP.NET een oplossing biedt voor dit specifieke probleem van het delen van weergaven. Dit kan het onderwerp zijn van een nieuwe servercomponent die we zelf bouwen. Het volstaat om deze component in de verschillende applicaties te gebruiken om de uniformiteit van de totale applicatie te waarborgen. Moeilijker te beheren is het probleem van de volgorde van de acties. Wanneer alle verzoeken via één enkele controller lopen, kan deze controleren of de gevraagde actie compatibel is met de vorige. Deze controlecode bevindt zich op één enkele plaats. Hier zal deze over de verschillende controllers moeten worden verdeeld, wat het onderhoud van de totale applicatie bemoeilijkt.

Laten we terugkeren naar onze bovenstaande applicatie. De startpagina hiervan is een klassieke HTML-pagina:

<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>

Deze startpagina bestaat uit drie frames, namelijk banner, contents en main:

De pagina [bandeau.htm], die in het frame [banner] is geplaatst, ziet er als volgt uit:

Image

De code HTML is als volgt:


<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>

De pagina [options.htm] is geplaatst in de banner [contents]. Het is een verzameling links:


<html>
    <head>
        <meta http-equiv="pragma" content="no-cache" />
        <title>options</title>
    </head>
    <body bgcolor="Gold">
        <ul>
            <li>
                <a href="main.aspx?action=label" 
                 target="main">Label</a>
            <li>
                <a href="main.aspx?action=button" 
                 target="main">Button</a>
            <li>
                <a href="main.aspx?action=textbox1" 
                 target="main">TextBox-1</a></li>
            <li>
                <a href="main.aspx?action=textbox2" 
                 target="main">TextBox-2</a></li>
            <li>
                <a href="main.aspx?action=dropdownlist" 
                 target="main">DropDownList</a></li>
            <li>
                <a href="main.aspx?action=listbox" 
                 target="main">ListBox</a></li>
            <li>
                <a href="main.aspx?action=casesacocher" 
                  target="main">CheckBox</a> et<br>
                RadioButton</li>
            <li>
                <a href="main.aspx?action=listecasesacocher" 
                  target="main">CheckBoxList</a> et<br>
                RadioButtonList</a></li>
            <li>
                <a href="main.aspx?action=panel" 
                  target="main">Panel</a></li>
        </ul>
    </body>
</html>

De verschillende links verwijzen allemaal naar de hoofdcontroller [main.aspx], waarbij de parameter [action] aangeeft welke actie moet worden uitgevoerd. Er wordt gevraagd om de bestemming van de links weer te geven in het frame [main] (target="main").

De eerste pagina die in het frame [main] wordt weergegeven, is [main.htm]:


<html>
    <head>
        <title>main</title>
    </head>
    <body>
        <P>Choisissez une option pour tester découvrir un type de composant serveur...</P>
    </body>
</html>

De hoofdcontroller [main.aspx, main.aspx.vb] is als volgt:

[main.aspx]

<%@ page src="main.aspx.vb" inherits="main" autoeventwireup="false" %>

[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) Met de handles MyBase.Load

' we halen de uit te voeren actie op

Dim actie As String

If Request.QueryString("action") Is Nothing Then

action = "label"

Else

actie = Request.QueryString("actie").ToString.ToLower

End If

' de actie wordt uitgevoerd

Select Case actie

Case "label"

Server.Transfer("form2.aspx")

Case "button"

Server.Transfer("form3.aspx")

Case "textbox1"

Server.Transfer("form4.aspx")

Case "textbox2"

Server.Transfer("form5.aspx")

Case "dropdownlist"

Server.Transfer("form6.aspx")

Vak "listbox"

Server.Transfer("form7.aspx")

Vakje "selectievakje"

Server.Transfer("form8.aspx")

Case "selectielijst met selectievakjes"

Server.Transfer("form8b.aspx")

Case "paneel"

Server.Transfer("form9.aspx")

Case Else

Server.Transfer("form2.aspx")

End Select

End Sub

End Class

Onze controller is eenvoudig. Afhankelijk van de waarde van de parameter [action] stuurt hij de verwerking van het verzoek door naar de juiste pagina. Hij biedt geen meerwaarde ten opzichte van een pagina HTML met links. Toch zou het volstaan om een authenticatiepagina toe te voegen om het nut ervan te zien. Als de gebruiker zich zou moeten authenticeren (gebruikersnaam, wachtwoord) om toegang te krijgen tot de applicaties, zou de controller [main.aspx] een geschikte plek zijn om te controleren of deze authenticatie heeft plaatsgevonden.