Skip to content

4. De basisprincipes van de ontwikkeling ASP.NET

4.1. Het begrip webapplicatie ASP.NET

4.1.1. Inleiding

Een webapplicatie is een applicatie die verschillende documenten (HTML, .NET-code, afbeeldingen, geluiden, ...) samenbrengt. Deze documenten moeten zich in dezelfde hoofdmap bevinden, die de hoofdmap van de webapplicatie wordt genoemd. Aan deze hoofdmap is een virtueel pad van de webserver gekoppeld. We hebben het begrip ‘virtuele map’ al leren kennen bij de Cassini-webserver. Dit begrip bestaat ook voor de IIS-webserver. Een belangrijk verschil tussen beide servers is dat IIS op een bepaald moment een willekeurig aantal virtuele mappen kan hebben, terwijl de Cassini-webserver er slechts één heeft, namelijk degene die bij de start is opgegeven. Dit betekent dat de IIS-server meerdere webapplicaties tegelijkertijd kan bedienen, terwijl de Cassini-server er slechts één tegelijk bedient. In de voorgaande voorbeelden werd de Cassini-server altijd gestart met de instellingen (<webroot>,/aspnet), die de virtuele map /aspnet koppelden aan de fysieke map <webroot>. De webserver bediende dus altijd dezelfde webapplicatie. Dit weerhield ons er echter niet van om verschillende, onafhankelijke pagina’s te schrijven en te testen binnen deze ene webapplicatie. Elke webapplicatie heeft zijn eigen bronnen, die zich onder de fysieke root <webroot> bevinden:

  • een map [bin] waarin voorgecompileerde klassen kunnen worden geplaatst
  • een bestand [global.asax] waarmee de webapplicatie in haar geheel en de uitvoeringsomgeving van elke gebruiker kan worden geïnitialiseerd
  • een bestand [web.config] waarmee de werking van de applicatie kan worden geconfigureerd
  • een [default.aspx]-bestand dat fungeert als toegangspoort tot de applicatie
  • ...

Zodra een applicatie gebruikmaakt van een van deze drie bronnen, heeft deze een eigen fysiek en virtueel pad nodig. Er is namelijk geen enkele reden waarom twee verschillende webapplicaties op dezelfde manier zouden moeten worden geconfigureerd. Onze voorgaande voorbeelden konden allemaal in dezelfde applicatie (<webroot>,/aspnet) worden geplaatst omdat ze geen gebruik maakten van de bovengenoemde bronnen.

Laten we terugkeren naar de architectuur MVC die aan het begin van dit hoofdstuk werd aanbevolen voor de ontwikkeling van een webapplicatie:

Image

De webapplicatie bestaat uit klassebestanden (controller, businessklassen, klassen voor gegevenstoegang) en presentatiebestanden (documenten HTML, afbeeldingen, geluiden, stylesheets, enz.). Al deze bestanden worden onder één enkele hoofdmap geplaatst, die we soms <application-path> zullen noemen. Deze hoofdmap wordt gekoppeld aan een virtueel pad <application-vpath>. De koppeling tussen dit virtuele pad en het fysieke pad gebeurt via de configuratie van de webserver. We hebben gezien dat voor de Cassini-server deze koppeling plaatsvindt bij het opstarten van de server. In een DOS-venster zou men Cassini bijvoorbeeld starten met:

webserver.exe /port:80 /path:<application-path> /vpath:<application-vpath>

In de map <application-path> vinden we, afhankelijk van onze behoeften:

  • de map [bin] om daar voorgecompileerde klassen (dll's) in te plaatsen
  • het bestand [global.asax] wanneer we initialisaties moeten uitvoeren, hetzij bij het opstarten van de applicatie, hetzij bij het opstarten van een gebruikerssessie
  • het bestand [web.config] wanneer we de applicatie moeten configureren
  • het bestand [default.aspx] wanneer we een standaardpagina in de applicatie nodig hebben

Om dit concept van een webapplicatie te respecteren, worden de komende voorbeelden allemaal in een map <application-path> geplaatst die specifiek is voor de applicatie, waaraan een virtuele map <application-vpath> wordt gekoppeld, waarbij de Cassini-server zo wordt gestart dat deze twee parameters aan elkaar worden gekoppeld.

4.1.2. Een webapplicatie configureren

Als <application-path> de hoofdmap is van een applicatie ASP.NET, kunnen we het bestand <application-path>\web.config gebruiken om deze te configureren. Dit bestand heeft de indeling XML. Hier volgt een voorbeeld:

<?xml version="1.0" encoding="UTF-8" ?>

<configuration>
  <appSettings>
    <add key="nom" value="tintin"/>
    <add key="age" value="27"/>
  </appSettings>   
</configuration>

Houd er rekening mee dat de tags XML hoofdlettergevoelig zijn. Alle configuratiegegevens moeten tussen de tags <configuration> en </configuration> staan. Er zijn talrijke configuratiesecties beschikbaar. We bespreken er hier slechts één, namelijk de sectie <appSettings>, waarmee gegevens kunnen worden geïnitialiseerd met de tag <add>. De syntaxis van deze tag is als volgt:

<add key="identificateur" value="valeur"/>

Wanneer de webserver een applicatie start, controleert hij of er in <application-path> een bestand met de naam web.config aanwezig is. Zo ja, dan leest hij dit bestand en slaat hij de informatie op in een object van het type [ConfigurationSettings], dat beschikbaar is voor alle pagina’s van de applicatie zolang deze actief is. De klasse [ConfigurationSettings] heeft een statische methode [AppSettings]:

Image

Om de waarde van een sleutel C uit het configuratiebestand op te halen, schrijft men ConfigurationSettings.AppSettings("C"). Men krijgt een tekenreeks. Om het voorgaande configuratiebestand te gebruiken, maken we een pagina [default.aspx] aan. De code VB uit het bestand [default.aspx.vb] ziet er als volgt uit:


Imports System.Configuration

Public Class _default
    Inherits System.Web.UI.Page

    Protected nom As String
    Protected age As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        'de configuratiegegevens worden opgehaald
        nom = ConfigurationSettings.AppSettings("nom")
        age = ConfigurationSettings.AppSettings("age")
    End Sub

End Class

We zien dat bij het laden van de pagina de waarden van de configuratieparameters [nom] en [age] worden opgehaald. Deze worden weergegeven door de weergavecode van [default.aspx]:


<%@ Page src="default.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="_default" %>
<html>
    <head>
        <title>Configuration</title>
    </head>
    <body>
        Nom :
        <% =nom %><br/>
        Age :
        <% =age %><br/>
    </body>
</html>

Voor de test plaatsen we de bestanden [web.config], [default.aspx] en [default.aspx.vb] in dezelfde map:

D:\data\devel\aspnet\poly\chap2\config1>dir
30/03/2004  15:06                  418 default.aspx.vb
30/03/2004  14:57                  236 default.aspx
30/03/2004  14:53                  186 web.config

Stel dat <application-path> de map is waarin de drie bestanden van de applicatie zich bevinden. De Cassini-server wordt gestart met de parameters (<application-path>,/aspnet/config1). We vragen om de bestanden URL en [http://localhost/aspnet/config1]. Aangezien [config1] een map is, zoekt de webserver daarin naar een bestand met de naam [default.aspx] en geeft dit weer als het wordt gevonden. In dit geval wordt het gevonden:

Image

4.1.3. Toepassing, Sessie, Context

4.1.3.1. Het bestand global.asax

De code in het bestand [global.asax] wordt altijd uitgevoerd voordat de pagina die door het huidige verzoek wordt opgevraagd, wordt geladen. Het moet zich in de hoofdmap <application-path> van de applicatie bevinden. Indien aanwezig, wordt het bestand [global.asax] op verschillende momenten door de webserver gebruikt:

  1. wanneer de webapplicatie wordt gestart of afgesloten
  2. wanneer een gebruikerssessie begint of eindigt
  3. wanneer een gebruikersverzoek begint

Net als bij .aspx-pagina’s kan het bestand [global.asax] op verschillende manieren worden geschreven, met name door de VB-code te scheiden in een controllerklasse en presentatiecode. Dit is de standaardkeuze van Visual Studio en we zullen hier hetzelfde doen. Normaal gesproken hoeft er geen presentatie te worden verzorgd, aangezien deze rol is weggelegd voor de .aspx-pagina’s. De inhoud van het bestand [global.asax] blijft dan beperkt tot een instructie die verwijst naar het bestand met de controllercode:


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

Merk op dat de instructie niet langer [Page] is, maar [Application]. De bijbehorende controllercode [global.asax.vb], gegenereerd door Visual Studio, is als volgt:


Imports System
Imports System.Web
Imports System.Web.SessionState

Public Class Global
    Inherits System.Web.HttpApplication

  Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' Wordt geactiveerd wanneer de applicatie wordt gestart
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' Wordt geactiveerd wanneer de sessie wordt gestart
    End Sub

    Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
        ' Wordt geactiveerd aan het begin van elk verzoek
    End Sub

    Sub Application_AuthenticateRequest(ByVal sender As Object, ByVal e As EventArgs)
        ' Wordt geactiveerd bij een poging tot authenticatie van de gebruiker
    End Sub

    Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
        ' Wordt geactiveerd wanneer er een fout optreedt
    End Sub

    Sub Session_End(ByVal sender As Object, ByVal e As EventArgs)
        ' Wordt geactiveerd wanneer de sessie wordt beëindigd
    End Sub

    Sub Application_End(ByVal sender As Object, ByVal e As EventArgs)
        ' Wordt geactiveerd wanneer de applicatie wordt afgesloten
    End Sub

End Class

Merk op dat de controllerklasse is afgeleid van de klasse [HttpApplication]. Tijdens de levenscyclus van een applicatie vinden er verschillende belangrijke gebeurtenissen plaats. Deze worden afgehandeld door procedures waarvan het raamwerk hierboven is weergegeven.

  • [Application_Start]: ter herinnering: een webapplicatie is „opgesloten” in een virtueel pad. De applicatie start zodra een pagina in dit virtuele pad door een client wordt opgevraagd. De procedure [Application_Start] wordt dan uitgevoerd. Dit gebeurt slechts één keer. In deze procedure voeren we alle initialisaties uit die nodig zijn voor de applicatie, zoals het aanmaken van objecten waarvan de levensduur gelijk is aan die van de applicatie.
  • [Application-End]: wordt uitgevoerd wanneer de applicatie is beëindigd. Aan elke applicatie is een inactiviteitstijd gekoppeld, die kan worden geconfigureerd in [web.config], waarna de applicatie als beëindigd wordt beschouwd. Het is dus de webserver die deze beslissing neemt op basis van de instellingen van de applicatie. De inactiviteitstijd van een applicatie wordt gedefinieerd als de tijd gedurende welke geen enkele client een verzoek heeft gedaan voor een bron van de applicatie.
  • [Session-Start]/[Session_End]: Aan elke client is een sessie gekoppeld, tenzij de applicatie is geconfigureerd als een applicatie zonder sessie. Een client is niet hetzelfde als een gebruiker achter zijn scherm. Als deze gebruiker twee browsers heeft geopend om de applicatie te raadplegen, telt hij als twee clients. Een klant wordt gekenmerkt door een sessietoken dat hij aan elk van zijn verzoeken moet toevoegen. Dit sessietoken is een reeks tekens die willekeurig door de webserver wordt gegenereerd en uniek is. Twee klanten kunnen niet hetzelfde sessietoken hebben. Dit token volgt de klant op de volgende manier:
    • de klant die zijn eerste verzoek indient, stuurt geen sessietoken mee. De webserver herkent dit en wijst hem er een toe. Dit is het begin van de sessie en de procedure [Session_Start] wordt uitgevoerd. Dit gebeurt slechts één keer.
    • De klant doet zijn volgende verzoeken door het token te verzenden waarmee hij zich identificeert. Hierdoor kan de webserver informatie opzoeken die aan dit token is gekoppeld. Dit maakt het mogelijk om de verschillende verzoeken van de klant te volgen.
    • De applicatie kan een formulier voor het beëindigen van de sessie aan een klant ter beschikking stellen. In dat geval is het de klant zelf die verzoekt om zijn sessie te beëindigen. De procedure [Session_End] wordt uitgevoerd. Dit gebeurt slechts één keer.
    • Het is mogelijk dat de klant nooit zelf om beëindiging van zijn sessie vraagt. In dat geval wordt de sessie door de webserver beëindigd na een bepaalde periode van inactiviteit, die eveneens via [web.config] kan worden geconfigureerd. De procedure [Session_End] wordt dan uitgevoerd.
  • [Application_BeginRequest]: deze procedure wordt uitgevoerd zodra er een nieuw verzoek binnenkomt. Ze wordt dus bij elk verzoek van een willekeurige klant uitgevoerd. Dit is een goed moment om het verzoek te controleren voordat het wordt doorgestuurd naar de gevraagde pagina. Er kan zelfs worden besloten om het verzoek om te leiden naar een andere pagina.
  • [Application_Error]: wordt uitgevoerd telkens wanneer er een fout optreedt die niet expliciet wordt afgehandeld door de code van de controller [global.asax.vb]. Hier kan het verzoek van de klant worden omgeleid naar een pagina waarop de oorzaak van de fout wordt uitgelegd.

Als geen van deze gebeurtenissen hoeft te worden afgehandeld, kan het bestand [global.asax] worden genegeerd. Dit is wat er in de eerste voorbeelden van dit hoofdstuk is gedaan.

4.1.3.2. Voorbeeld 1

Laten we een applicatie ontwikkelen om de drie momenten – het opstarten van de applicatie, het starten van de sessie en het indienen van een verzoek door de klant – beter te begrijpen. Het bestand [global.asax] ziet er als volgt uit:

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

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


Imports System
Imports System.Web
Imports System.Web.SessionState

Public Class global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' Wordt geactiveerd wanneer de applicatie wordt gestart
        ' de tijd wordt genoteerd
        Dim startApplication As String = Date.Now.ToString("T")
        ' wordt opgeslagen in de context van de applicatie
        Application.Item("startApplication") = startApplication
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' Wordt geactiveerd wanneer de sessie wordt gestart
        ' de tijd wordt genoteerd
        Dim startSession As String = Date.Now.ToString("T")
        ' wordt in de sessie geplaatst
        Session.Item("startSession") = startSession
    End Sub

    Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
        ' de tijd wordt genoteerd
        Dim startRequest As String = Date.Now.ToString("T")
        ' de tijd wordt in de sessie opgeslagen
        Context.Items("startRequest") = startRequest
    End Sub
End Class

De belangrijkste punten van de code zijn de volgende:

  • de webserver stelt een aantal objecten beschikbaar voor de klasse [HttpApplication] van [global.asax.vb]:
    • Toepassing van het type [HttpApplicationState] – vertegenwoordigt de webtoepassing – biedt toegang tot een objectenrepertorium [Application.Item] dat toegankelijk is voor alle clients van de toepassing – maakt het delen van informatie tussen verschillende clients mogelijk – gelijktijdige toegang van meerdere clients tot dezelfde gegevens voor lezen/schrijven vereist synchronisatie van de clients.
    • Sessie van het type [HttpSessionState] – vertegenwoordigt een specifieke klant – biedt toegang tot een objectenregister [Session.Item] dat toegankelijk is voor alle verzoeken van deze klant – maakt het mogelijk om informatie over een klant op te slaan die bij zijn volgende verzoeken kan worden opgehaald.
    • Verzoek van het type [HttpRequest] – vertegenwoordigt het huidige verzoek HTTP van de klant
    • Respons van het type [HttpResponse] – vertegenwoordigt het antwoord HTTP dat momenteel door de server aan de klant wordt opgebouwd
    • Server van het type [HttpServerUtility] – biedt hulpprogramma’s, met name om de verzoek naar een andere pagina door te sturen dan de oorspronkelijk beoogde.
    • Context van het type [HttpContext] – dit object wordt bij elk nieuw verzoek opnieuw aangemaakt, maar wordt gedeeld door alle pagina’s die bij de verwerking van het verzoek betrokken zijn – maakt het mogelijk om tijdens de verwerking van een verzoek informatie van pagina naar pagina door te geven via het Items-woordenboek.
  • De procedure [Application_Start] registreert het begin van de applicatie in een variabele die is opgeslagen in een woordenboek dat op applicatieniveau toegankelijk is
  • de procedure [Session_Start] slaat het begin van de sessie op in een variabele die is opgeslagen in een woordenboek dat op sessieniveau toegankelijk is
  • de procedure [Application_BeginRequest] registreert het begin van de verzoek in een variabele die is opgeslagen in een woordenboek dat op verzoekniveau toegankelijk is (c.a.d is gedurende de gehele verwerking beschikbaar, maar gaat aan het einde daarvan verloren)

De doelpagina is de volgende pagina [main.aspx]:


<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<html>
    <head>
        <title>global.asax</title>
    </head>
    <body>
        jeton de session  :
        <% =jeton %><br/>
        début Application  :
        <% =startApplication %><br/>
        début Session  :
        <% =startSession %><br/>
        début Requête  :
        <% =startRequest %><br/>        
    </body>
</html>

Op deze overzichtspagina worden waarden weergegeven die zijn berekend door de bijbehorende controller [main.aspx.vb]:

Public Class main
    Inherits System.Web.UI.Page

    Protected startApplication As String
    Protected startSession As String
    Protected startRequest As String
    Protected jeton as String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
         ' de gegevens van de applicatie en de sessie worden opgehaald
        jeton=Session.SessionId
        startApplication = Application.Item("startApplication").ToString
        startSession = Session.Item("startSession").ToString
        startRequest = Context.Items("startRequest").ToString
    End Sub

End Class

De controller haalt alleen de drie gegevens op die door [global.asax.vb] respectievelijk in de applicatie, de sessie en de context zijn geplaatst.

We testen de applicatie als volgt:

  1. de bestanden worden verzameld in één map <application-path>

Image

  1. de Cassini-server wordt gestart met de parameters (<application-path>,/aspnet/globalasax1)
  2. een eerste client vraagt de URL [http://localhost/aspnet/globalasax1/main.aspx] op en krijgt het volgende resultaat:

Image

  1. dezelfde client doet een nieuw verzoek (optie ‘Reload’ van de browser):

Image

We zien dat alleen het tijdstip van de verzoek is veranderd. Dit toont twee dingen aan:

  • de procedures [Application_Start] en [Session_Start] van [global.asax] zijn bij het tweede verzoek niet uitgevoerd.
  • de objecten [Application] en [Session], waarin de starttijden van de applicatie en de sessie waren opgeslagen, zijn nog steeds beschikbaar voor de tweede aanvraag.
  1. We starten een tweede browser om een tweede client aan te maken en vragen opnieuw dezelfde URL op:

Image

Deze keer zien we dat de sessietijd is veranderd. De tweede browser, hoewel op dezelfde machine, werd beschouwd als een tweede client en er werd een nieuwe sessie voor aangemaakt. We kunnen vaststellen dat de twee clients niet hetzelfde sessietoken hebben. De starttijd van de applicatie is niet veranderd, wat betekent dat:

  • de procedure [Application_Start] van [global.asax.vb] niet is uitgevoerd
  • het object [Application], waarin het starttijdstip van de applicatie is opgeslagen, toegankelijk is voor de tweede klant. Het is dus in dit object dat de informatie moet worden opgeslagen die door de verschillende clients van de applicatie moet worden gedeeld, terwijl het object [Session] dient voor het opslaan van informatie die door de verzoeken van één en dezelfde client moet worden gedeeld.

4.1.3.3. Een overzicht

Met wat we tot nu toe hebben geleerd, kunnen we een eerste schematisch overzicht maken van de werking van een webserver en de webapplicaties die deze bedient:

Image

Het bovenstaande schema toont een server die twee applicaties bedient, aangeduid met A en B, elk met twee clients. Een webserver kan meerdere webapplicaties tegelijkertijd bedienen. Deze zijn volledig onafhankelijk van elkaar. We zullen ons concentreren op applicatie A. De verwerking van een verzoek van client-1A aan applicatie A verloopt als volgt:

  • de client 1A vraagt de webserver om een bron die tot het domein van applicatie A behoort. Dit betekent dat hij een URL aanvraagt in de vorm [http://machine:port/VA/ressource], waarbij VA het virtuele pad van applicatie A is.
  • Als de webserver detecteert dat dit het eerste verzoek om een bron van applicatie A is, activeert hij de gebeurtenis [Application_Start] uit het bestand [global.asax] van applicatie A. Er wordt een object [ApplicationA] van het type [HttpApplicationState] aangemaakt. De verschillende codes van de applicatie zullen in dit object gegevens opslaan met het bereik [Application], c.a.d, namelijk gegevens die betrekking hebben op alle gebruikers. Het object [ApplicationA] blijft bestaan totdat de webserver applicatie A afsluit.
  • Als de webserver bovendien detecteert dat hij te maken heeft met een nieuwe client van applicatie A, zal hij de gebeurtenis [Session_Start] uit het bestand [global.asax] van applicatie A activeren. Er wordt een object [Session-1A] van het type [HttpSessionState] aangemaakt. Met dit object kan applicatie A objecten met het bereik [Session] en c.a.d opslaan, objecten die bij een specifieke klant horen. Het object [Session-1A] blijft bestaan zolang de klant 1A verzoeken indient. Het maakt het mogelijk om deze klant te volgen. De webserver detecteert in twee gevallen dat hij te maken heeft met een nieuwe klant:
    • de client heeft geen sessietoken in de HTTP-headers van zijn verzoek verzonden
    • de client heeft een sessietoken verzonden dat niet bestaat (storing bij de client of poging tot hacking) of dat niet meer bestaat. Een sessietoken verloopt namelijk na een bepaalde periode van inactiviteit van de client (standaard 20 minuten bij IIS). Deze tijdsduur is instelbaar.
  • In alle gevallen zal de webserver de gebeurtenis [Application_BeginRequest] uit het bestand [global.asax] activeren. Deze gebeurtenis start de verwerking van een verzoek van de klant. Het komt vaak voor dat deze gebeurtenis niet wordt verwerkt en dat het stokje wordt doorgegeven aan de door de klant opgevraagde pagina, die het verzoek vervolgens zal verwerken. Men kan deze gebeurtenis ook gebruiken om het verzoek te analyseren, te verwerken en te beslissen welke pagina als antwoord moet worden verzonden. We zullen deze techniek gebruiken om een applicatie te implementeren die voldoet aan de MVC-architectuur waarover we het eerder hebben gehad.
  • Zodra het filter van [global.asax] is doorlopen, wordt het verzoek van de klant doorgestuurd naar een .aspx-pagina die het verzoek zal verwerken. We zullen later zien dat het mogelijk is om het verzoek door een filter van meerdere pagina’s te leiden. De laatste pagina zal verantwoordelijk zijn voor het verzenden van het antwoord naar de klant. De pagina’s kunnen aan het oorspronkelijke verzoek van de klant informatie toevoegen die ze zelf hebben berekend. Ze kunnen deze informatie opslaan in de verzameling Context.Items. Alle pagina’s die betrokken zijn bij de verwerking van het verzoek van een klant hebben namelijk toegang tot deze gegevensverzameling.
  • De code van de verschillende pagina’s heeft toegang tot de gegevensreservoirs, namelijk de objecten [ApplicationA], [Session-1A], ... Houd er rekening mee dat de webserver voor applicatie A meerdere klanten tegelijkertijd verwerkt. Al deze klanten hebben toegang tot het object [Application A]. Als zij gegevens in dit object moeten wijzigen, moet er synchronisatie tussen de clients plaatsvinden. Elke client XA heeft bovendien toegang tot de gegevenspool [Session-XA]. Aangezien deze voor hem is gereserveerd, is er hier geen synchronisatie nodig.
  • De webserver bedient meerdere webapplicaties tegelijkertijd. Er is geen interferentie tussen de clients van deze verschillende applicaties.

Uit deze uitleg kunnen we de volgende punten onthouden:

  • Op een bepaald moment bedient een webserver meerdere clients tegelijkertijd. Dit betekent dat hij niet wacht tot een verzoek is afgerond om een ander te verwerken. Op tijdstip T zijn er dus meerdere verzoeken in behandeling die afkomstig zijn van verschillende clients voor verschillende applicaties. De verwerkingscodes die tegelijkertijd binnen de webserver worden uitgevoerd, worden soms uitvoeringsthreads genoemd.
  • De uitvoeringsthreads van klanten van verschillende webapplicaties interfereren niet met elkaar. Er is sprake van afscherming.
  • De uitvoeringsthreads van clients van dezelfde applicatie moeten mogelijk gegevens delen:
    • de uitvoeringsthreads van verzoeken van twee verschillende klanten (niet hetzelfde sessietoken) kunnen gegevens delen via het object [Application].
    • De uitvoeringsthreads van opeenvolgende verzoeken van dezelfde client kunnen gegevens delen via het object [Session].
    • De uitvoeringsthreads van opeenvolgende pagina’s die hetzelfde verzoek van een bepaalde klant verwerken, kunnen gegevens delen via het object [Context].

4.1.3.4. Voorbeeld 2

Laten we een nieuw voorbeeld ontwikkelen om het zojuist besprokene te verduidelijken. We verzamelen de volgende bestanden in dezelfde map:

[global.asax]

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

[global.asax.vb]


Imports System
Imports System.Web
Imports System.Web.SessionState

Public Class global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' Wordt geactiveerd wanneer de applicatie wordt gestart
        ' de klantenteller initialiseren
        Application.Item("nbRequêtes") = 0
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' Wordt geactiveerd wanneer de sessie wordt gestart
        ' initialiseert de teller voor verzoeken
        Session.Item("nbRequêtes") = 0
    End Sub
End Class

Het principe van de applicatie is om het totale aantal verzoeken aan de applicatie en het aantal verzoeken per klant te tellen. Wanneer de applicatie [Application_Start] wordt gestart, wordt de teller voor het aantal verzoeken aan de applicatie op 0 gezet. Deze teller wordt in het bereik [Application] geplaatst, omdat hij door alle klanten moet worden opgehoogd. Wanneer een klant zich voor het eerst aanmeldt bij [Session_Start], wordt de teller voor het aantal verzoeken van die klant op 0 gezet. Deze teller wordt in het bereik [Session] geplaatst, omdat deze slechts betrekking heeft op één bepaalde klant.

Zodra [global.asax] is uitgevoerd, wordt het volgende bestand [main.aspx] uitgevoerd:


<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<html>
    <head>
        <title>application-session</title>
    </head>
    <body>
        jeton de session :
        <% =jeton %>
        <br />
        requêtes Application :
        <% =nbRequêtesApplication %>
        <br />
        requêtes Client :
        <% =nbRequêtesClient %>
        <br />
    </body>
</html>

Het toont drie gegevens die door de controller zijn berekend:

  1. de identiteit van de klant via zijn sessietoken: [jeton]
  2. het totale aantal verzoeken dat aan de applicatie is gedaan: [nbRequêtesApplication]
  3. het totale aantal verzoeken dat is gedaan door de klant die in 1 is geïdentificeerd: [nbRequêtesClient]

Deze drie gegevens worden berekend in [main.aspx.vb]:

Public Class main
    Inherits System.Web.UI.Page

    Protected nbRequêtesApplication As String
    Protected nbRequêtesClient As String
    Protected jeton As String

    Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
         ' Nog een verzoek voor de applicatie
        Application.Item("nbRequêtes") = CType(Application.Item("nbRequêtes"), Integer) + 1
         ' Nog een verzoek in de sessie
        Session.Item("nbRequêtes") = CType(Session.Item("nbRequêtes"), Integer) + 1
         ' weergavevariabelen initialiseren
        nbRequêtesApplication = Application.Item("nbRequêtes").ToString
        jeton = Session.SessionID
        nbRequêtesClient = Session.Item("nbRequêtes").ToString
    End Sub
End Class

Wanneer [main.aspx.vb] wordt uitgevoerd, zijn we bezig met het verwerken van een verzoek van een bepaalde klant. We gebruiken het object [Application] om het aantal verzoeken van de applicatie te verhogen en het object [Session] om het aantal verzoeken van de klant waarvan we het verzoek momenteel verwerken te verhogen. Ter herinnering: hoewel alle klanten van dezelfde applicatie hetzelfde object [Application] delen, heeft elke klant een eigen object [Session].

We testen de applicatie door de vier voorgaande bestanden in een map te plaatsen die we <application-path> noemen en we starten de Cassini-server met de parameters (<application-path>,/aspnet/webapplia). We openen een eerste browser en roepen de URL [http://localhost/aspnet/webapplia/main.aspx] op:

Image

We doen een tweede verzoek via de knop [Reload]:

Image

We openen een tweede browser om dezelfde URL op te vragen. Voor de webserver is dit een nieuwe client:

Image

We zien dat het sessietoken is veranderd en dat we dus een nieuwe client hebben. Dit komt tot uiting in het aantal verzoeken van de client. Laten we nu teruggaan naar de eerste browser en opnieuw dezelfde URL opvragen:

Image

Het aantal verzoeken aan de applicatie wordt inderdaad allemaal geteld.

4.1.3.5. Over de noodzaak om de clients van een applicatie te synchroniseren

In de vorige applicatie wordt de teller van het aantal verzoeken aan de applicatie verhoogd in de procedure [Form_Load] van de pagina [main.aspx] op de volgende manier:

         ' nog een verzoek voor de applicatie
        Application.Item("nbRequêtes") = CType(Application.Item("nbRequêtes"), Integer) + 1

Deze instructie is weliswaar eenvoudig, maar vereist meerdere processorinstructies om te worden uitgevoerd. Laten we aannemen dat er drie nodig zijn:

  1. het uitlezen van de teller
  2. de teller verhogen
  3. de teller overschrijven

De webserver draait op een multitasking-machine, wat betekent dat elke taak de processor enkele milliseconden krijgt toegewezen, deze vervolgens verliest en weer terugkrijgt nadat alle andere taken ook hun tijdslot hebben gehad. Laten we aannemen dat twee clients, A en B, tegelijkertijd een verzoek indienen bij de webserver. Laten we aannemen dat klant A als eerste aan de beurt is, dat hij in de procedure [Form_Load] terechtkomt vanuit [main.aspx.vb], de teller leest (=100) en vervolgens wordt onderbroken omdat zijn tijdsquota is opgebruikt. Laten we nu aannemen dat het de beurt is aan klant B en dat deze hetzelfde lot ondergaat: hij slaagt erin de waarde van de teller (=100) te lezen, maar heeft geen tijd om deze te verhogen. Klanten A en B hebben allebei een teller die gelijk is aan 100. Stel dat het weer de beurt is aan klant A: hij verhoogt zijn teller, zet deze op 101 en beëindigt vervolgens de bewerking. Het is nu de beurt aan klant B, die de oude waarde van de teller in zijn bezit heeft en niet de nieuwe. Hij zet de waarde van de teller dus ook op 101 en beëindigt de bewerking. De waarde van de verzoekenteller van de applicatie is nu onjuist.

Om dit probleem te illustreren, nemen we de vorige applicatie weer op en passen we deze als volgt aan:

  • de bestanden [global.asax], [global.asax.vb] en [main.aspx] blijven ongewijzigd
  • het bestand [main.aspx.vb] ziet er nu als volgt uit:

Imports System.Threading

Public Class main
    Inherits System.Web.UI.Page

    Protected nbRequêtesApplication As Integer
    Protected nbRequêtesClient As Integer
    Protected jeton As String

    Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' nog een verzoek voor de applicatie en de sessie
        ' tellers uitlezen
        nbRequêtesApplication = CType(Application.Item("nbRequêtes"), Integer)
        nbRequêtesClient = CType(Session.Item("nbRequêtes"), Integer)
        ' 5 seconden wachten
        Thread.Sleep(5000)
        ' tellers verhogen
        nbRequêtesApplication += 1
        nbRequêtesClient += 1
        ' tellers opslaan
        Application.Item("nbRequêtes") = nbRequêtesApplication
        Session.Item("nbRequêtes") = nbRequêtesClient
        ' presentatievariabelen initialiseren
        jeton = Session.SessionID
    End Sub
End Class

Het bijwerken van de tellers is verdeeld in vier fasen:

  1. het uitlezen van de teller
  2. de uitvoeringsthread in slaapstand zetten
  3. de teller verhogen
  4. het herschrijven van de teller

Laten we nogmaals kijken naar onze twee clients A en B. Tussen de leesfase en de fase waarin de tellers voor het aantal verzoeken worden opgehoogd, dwingen we de uitvoeringsthread om 5 seconden stil te staan. Dit heeft als onmiddellijk gevolg dat de thread de processor kwijtraakt, die vervolgens aan een andere taak wordt toegewezen. Stel dat klant A als eerste aan de beurt is. Hij leest de waarde N van de teller en wordt vervolgens gedurende 5 seconden onderbroken. Als klant B gedurende die tijd de processor tot zijn beschikking heeft, zou hij dezelfde waarde N van de teller moeten lezen. Uiteindelijk zouden beide klanten dezelfde waarde van de teller moeten weergeven, wat onnormaal zou zijn.

We testen de applicatie door de vier voorgaande bestanden in een map te plaatsen die we <application-path> noemen en we starten de Cassini-server met de parameters (<application-path>,/aspnet/webapplib). We zetten twee verschillende browsers klaar met de URL [http://localhost/aspnet/webapplib/main.aspx]. We starten de eerste browser zodat deze URL opvraagt, en zonder te wachten op het antwoord dat 5 seconden later zal komen, starten we de tweede browser. Na iets meer dan 5 seconden krijgen we het volgende resultaat:

Image

We zien:

  • dat er twee verschillende clients zijn (niet hetzelfde sessietoken)
  • dat elke client een verzoek heeft gedaan
  • dat de teller van het aantal verzoeken aan de applicatie dus op 2 zou moeten staan in een van de twee browsers. Dat is niet het geval.

Laten we nu nog een experiment doen. Met dezelfde browser sturen we vijf verzoeken naar de URL [http://localhost/aspnet/webapplib/main.aspx]. Ook hier sturen we ze achter elkaar zonder op de resultaten te wachten. Wanneer alle verzoeken zijn uitgevoerd, krijgen we het volgende resultaat voor het laatste verzoek:

Image

We kunnen het volgende opmerken:

  • dat de 5 verzoeken werden beschouwd als afkomstig van dezelfde client, aangezien de teller voor clientverzoeken op 5 staat. Hoewel dit hierboven niet wordt weergegeven, zien we dat het sessietoken inderdaad hetzelfde is voor alle 5 verzoeken.
  • dat de teller van het aantal verzoeken aan de applicatie correct is.

Wat kunnen we hieruit concluderen? Niets definitiefs. Misschien begint de webserver pas met het uitvoeren van een verzoek van een klant als er nog geen verzoek van die klant in uitvoering is? Er zou dus nooit sprake zijn van gelijktijdige uitvoering van verzoeken van dezelfde klant. Ze zouden de ene na de andere worden uitgevoerd. Dit punt moet worden gecontroleerd. Het kan namelijk afhangen van het type klant dat wordt gebruikt.

4.1.3.6. Synchronisatie van clients

Het probleem dat in de vorige toepassing aan het licht kwam, is een klassiek (maar niet eenvoudig op te lossen) probleem van exclusieve toegang tot een hulpbron. In ons specifieke geval moeten we ervoor zorgen dat twee clients A en B niet tegelijkertijd in de codereeks kunnen voorkomen:

  1. het uitlezen van de teller
  2. de teller verhogen
  3. het overschrijven van de teller

Een dergelijke codereeks wordt een kritieke reeks genoemd. Deze vereist synchronisatie van de threads die deze reeks gelijktijdig moeten uitvoeren. Het platform .NET biedt diverse hulpmiddelen om dit te waarborgen. We zullen hier de klasse [Mutex] gebruiken.

Image

We zullen hier alleen de volgende constructors en methoden gebruiken:

public Mutex()
maakt een synchronisatieobject M aan
public bool WaitOne()
De thread T1 die de bewerking M.WaitOne() uitvoert, vraagt de eigendom van het synchronisatieobject M aan. Als de mutex M door geen enkele thread wordt vastgehouden (wat in het begin het geval is), wordt deze „toegewezen” aan de thread T1 die erom heeft gevraagd. Als even later een thread T2 dezelfde bewerking uitvoert, wordt deze geblokkeerd. Een mutex kan namelijk slechts aan één thread toebehoren. De thread wordt gedeblokkeerd zodra de thread T1 de mutex M vrijgeeft die hij in bezit heeft. Zo kunnen meerdere threads geblokkeerd raken terwijl ze wachten op de mutex M.
public void ReleaseMutex()
De thread T1, die de bewerking M.ReleaseMutex() uitvoert, geeft de Mutex M vrij. Wanneer de thread T1 de processor kwijtraakt, kan het systeem deze toewijzen aan een van de threads die op de Mutex M wachten. Slechts één thread zal deze op zijn beurt krijgen; de andere threads die op M wachten, blijven geblokkeerd

Een mutex M regelt de toegang tot een gedeelde bron R. Een thread vraagt de bron R aan via M.WaitOne() en geeft deze terug via M.ReleaseMutex(). Een kritiek gedeelte van de code dat slechts door één thread tegelijk mag worden uitgevoerd, is een gedeelde bron. De uitvoering van de kritieke sectie kan als volgt worden gesynchroniseerd:

M.WaitOne()
' alleen deze thread mag hier binnenkomen
' kritieke sectie
....
M.ReleaseMutex()

waarbij M een object Mutex is. Men mag natuurlijk nooit vergeten een Mutex vrij te geven dat niet langer nodig is, zodat een andere thread op zijn beurt de kritieke sectie kan betreden; anders zullen de threads die wachten op een nooit vrijgegeven mutex nooit toegang krijgen tot de processor. Bovendien moet een deadlock-situatie (deadlock) worden vermeden, waarbij twee threads op elkaar wachten. Laten we de volgende acties bekijken die elkaar in de tijd opvolgen:

  • een thread T1 verkrijgt de eigendom van een mutex M1 om toegang te krijgen tot een gedeelde bron R1
  • een thread T2 verkrijgt de eigendom van een mutex M2 om toegang te krijgen tot een gedeelde bron R2
  • de thread T1 vraagt de mutex M2 aan. Hij wordt geblokkeerd.
  • de thread T2 vraagt de mutex M1 aan. Hij wordt geblokkeerd.

Hier wachten de threads T1 en T2 op elkaar. Dit scenario doet zich voor wanneer threads twee gedeelde bronnen nodig hebben: de bron R1, die wordt beheerd door de mutex M1, en de bron R2, die wordt beheerd door de mutex M2. Een mogelijke oplossing is om beide resources tegelijkertijd aan te vragen met behulp van één enkele mutex M. Dit is echter niet altijd mogelijk, met name als dit leidt tot langdurige bezetting van een kostbare resource. Een andere oplossing is dat een thread die M1 bezit en M2 niet kan verkrijgen, vervolgens M1 vrijgeeft om een deadlock te voorkomen.

Als we hetgeen we zojuist hebben geleerd in de praktijk brengen, ziet onze toepassing er als volgt uit:

  • de bestanden [global.asax] en [main.aspx] blijven ongewijzigd
  • het bestand [global.asax.vb] ziet er als volgt uit:

Imports System
Imports System.Web
Imports System.Web.SessionState
Imports System.Threading

Public Class global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' Wordt geactiveerd wanneer de applicatie wordt gestart
        ' klantenteller initialiseren
        Application.Item("nbRequêtes") = 0
        ' aanmaken van een synchronisatievergrendeling
        Application.Item("verrou") = New Mutex
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' Wordt geactiveerd wanneer de sessie wordt gestart
        ' initialisatie van de verzoekenteller
        Session.Item("nbRequêtes") = 0
    End Sub
End Class

De enige nieuwigheid is de aanmaak van een [Mutex], dat door de clients zal worden gebruikt om te synchroniseren. Omdat het voor alle clients toegankelijk moet zijn, wordt het in het object [Application] geplaatst.

  • Het bestand [main.aspx.vb] ziet er nu als volgt uit:

Imports System.Threading

Public Class main
    Inherits System.Web.UI.Page

    Protected nbRequêtesApplication As Integer
    Protected nbRequêtesClient As Integer
    Protected jeton As String

    Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' Nog een verzoek voor de applicatie en de sessie
        ' we gaan een kritieke sectie binnen – we halen de synchronisatievergrendeling op
        Dim verrou As Mutex = CType(Application.Item("verrou"), Mutex)
        ' er wordt gevraagd om als enige de volgende kritieke sectie te betreden
        verrou.WaitOne()
        ' tellers uitlezen
        nbRequêtesApplication = CType(Application.Item("nbRequêtes"), Integer)
        nbRequêtesClient = CType(Session.Item("nbRequêtes"), Integer)
        ' 5 seconden wachten
        Thread.Sleep(5000)
        ' de tellers verhogen
        nbRequêtesApplication += 1
        nbRequêtesClient += 1
        ' tellers opslaan
        Application.Item("nbRequêtes") = nbRequêtesApplication
        Session.Item("nbRequêtes") = nbRequêtesClient
        ' toegang tot het kritieke gedeelte wordt toegestaan
        verrou.ReleaseMutex()
        ' weergavevariabelen initialiseren
        jeton = Session.SessionID
    End Sub
End Class

We zien dat de client:

  • vraagt om als enige de kritieke sectie te betreden. Hiervoor vraagt hij exclusief eigendom van de mutex [verrou]
  • aan het einde van de kritieke sectie de mutex [verrou] vrijgeeft, zodat een andere client op zijn beurt de kritieke sectie kan betreden.

We testen de applicatie door de vier voorgaande bestanden in een map te plaatsen die we <application-path> noemen en we starten de Cassini-server met de parameters (<application-path>,/aspnet/webapplic). We zetten twee verschillende browsers klaar met de URL [http://localhost/aspnet/webapplic/main.aspx]. We starten de eerste browser zodat deze URL opvraagt en vervolgens, zonder te wachten op het antwoord dat 5 seconden later zal komen, starten we de tweede browser. Na iets meer dan 5 seconden krijgen we het volgende resultaat:

Image

Deze keer klopt de verzoekenteller van de applicatie.

Wat we uit deze uitgebreide demonstratie kunnen concluderen, is dat het absoluut noodzakelijk is om de clients van eenzelfde webapplicatie te synchroniseren, als ze elementen moeten bijwerken die door alle clients worden gedeeld.

4.1.3.7. Beheer van het sessietoken

We hebben al meerdere keren gesproken over het sessietoken dat tussen de client en de webserver wordt uitgewisseld. Laten we het principe nog eens op een rijtje zetten:

  • de client doet een eerste verzoek aan de server. Hij stuurt geen sessietoken mee.
  • Omdat het sessietoken ontbreekt in het verzoek, herkent de server een nieuwe client en wijst hem een token toe. Aan dit token is ook een object [Session] gekoppeld, dat zal worden gebruikt om informatie op te slaan die specifiek is voor deze client. Het token zal alle verzoeken van deze klant volgen. Het wordt opgenomen in de headers HTTP van het antwoord op het eerste verzoek van de klant.
  • De klant kent nu zijn sessietoken. Hij zal dit terugsturen in de HTTP-headers van elk van de volgende verzoeken die hij aan de webserver zal doen. Dankzij het token kan de server het aan de klant gekoppelde object [Session] terugvinden.

Om dit mechanisme te illustreren, nemen we de vorige applicatie weer op en wijzigen we alleen het bestand [main.aspx.vb]:


Imports System.Threading

Public Class main
    Inherits System.Web.UI.Page

    Protected nbRequêtesApplication As Integer
    Protected nbRequêtesClient As Integer
    Protected jeton As String

    Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' nog een verzoek voor de applicatie en de sessie
        ' we gaan een kritieke sectie binnen – we halen de synchronisatievergrendeling op
        Dim verrou As Mutex = CType(Application.Item("verrou"), Mutex)
        ' er wordt gevraagd om als enige de volgende sectie te betreden
        verrou.WaitOne()
        ' tellers uitlezen
        nbRequêtesApplication = CType(Application.Item("nbRequêtes"), Integer)
        nbRequêtesClient = CType(Session.Item("nbRequêtes"), Integer)
        ' 5 seconden wachten
        Thread.Sleep(5000)
        ' de tellers verhogen
        nbRequêtesApplication += 1
        nbRequêtesClient += 1
        ' tellers opslaan
        Application.Item("nbRequêtes") = nbRequêtesApplication
        Session.Item("nbRequêtes") = nbRequêtesClient
        ' toegang tot de kritieke sectie wordt toegestaan
        verrou.ReleaseMutex()
        ' weergavevariabelen initialiseren
        jeton = Session.SessionID
    End Sub

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Init
        ' de verzoek van de client wordt opgeslagen in request.txt in de applicatiemap
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub
End Class

Wanneer de gebeurtenis [Page_Init] plaatsvindt, slaan we de verzoek van de client op in de map van de applicatie. Laten we enkele punten nog eens op een rijtje zetten:

  • [TemplateSourceDirectory] staat voor het virtuele pad van de pagina die op dat moment wordt uitgevoerd,
  • MapPath (TemplateSourceDirectory) staat voor het bijbehorende fysieke pad. Hierdoor kunnen we het fysieke pad samenstellen van het te genereren bestand,
  • [Request] is een object dat het verzoek vertegenwoordigt dat momenteel wordt verwerkt. Dit object is samengesteld op basis van het ruwe verzoek dat door de klant is verzonden, c.a.d: een reeks tekstregels in de vorm:

Image

  • Request.Save([FileName]) slaat het volledige verzoek van de klant op (HTTP-headers en eventueel het daaropvolgende document) in een bestand waarvan het pad als parameter wordt doorgegeven.

Zo kunnen we precies achterhalen wat het verzoek van de client was. We testen de applicatie door de vier voorgaande bestanden in een map met de naam <application-path> te plaatsen en de Cassini-server te starten met de parameters (<application-path>,/aspnet/session1). Vervolgens vragen we met een browser het bestand URL

[http://localhost/aspnet/session1/main.aspx] op. We krijgen het volgende resultaat:

Image

We gebruiken het bestand [request.txt], dat is opgeslagen door [main.aspx.vb], om toegang te krijgen tot de verzoek van de browser:

GET /aspnet/session1/main.aspx HTTP/1.1
Cache-Control: max-age=0
Connection: keep-alive
Keep-Alive: 300
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0,5
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.0; en-US; rv:1.7b) Gecko/20040316

We zien dat de browser het verzoek voor URL [/aspnet/session1/main.aspx] heeft verzonden, samen met andere informatie die we in het vorige hoofdstuk al hebben besproken. Er is geen sessietoken te zien. De pagina die als antwoord is ontvangen, laat echter zien dat de server een sessietoken heeft aangemaakt. Het is nog niet duidelijk of de browser dit heeft ontvangen. Laten we nu een tweede verzoek doen met dezelfde browser (Reload). We krijgen het volgende nieuwe antwoord:

Image

Er is wel degelijk sprake van sessiebewaking, aangezien het aantal verzoeken van de sessie correct is verhoogd. Laten we nu eens kijken naar de inhoud van het bestand [request.txt]:

GET /aspnet/session1/main.aspx HTTP/1.1
Cache-Control: max-age=0
Connection: keep-alive
Keep-Alive: 300
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0,5
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
Cookie: ASP.NET_SessionId=y153tk45sise0lrhdzrf22m3
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.7b) Gecko/20040316

We zien dat de browser voor dit tweede verzoek een nieuwe header HTTP [Cookie:] naar de server heeft gestuurd, waarin een gegeven met de naam [ASP.NET_SessionId] wordt gedefinieerd, met als waarde het sessietoken dat we in het antwoord op het eerste verzoek zagen verschijnen. Dankzij dit token koppelt de webserver dit nieuwe verzoek aan het object [Session], dat wordt geïdentificeerd door het token [y153tk45sise0lrhdzrf22m3], en haalt hij de bijbehorende verzoeksteller op.

We weten nog steeds niet via welk mechanisme de server het token naar de client heeft verzonden, omdat we geen toegang hebben tot het antwoord HTTP van de server. Ter herinnering: dit antwoord heeft dezelfde structuur als het verzoek van de client, namelijk een reeks tekstregels in de vorm:

Image

We hebben eerder een webclient gebruikt die ons toegang gaf tot het antwoord HTTP van de webserver, namelijk de client curl. We gebruiken deze opnieuw, in een DOS-venster, om dezelfde URL op te vragen als de vorige browser:

E:\curl>curl --include http://localhost/aspnet/session1/main.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 07:31:42 GMT
X-AspNet-Version: 1.1.4322
Set-Cookie: ASP.NET_SessionId=qxnxmqmvhde3al55kzsmx445; path=/
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 228
Connection: Close


<HTML>
        <HEAD>
                <title>application-session</title>
        </HEAD>
        <body>
                jeton de session :
                qxnxmqmvhde3al55kzsmx445
                <br>
                requêtes Application :
                3
                <br>
                requêtes Client :
                1
                <br>
        </body>
</HTML>

We hebben het antwoord op onze vraag. De webserver verstuurt het sessietoken in de vorm van een header HTTP [Set-Cookie:]:

Set-Cookie: ASP.NET_SessionId=qxnxmqmvhde3al55kzsmx445; path=/

Laten we hetzelfde verzoek doen zonder het sessietoken mee te sturen. We krijgen dan het volgende antwoord:

E:\curl>curl --include http://localhost/aspnet/session1/main.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 07:36:06 GMT
X-AspNet-Version: 1.1.4322
Set-Cookie: ASP.NET_SessionId=cs2p12mehdiz5v55ihev1kaz; path=/
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 228
Connection: Close


<HTML>
        <HEAD>
                <title>application-session</title>
        </HEAD>
        <body>
                jeton de session :
                cs2p12mehdiz5v55ihev1kaz
                <br>
                requêtes Application :
                4
                <br>
                requêtes Client :
                1
                <br>
        </body>
</HTML>

Omdat we het sessietoken niet hebben teruggestuurd, kon de server ons niet identificeren en heeft hij ons een nieuw token gegeven. Om een gestarte sessie voort te zetten, moet de client het ontvangen sessietoken naar de server terugsturen. Dat gaan we hier doen met behulp van de optie [--cookie clé=valeur] van curl, die de header HTTP [Cookie: clé=valeur] genereert. We hebben gezien dat de browser deze header HTTP bij zijn tweede verzoek had verzonden.

E:\curl>curl --include --cookie ASP.NET_SessionId=cs2p12mehdiz5v55ihev1kaz http://localhost/aspnet/session1/main.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 07:40:20 GMT
X-AspNet-Version: 1.1.4322
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 228
Connection: Close


<HTML>
        <HEAD>
                <title>application-session</title>
        </HEAD>
        <body>
                jeton de session :
                cs2p12mehdiz5v55ihev1kaz
                <br>
                requêtes Application :
                5
                <br>
                requêtes Client :
                2
                <br>
        </body>
</HTML>

Er vallen verschillende dingen op:

  • de teller voor clientverzoeken is inderdaad verhoogd, wat aantoont dat de server ons token inderdaad heeft herkend.
  • het sessietoken dat door de pagina wordt weergegeven, is inderdaad het token dat we hebben verzonden
  • het sessietoken staat niet meer in de HTTP-headers die door de webserver worden verzonden. De server verstuurt het namelijk maar één keer: bij het genereren van het token bij het starten van een nieuwe sessie. Zodra de client zijn token heeft ontvangen, is het aan hem om het te gebruiken wanneer hij maar wil om zich te laten herkennen.

Niets weerhoudt een client ervan om met meerdere sessietokens te werken, zoals blijkt uit het volgende voorbeeld met [curl], waarin we het token gebruiken dat we bij ons eerste verzoek (verzoek nr. 1) hebben ontvangen:

E:\curl>curl --include --cookie ASP.NET_SessionId=qxnxmqmvhde3al55kzsmx445 http://localhost/aspnet/session1/main.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 07:48:47 GMT
X-AspNet-Version: 1.1.4322
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 228
Connection: Close


<HTML>
        <HEAD>
                <title>application-session</title>
        </HEAD>
        <body>
                jeton de session :
                qxnxmqmvhde3al55kzsmx445
                <br>
                requêtes Application :
                6
                <br>
                requêtes Client :
                2
                <br>
        </body>
</HTML>

Wat betekent dit voorbeeld? We hebben een token verzonden dat we even eerder hadden ontvangen. Wanneer de webserver een token aanmaakt, bewaart hij dit zolang de client die aan dit token is gekoppeld, verzoeken naar de server blijft sturen. Na een bepaalde tijd van inactiviteit (standaard 20 minuten bij IIS) wordt het token verwijderd. Het vorige voorbeeld laat zien dat we een token hebben gebruikt dat nog actief was.

Misschien ben je wel benieuwd welke verzoeken HTTP van de client [curl] tijdens al deze handelingen heeft verzonden. We weten dat deze zijn opgeslagen in het bestand [request.txt]. Hier is het laatste verzoek:

GET /aspnet/session1/main.aspx HTTP/1.1
Pragma: no-cache
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
Cookie: ASP.NET_SessionId=qxnxmqmvhde3al55kzsmx445
Host: localhost
User-Agent: curl/7.10.8 (win32) libcurl/7.10.8 OpenSSL/0.9.7a zlib/1.1.4

Hierin is inderdaad de header HTTP te vinden die het sessietoken verstuurt.

De informatie die door de server via de headers HTTP en [Set-Cookie:] wordt verzonden, wordt ‘cookies’ genoemd. De server kan dit mechanisme gebruiken om andere informatie dan het sessietoken door te geven. Wanneer server S een cookie naar een client verstuurt, geeft hij ook de levensduur D ervan en de bijbehorende URL U aan. Dit betekent voor de client dat wanneer hij bij server S een URL in de vorm /U/pad opvraagt, hij de cookie kan terugsturen als hij deze al langer dan D niet heeft ontvangen. Niets weerhoudt een client ervan om zich niet aan deze gedragscode te houden. Browsers houden zich er wel aan. Sommige browsers geven toegang tot de inhoud van de cookies die ze ontvangen. Dit is het geval bij de Mozilla-browser. Hieronder volgt bijvoorbeeld de informatie met betrekking tot de cookie die door de server in een eerder voorbeeld is verzonden:

Image

Hierin staat:

  • de naam van de cookie [ASP.NET_SessionId]
  • de waarde ervan: [y153...m3]
  • het apparaat waaraan het is gekoppeld: [localhost]
  • de URL waaraan het is gekoppeld: [/]
  • de geldigheidsduur: [at end of session]

De browser zal het sessietoken dus telkens verzenden wanneer hij een URL opvraagt in de vorm van [http://localhost/...], c.a.d. telkens wanneer hij een URL opvraagt bij de webserver van de machine [localhost]. De levensduur van de cookie is gelijk aan die van de sessie. Voor de browser betekent dit dat de cookie nooit verloopt. Hij zal deze telkens verzenden wanneer hij een URL opvraagt bij de machine [localhost]. Als de browser dus op dag D het sessietoken ontvangt, wordt afgesloten en de volgende dag opnieuw wordt gebruikt, zal hij het sessietoken (dat in een bestand is bewaard) opnieuw verzenden. De server ontvangt dit token dat hij zelf niet meer heeft, omdat een sessietoken een beperkte levensduur heeft op de server (20 minuten op IIS). Daarom start de server een nieuwe sessie.

Het is mogelijk om het gebruik van cookies in een browser uit te schakelen. In dat geval ontvangt de client wel het sessietoken, maar stuurt hij het niet terug, waardoor het volgen van de sessie niet mogelijk is. Om dit te demonstreren, schakelen we het gebruik van cookies uit in onze browser (hier Mozilla):

Image

Daarnaast verwijderen we alle bestaande cookies:

Image

Nadat dit is gebeurd, starten we de Cassini-server opnieuw op om helemaal opnieuw te beginnen en vragen we met de browser opnieuw de URL [http://localhost/aspnet/session1/main.aspx] op:

Image

Laten we eens kijken of onze browser een cookie heeft opgeslagen:

Image

We zien dat de browser de sessietoken-cookie die de server hem heeft gestuurd, niet heeft opgeslagen. We kunnen dus verwachten dat er geen sessietracking plaatsvindt. We vragen dezelfde URL opnieuw op (Reload):

Image

Het resultaat is precies zoals verwacht. De browser heeft het sessietoken niet teruggestuurd, hoewel hij het wel had ontvangen maar niet opgeslagen. De server is daarom een nieuwe sessie gestart met een nieuw token. Uit dit voorbeeld blijkt dat ons beleid voor sessietracking in het gedrang komt als de gebruiker het gebruik van cookies in zijn browser heeft uitgeschakeld. Er is echter nog een andere manier dan cookies om het sessietoken tussen server en client uit te wisselen. Het is namelijk mogelijk om de webserver te laten weten dat de applicatie zonder cookies werkt. Dit gebeurt via het configuratiebestand [web.config]:


<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <system.web>
        <sessionState cookieless="true" timeout="10" />
    </system.web>
</configuration>

Het bovenstaande configuratiebestand geeft aan dat de applicatie zonder cookies werkt (cookieless="true") en dat de maximale inactiviteitstijd van een sessietoken 10 minuten bedraagt (timeout="10"). Na deze tijd wordt de sessie die aan het token is gekoppeld, beëindigd. Het proces van de uitwisseling van het sessietoken tussen de server en de client verloopt als volgt:

  1. de client vraagt de URL [http://machine:port/V/chemin] op, waarbij V een virtuele map op de webserver is
  2. de server genereert een token J en antwoordt de client dat hij moet worden omgeleid naar de URL [http://machine:port/V/(J)/chemin]. Het token is dus in de op te vragen URL geplaatst, direct achter de virtuele map V
  3. de client volgt deze omleiding en vraagt de nieuwe URL URL [http://machine:port/V/(J)/chemin] op.
  4. De server beantwoordt dit verzoek en stuurt een antwoordpagina.

Laten we deze verschillende punten illustreren. We plaatsen de volledige vorige applicatie in een nieuwe map <application-path>. In diezelfde map plaatsen we het vorige bestand [web.config]. Daarnaast passen we de weergavecode [main.aspx] aan om er een link in op te nemen:


<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<HTML>
    <HEAD>
        <title>application-session</title>
    </HEAD>
    <body>
        jeton de session :
        <% =jeton %>
        <br>
        requêtes Application :
        <% =nbRequêtesApplication %>
        <br>
        requêtes Client :
        <% =nbRequêtesClient %>
        <br>
        <a href="main.aspx">Recharger l'application</a>
    </body>
</HTML>

Deze link verwijst naar de pagina [main.aspx] en is dus gelijk aan de knop (Reload) van de browser. De Cassini-server wordt gestart met de parameters (<application-path>,/session2). We wijken hier af van onze gebruikelijke werkwijze, waarbij we de virtuele map [/aspnet/XX] noteerden. Door de toevoeging van het sessietoken aan de URL mag de virtuele map namelijk slechts één element bevatten: /XX. We gebruiken eerst de client [curl] om de URL [http://localhost/session2/main.aspx] op te vragen:

E:\curl>curl --include http://localhost/session2/main.aspx
HTTP/1.1 302 Found
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 13:52:36 GMT
X-AspNet-Version: 1.1.4322
Location: /session2/(hinadjag3bt0u155g5hqe245)/main.aspx
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 163
Connection: Close

<html><head><title>Object moved</title></head><body>
<h2>Object moved to <a href='/session2/(hinadjag3bt0u155g5hqe245)/main.aspx'>here
</body></html>

We zien dat de server reageert met de header HTTP [HTTP/1.1 302 Found] in plaats van [HTTP/1.1 200 OK]. Dit is een header die de client vraagt om door te sturen naar de url die wordt aangegeven door de header HTTP Location [Location: /session2/(hinadjag3bt0u155g5hqe245)/main.aspx]. We zien het sessietoken dat in de omleidings-URL is opgenomen. Een browser die dit antwoord ontvangt, vraagt de nieuwe URL op, op een manier die transparant is voor de gebruiker, die het nieuwe verzoek niet ziet. Mocht de browser de omleiding niet zelf afhandelen, dan wordt er achter de bovenstaande code HTTP een document met de code HTML verzonden. Daarin staat een link naar de omleidings-URL, waarop de gebruiker kan klikken.

Laten we nu hetzelfde doen met een browser waarin cookies zijn uitgeschakeld. We vragen opnieuw de URL [http://localhost/session2/main.aspx] op. We krijgen het volgende antwoord van de server:

Image

Allereerst zien we dat de URL die door de browser wordt weergegeven niet dezelfde is als die we hebben opgevraagd. Dit is een teken dat er een omleiding heeft plaatsgevonden. De browser geeft namelijk altijd de URL URL weer van het laatst ontvangen document. Als de browser dus niet de URL [http://localhost/session2/main.aspx] weergeeft, betekent dit dat hij is gevraagd om door te sturen naar een andere URL. Er kunnen meerdere omleidingen plaatsvinden. De URL die door de browser wordt weergegeven, is de URL van de laatste omleiding. We kunnen zien dat het sessietoken aanwezig is in de URL die door de browser wordt weergegeven. Dit is te zien omdat dit token ook door ons programma op de pagina wordt weergegeven.

Laten we nog eens kijken naar de code van de link die op de pagina is geplaatst:


        <a href="main.aspx">Recharger l'application</a>

Dit is een relatieve link, aangezien deze niet begint met het teken /, wat er een absolute link van zou maken. Relatief ten opzichte van wat? Om dit te begrijpen, moeten we teruggaan naar de URL van het document dat momenteel wordt weergegeven: [http://localhost/session2/(gu5ee455pkpffn554e3b1a32)/main.aspx]. Relatieve links die in dit document worden aangetroffen, zijn relatief ten opzichte van het pad [http://localhost/session2/(gu5ee455pkpffn554e3b1a32)]. Onze bovenstaande link is dus gelijk aan de link:


        <a href=" http://localhost/session2/(gu5ee455pkpffn554e3b1a32)/main.aspx">Recharger l'application</a>

Dit is wat de browser ons laat zien als we met de muis over de link gaan:

Image

Als we op de link [Recharger l'application] klikken, wordt dus de URL

[http://localhost/session2/(gu5ee455pkpffn554e3b1a32)/main.aspx] die wordt opgeroepen. De server ontvangt dan het sessietoken en kan de bijbehorende informatie ophalen. Dit is wat het antwoord van de server ons laat zien:

Image

We onthouden dat als we een sessie moeten bijhouden in een webapplicatie en we niet zeker weten of de browsers van de gebruikers van deze applicatie het gebruik van cookies toestaan,

  • moeten we de applicatie zo configureren dat deze zonder cookies werkt
  • de pagina’s van de applicatie relatieve links moeten bevatten in plaats van absolute links

4.2. Informatie uit een verzoek van de klant ophalen

4.2.1. De verzoek-antwoordcyclus van de webclient-server

Laten we hier nog even de client-servercontext van een webapplicatie in herinnering brengen:

Image

Een verzoek van een klant voor een webapplicatie wordt als volgt verwerkt:

  1. de klant opent een TCP/IP-verbinding naar poort P van de webservice op machine M, waarop de webapplicatie draait
  2. hij verstuurt via deze verbinding een reeks tekstregels volgens het protocol HTTP. Deze reeks regels vormt wat we het verzoek van de client noemen. Het heeft de volgende vorm:

Image

Zodra het verzoek is verzonden, wacht de client op het antwoord.

  1. De eerste regel van de headers HTTP specificeert de actie die van de webserver wordt gevraagd. Deze kan verschillende vormen aannemen:
    1. GET url HTTP/<versie>, waarbij <versie> momenteel gelijk is aan 1.0 of 1.1. In dit geval bevat het verzoek niet het gedeelte [Document]
    2. POST url HTTP/<versie>. In dit geval bevat het verzoek een gedeelte [Document], meestal een lijst met informatie bestemd voor de webapplicatie
    3. PUT url HTTP/<versie>. De client verstuurt een document in het gedeelte [Document] en wil dit opslaan op de server op het adres url

Wanneer de client informatie wil doorgeven aan de webapplicatie waarmee hij verbinding heeft gemaakt, heeft hij hoofdzakelijk twee mogelijkheden:

  • (vervolg)
    1. zijn verzoek is [GET url_enrichie HTTP/<version>], waarbij url_enrichie de vorm [url?param1=val1&param2=val2&...] heeft. De client verzendt naast de url een reeks gegevens in de vorm [clé=valeur].
    2. zijn verzoek is [POST url HTTP/<version>]. In het gedeelte [Document] verstuurt hij informatie in dezelfde vorm als eerder: [param1=val1&param2=val2&...].
  1. Op de server heeft de gehele verwerkingsketen van het verzoek van de klant toegang tot dit verzoek via een globaal object met de naam Request. De webserver heeft in dit object het volledige verzoek van de klant geplaatst in een vorm die we straks zullen bekijken. De aangeroepen applicatie verwerkt dit object en stelt een antwoord voor de klant samen. Dit antwoord is beschikbaar in een globaal object met de naam Response. De rol van de webapplicatie is om een object [Response] samen te stellen op basis van het ontvangen object [Request]. De verwerkingsketen beschikt ook over de globale objecten [Application] en [Session], waarover we het al hebben gehad en die het mogelijk maken om gegevens te delen tussen verschillende klanten (toepassing) of tussen opeenvolgende verzoeken van dezelfde klant (sessie).
  2. De applicatie stuurt haar antwoord naar de server via het object [Response]. Zodra dit op het netwerk is, krijgt het de volgende vorm: HTTP:

Image

Zodra dit antwoord is verzonden, verbreekt de server de inkomende netwerkverbinding (tenzij de klant hem heeft opgedragen dit niet te doen).

  1. De client ontvangt het antwoord en verbreekt op zijn beurt de verbinding (bij het verzenden). Wat er met dit antwoord gebeurt, hangt af van het type client. Als het een browser betreft en het ontvangen document een HTML-document is, wordt dit weergegeven. Als de client een programma is, wordt het antwoord geanalyseerd en verwerkt.
  2. Het feit dat na de vraag-antwoordcyclus de verbinding tussen de client en de server wordt verbroken, maakt het HTTP-protocol tot een stateloos protocol. Bij het volgende verzoek zal de client een nieuwe netwerkverbinding met dezelfde server tot stand brengen. Aangezien dit niet meer dezelfde netwerkverbinding is, heeft de server geen mogelijkheid (op TCP/IP- en HTTP-niveau) om deze nieuwe verbinding aan een eerdere te koppelen. Het is het sessietokensysteem dat deze koppeling mogelijk maakt.

4.2.2. De door de client verzonden informatie ophalen

We bekijken nu enkele eigenschappen en methoden van het object [Request], waarmee de applicatiecode toegang krijgt tot het verzoek van de client en dus tot de informatie die deze heeft verzonden. Het object [Request] is van het type [HttpRequest]:

Image

Deze klasse heeft talrijke eigenschappen en methoden. Wij zijn geïnteresseerd in de eigenschappen HttpMethod, QueryString, Form en Params, waarmee we toegang krijgen tot de elementen van de informatiereeks [param1=val1&param2=val2&...].

HttpMethod as String
verzoekmethoden van de client: GET, POST, HEAD, ...
QueryString as NameValueCollection
verzameling van de elementen van de queryreeks param1=val1&param2=val2&.. van de eerste regel HTTP [méthode]?param1=val1&param2=val2&... waarbij [méthode] GET, POST of HEAD kan zijn.
Form as NameValueCollection
verzameling van de elementen van de queryreeks param1=val1&param2=val2&... die zich in het gedeelte [Document] van de query bevinden (methode POST).
Params as NameValueCollection
brengt meerdere verzamelingen samen: QueryString, Form, ServerVariables, Cookies in één enkele verzameling.

4.2.3. Voorbeeld 1

Laten we deze elementen in een eerste voorbeeld toepassen. De applicatie zal slechts één [main.aspx]-element bevatten. De weergavecode [main.aspx] ziet er als volgt uit:


<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<html>
    <head>
        <title>Requête client</title>
    </head>
    <body>
        Requête :
        <% = méthode %>
        <br />
        nom :
        <% = nom %>
        <br />
        âge :
        <% = age %>
        <br />
    </body>
</html>

De pagina toont drie gegevens [méthode, nom, age] die zijn berekend door het besturingsgedeelte [main.aspx.vb]:

Public Class main
    Inherits System.Web.UI.Page

    Protected nom As String = "xx"
    Protected age As String = "yy"
    Protected méthode As String

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Init
         ' de verzoekgegevens van de client worden opgeslagen in request.txt in de applicatiemap
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
         ' de parameters van het verzoek worden opgehaald
        méthode = Request.HttpMethod.ToLower
        If Not Request.QueryString("nom") Is Nothing Then nom = Request.QueryString("nom").ToString
        If Not Request.QueryString("age") Is Nothing Then age = Request.QueryString("age").ToString
        If Not Request.Form("nom") Is Nothing Then nom = Request.Form("nom").ToString
        If Not Request.Form("age") Is Nothing Then age = Request.Form("age").ToString
    End Sub

End Class

Wanneer de pagina wordt geladen (Form_Load), wordt de informatie uit [nom, age] opgehaald uit het verzoek van de klant. Deze wordt gezocht in de twee collecties [QueryString] en [Form]. . Daarnaast slaan we in [Page_Init] het verzoek van de client op, zodat we kunnen controleren wat hij heeft verzonden. We plaatsen deze twee bestanden in een map <application-path> en starten de Cassini-server met de parameters (<application-path>,/request1). Vervolgens vragen we met een browser de url

[http://localhost/request1/main.aspx?nom=tintin&age=27] op. We krijgen het volgende antwoord:

Image

De door de client verzonden informatie is correct opgehaald. Het verzoek van de browser dat is opgeslagen in het bestand [request.txt] luidt als volgt:

GET /request1/main.aspx?nom=tintin&age=27 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

We zien dat de browser een verzoek GET heeft verzonden. Om een verzoek POST te verzenden, gaan we de client [curl] gebruiken. In een DOS-venster typen we de volgende opdracht:

C:\curl>curl --include --data nom=tintin --data age=27 http://localhost/request1/main.aspx
--include
om de headers van het antwoord HTTP weer te geven
--data param=valeur
om de informatie parameter=waarde te verzenden via een POST

Het antwoord van de server is als volgt:

HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Fri, 02 Apr 2004 09:27:25 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 178
Connection: Close


<html>
        <head>
                <title>Requête client</title>
        </head>
        <body>
                Requête :
                post
                <br />
                nom :
                tintin
                <br />
                âge :
                27
                <br />
        </body>
</html>

De server heeft ook deze keer de parameters goed ontvangen, die ditmaal door een POST zijn verzonden. Om dit laatste punt te verifiëren, kan men de inhoud van het bestand [request.txt] controleren:

POST /request1/main.aspx HTTP/1.1
Pragma: no-cache
Content-Length: 17
Content-Type: application/x-www-form-urlencoded
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
Host: localhost
User-Agent: curl/7.10.8 (win32) libcurl/7.10.8 OpenSSL/0.9.7a zlib/1.1.4

nom=tintin&age=27

De klant [curl] heeft inderdaad een POST uitgevoerd. Laten we nu de twee methoden voor het doorgeven van informatie combineren. We plaatsen [age] in de opgevraagde URL en [nom] in het verzonden document:

E:\curl>curl --include --data nom="tintin" http://localhost/request1/main.aspx?age=27

Het verzoek dat door [curl] wordt verzonden, is als volgt (request.txt):

POST /request1/main.aspx?age=27 HTTP/1.1
Pragma: no-cache
Content-Length: 10
Content-Type: application/x-www-form-urlencoded
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
Host: localhost
User-Agent: curl/7.10.8 (win32) libcurl/7.10.8 OpenSSL/0.9.7a zlib/1.1.4

nom=tintin

We zien dat de leeftijd in de opgevraagde URL is opgenomen. Deze vinden we terug in de collectie [QueryString]. De naam is opgenomen in het document dat naar deze URL is verzonden. Deze vinden we terug in de collectie [Form]. Het antwoord dat de client heeft ontvangen: [curl]:

<html>
        <head>
                <title>Requête client</title>
        </head>
        <body>
                Requête :
                post
                <br />
                nom :
                tintin
                <br />
                âge :
                27
                <br />
        </body>
</html>

Laten we ten slotte geen informatie naar de server sturen:

E:\curl>curl --include http://localhost/request1/main.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Fri, 02 Apr 2004 12:43:14 GMT
X-AspNet-Version: 1.1.4322
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 173
Connection: Close


<html>
        <head>
                <title>Requête client</title>
        </head>
        <body>
                Requête :
                get
                <br />
                nom :
                xx
                <br />
                âge :
                yy
                <br />
        </body>
</html>

De lezer wordt verzocht de code van de controller [main.aspx.vb] nog eens door te nemen om dit antwoord te begrijpen.

4.2.4. Voorbeeld 2

Het is mogelijk dat de klant meerdere waarden voor dezelfde sleutel verstuurt. Wat gebeurt er dan als we in het vorige voorbeeld de URL [http://localhost/request1/main.aspx?nom=tintin&age=27&nom=milou] opvragen, waarin de sleutel [nom] twee keer voorkomt? Laten we dit eens proberen met een browser:

Image

Onze applicatie heeft de twee waarden die bij de sleutel [nom] horen inderdaad opgehaald. De weergave is een beetje misleidend. Deze is verkregen via de instructie


        If Not Request.QueryString("nom") Is Nothing Then nom = Request.QueryString("nom").ToString

De methode [ToString] heeft de tekenreeks [tintin,milou] gegenereerd, die vervolgens is weergegeven. Dit verhult het feit dat het object [Request.QueryString("nom")] in werkelijkheid een array van tekenreeksen is: {"tintin","milou"}. Het volgende voorbeeld illustreert dit. De presentatiepagina [main.aspx] ziet er als volgt uit:


<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<HTML>
    <HEAD>
        <title>Requête client</title>
    </HEAD>
    <body>
        <P>Informations passées par le client :</P>
        <form runat="server">
            <P>QueryString :</P>
            <P><asp:listbox id="lstQueryString" runat="server" EnableViewState="False" Rows="6"></asp:listbox></P>
            <P>Form :</P>
            <P><asp:listbox id="lstForm" runat="server" EnableViewState="False" Rows="2"></asp:listbox></P>
        </form>
    </body>
</HTML>

Er zijn enkele nieuwe elementen op deze pagina die gebruikmaken van zogenaamde serverbesturingselementen. Deze worden gekenmerkt door het attribuut [runat="server"]. Het is nog te vroeg om het begrip 'serverbesturingselement' te introduceren. Het volstaat te weten dat hier:

  • de pagina twee lijsten bevat (<asp:listbox>-tags)
  • dat deze lijsten objecten zijn (lstQueryString, lstForm) van het type [ListBox], die door de controller van de pagina worden aangemaakt
  • dat deze objecten alleen op de webserver bestaan. Op het moment van de respons worden ze omgezet in klassieke HTML-tags die de client kan begrijpen. Een [listbox]-object wordt dus omgezet (ook wel ‘gerenderd’ genoemd) in HTML-tags <select> en <option>.
  • Het belangrijkste voordeel van deze objecten is dat de presentatiecode wordt ontdaan van alle VB-code, die beperkt blijft tot de controller.

De controller [main.aspx.vb] die verantwoordelijk is voor het samenstellen van de twee objecten [lstQueryString] en [lstForm] ziet er als volgt uit:


Imports System.Collections
Imports System
Imports System.Collections.Specialized

Public Class main
    Inherits System.Web.UI.Page

    Protected infosQueryString As ArrayList
    Protected WithEvents lstQueryString As System.Web.UI.WebControls.ListBox
    Protected WithEvents lstForm As System.Web.UI.WebControls.ListBox
    Protected infosForm As ArrayList

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Init
        ' de verzoekgegevens van de klant worden opgeslagen in request.txt in de applicatiemap
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' de volledige verzameling gegevens uit QueryString wordt opgehaald
        infosQueryString = getValeurs(Request.QueryString)
        lstQueryString.DataSource = infosQueryString
        lstQueryString.DataBind()
        infosForm = getValeurs(Request.Form)
        lstForm.DataSource = infosForm
        lstForm.DataBind()
    End Sub

    Private Function getValeurs(ByRef data As NameValueCollection) As ArrayList
        ' in eerste instantie een lege lijst met gegevens
        Dim infos As New ArrayList
        ' we halen de sleutels uit de verzameling op
        Dim clés() As String = data.AllKeys
        ' we doorlopen de sleuteltabel
        Dim valeurs() As String
        For Each clé As String In clés
            ' waarden die aan de sleutel zijn gekoppeld
            valeurs = data.GetValues(clé)
            ' slechts één waarde?
            If valeurs.Length = 1 Then
                infos.Add(clé + "=" + valeurs(0))
            Else
                ' meerdere waarden
                For ivalue As Integer = 0 To valeurs.Length - 1
                    infos.Add(clé + "(" + ivalue.ToString + ")=" + valeurs(ivalue))
                Next
            End If
        Next
        ' het resultaat wordt weergegeven
        Return infos
    End Function
End Class

De belangrijkste punten van deze code zijn de volgende:

  • in [Form_Load] haalt de pagina de twee collecties [QueryString] en [Form] op. De pagina gebruikt een functie [getValeurs] om de inhoud van deze twee collecties in twee objecten van het type [ArrayList] te plaatsen, die tekenreeksen van het type [clé=valeur] bevatten als de sleutel van de collectie aan één enkele waarde is gekoppeld, of van het type [clé(i)=valeur] als de sleutel aan meerdere waarden is gekoppeld.
  • Elk van de objecten van het type [ArrayList] wordt vervolgens aan een van de objecten van het type [ListBox] op de presentatiepagina gekoppeld door middel van twee instructies:
    • [ListBox.DataSource=ArrayList] en [ListBox.DataBind]. Deze laatste instructie verplaatst de elementen van [DataSource] naar de collectie [Items] van het object [ListBox]

opgemerkt moet worden dat geen van beide objecten [ListBox] expliciet wordt aangemaakt door een bewerking [New]. Hieruit kan worden afgeleid dat bij aanwezigheid van de tag <asp:listbox id="xx">...<asp:listbox/>, de webserver zelf het object [ListBox] aanmaakt waarnaar wordt verwezen door het attribuut [id] van de tag.

  • De functie [getValeurs] verwerkt het object van het type [NameValueCollection] dat als parameter wordt doorgegeven om een resultaat van het type [ArrayList] te genereren.

We plaatsen de twee voorgaande bestanden in een map <application-path> en starten de Cassini-server met de parameters (<application-path>,/request2), waarna we de URL

[http://localhost/request2/main.aspx?nom=tintin&age=27] op. We krijgen het volgende antwoord:

Image

We roepen nu een URL op waarin de sleutel [nom] twee keer voorkomt:

Image

We zien dat het object [Request.QueryString("naam")) inderdaad een array was. Hier werden de verzoeken uitgevoerd via de methode GET. We gebruiken de client [curl] om een verzoek POST te doen:

E:\curl>curl --data nom=milou --data nom=tintin --data age=14 --data age=27 http://localhost/request2/main.aspx

<HTML>
        <HEAD>
                <title>Requête client</title>
        </HEAD>
        <body>
                <P>Informations passées par le client :</P>
                <form name="_ctl0" method="post" action="main.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwtMTI3MjA1MzUzMTs7PtCDC7NG4riDYIB4YjyGFpVAAviD" />

                        <P>QueryString :</P>
                        <P><select name="lstQueryString" size="6" id="lstQueryString">

</select></P>
                        <P>Form :</P>
                        <P><select name="lstForm" size="2" id="lstForm">
        <option value="nom(0)=milou">nom(0)=milou</option>
        <option value="nom(1)=tintin">nom(1)=tintin</option>
        <option value="age(0)=14">age(0)=14</option>
        <option value="age(1)=27">age(1)=27</option>

</select></P>
                </form>
        </body>
</HTML>

We zien dat de client inderdaad de standaard HTML-code ontvangt voor beide lijsten op de pagina. Er verschijnt informatie die we niet zelf hebben ingevoerd, zoals het verborgen veld [_VIEWSTATE]. Deze informatie is gegenereerd door de tags <asp:xx runat="server>. We zullen moeten leren hiermee om te gaan.

4.3. Implementatie van een MVC-architectuur

4.3.1. Het concept

Laten we dit lange hoofdstuk afsluiten met de implementatie van een applicatie die is opgebouwd volgens het MVC-model (Model-View-Controller). Een dergelijke webapplicatie die volgens dit model is opgebouwd, ziet er als volgt uit:

Image

  • de client stuurt zijn verzoeken naar een specifieke entiteit van de applicatie, de controller genaamd
  • de controller analyseert het verzoek van de client en zorgt ervoor dat het wordt uitgevoerd. Hierbij wordt hij bijgestaan door klassen die de bedrijfslogica van de applicatie bevatten en door klassen voor gegevenstoegang.
  • Afhankelijk van het resultaat van de uitvoering van het verzoek, kiest de controller ervoor om een bepaalde pagina als antwoord naar de klant te sturen

In ons model lopen alle verzoeken via één enkele controller, die de regisseur is van de gehele webapplicatie. Het voordeel van dit model is dat we in de controller alles kunnen bundelen wat vóór elk verzoek moet gebeuren. Stel bijvoorbeeld dat de applicatie authenticatie vereist. Dit gebeurt slechts één keer. Zodra de authenticatie is geslaagd, slaat de applicatie informatie over de gebruiker die zich zojuist heeft geauthenticeerd op in de sessie. Aangezien een client rechtstreeks een pagina van de applicatie kan oproepen zonder zich te authenticeren, moet elke pagina dus in de sessie controleren of de authenticatie daadwerkelijk heeft plaatsgevonden. Als alle verzoeken via één enkele controller lopen, kan deze die taak op zich nemen. De pagina’s waarnaar het verzoek eventueel wordt doorgestuurd, hoeven dit niet te doen.

4.3.2. Een applicatie MVC controleren zonder sessie

Op basis van wat we tot nu toe hebben gezien, zou je kunnen denken dat het bestand [global.asax] de rol van controller zou kunnen vervullen. We weten immers dat alle verzoeken via dit bestand lopen. Het is dus bij uitstek geschikt om alles te controleren. De volgende applicatie gebruikt het voor dit doel. Het virtuele pad ervan is [http://localhost/mvc1/main.aspx]. Om aan te geven wat hij wil, voegt de client achter de URL een parameter action=waarde toe. Afhankelijk van de waarde van de parameter [action] zal de controller [global.asax] het verzoek naar een specifieke pagina leiden:

  1. [main.aspx] als de parameter action niet is gedefinieerd of als action=main
  2. [action1.aspx] als action=action1
  3. [inconnu.aspx] als `action` niet onder geval 1 of 2 valt

De pagina's [main.aspx, action1.aspx, inconnu.aspx] geven alleen de waarde weer van [action] die ervoor heeft gezorgd dat ze worden weergegeven. Hieronder geven we een overzicht van de acht bestanden van deze applicatie en geven we waar nodig toelichting:

[global.asax]

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

[global.asax.vb]


Imports System
Imports System.Web
Imports System.Web.SessionState

Public Class Global
    Inherits System.Web.HttpApplication

    Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
        ' de uit te voeren actie wordt opgehaald
        Dim action As String
        If Request.QueryString("action") Is Nothing Then
            action = "main"
        Else
            action = Request.QueryString("action").ToString.ToLower
        End If
        ' we plaatsen de actie in de context van het verzoek
        Context.Items("action") = action
        ' de actie wordt uitgevoerd
        Select Case action
            Case "main"
                Server.Transfer("main.aspx", True)
            Case "action1"
                Server.Transfer("action1.aspx", True)
            Case Else
                Server.Transfer("inconnu.aspx", True)
        End Select
    End Sub
End Class

Belangrijke opmerkingen:

  • we onderscheppen alle verzoeken van de client in de procedure [Application_BeginRequest], die automatisch wordt uitgevoerd bij het starten van elk nieuw verzoek aan de applicatie.
  • In deze procedure hebben we toegang tot het object [Request], dat de afspiegeling is van het verzoek HTTP van de klant. Aangezien we een URL verwachten in de vorm [http://localhost/mvc1/main.aspx?action=xx], zoeken we naar een sleutel [action] in de verzameling [Request.QueryString]. Als deze daar niet aanwezig is, stellen we de actie standaard in op ‘main’.
  • De waarde van de parameter [action] wordt in het object [Context] geplaatst. Net als de objecten [Application, Session, Request, Response, Server] is dit object globaal en toegankelijk vanuit elke code. Dit object wordt van pagina naar pagina doorgegeven als de aanvraag door meerdere pagina’s wordt verwerkt, zoals hier het geval zal zijn. Het wordt verwijderd zodra het antwoord naar de klant is verzonden. De levensduur ervan is dus gelijk aan de verwerkingstijd van de aanvraag.
  • Afhankelijk van de waarde van de parameter [action] wordt het verzoek doorgestuurd naar de juiste pagina. Hiervoor wordt het globale object [Server] gebruikt, dat dankzij zijn methode het mogelijk maakt het huidige verzoek door te sturen naar een andere pagina. De eerste parameter is de naam van de doelpagina, de tweede een booleaanse waarde die aangeeft of de collecties [QueryString] en [Form] al dan niet naar de doelpagina moeten worden doorgestuurd. In dit geval is dat ja.

De bestanden [main.aspx] en [main.aspx.vb]:


<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<HTML>
    <head>
        <title>main</title></head>
    <body>
        <h3>Page [main]</h3>
        Action : <% =action %>
    </body>
</HTML>

Public Class main
    Inherits System.Web.UI.Page

    Protected action As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' de lopende actie wordt opgehaald
        action = Me.Context.Items("action").ToString
    End Sub
End Class

De controller [main.aspx.vb] haalt alleen de waarde van de sleutel [action] uit de context op; deze waarde wordt weergegeven door de presentatiecode. Hier wordt getoond hoe het object [Context] tussen verschillende pagina’s wordt doorgegeven die hetzelfde verzoek van de klant verwerken. De pagina’s [action1.aspx] en [inconnu.aspx] werken op dezelfde manier:

[action1.aspx]


<%@ Page src="action1.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="action1" %>
<HTML>
    <head>
        <title>action1</title></head>
    <body>
        <h3>Page [action1]</h3>
        Action : <% =action %>
    </body>
</HTML>

[action1.aspx.vb]

Public Class action1
    Inherits System.Web.UI.Page

    Protected action As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
         ' de lopende actie wordt opgehaald
        action = Me.Context.Items("action").ToString
    End Sub
End Class

[inconnu.aspx]


<%@ Page src="inconnu.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="inconnu" %>
<HTML>
    <head>
        <title>inconnu</title></head>
    <body>
        <h3>Page [inconnu]</h3>
        Action : <% =action %>
    </body>
</HTML>

[inconnu.aspx.vb]

Public Class inconnu
    Inherits System.Web.UI.Page

    Protected action As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
         ' de lopende actie wordt opgehaald
        action = Me.Context.Items("action").ToString
    End Sub
End Class

Om dit te testen, worden de bovenstaande documenten in een map <application-path> geplaatst en wordt Cassini gestart met de parameters (<application-path>,/mvc1). We roepen de URL [http://localhost/mvc1/main.aspx] op:

Image

Het verzoek heeft geen parameters verzonden [action]. De code van de applicatiecontroller [global.asax.vb] heeft de pagina [main.aspx] weergegeven. Nu vragen we de URL [http://localhost/mvc1/main.aspx?action=action1] op:

Image

De code van de applicatiecontroller [global.asax.vb] heeft de pagina [action1.aspx] gegenereerd. Nu vragen we de URL [http://localhost/mvc1/main.aspx?action=xx] op:

Image

De actie werd niet herkend en de controller [global.asax.vb] heeft de pagina [inconnu.aspx] weergegeven.

4.3.3. Een applicatie MVC met sessie controleren

Meestal moeten de verschillende verzoeken van een client voor een applicatie informatie met elkaar delen. We hebben een mogelijke oplossing voor dit probleem gezien: de te delen informatie opslaan in het object [Session] van het verzoek. Dit object wordt inderdaad door alle verzoeken gedeeld en kan informatie opslaan in de vorm (sleutel, waarde), waarbij de sleutel van het type [String] is en de waarde van elk type dat is afgeleid van [Object].

In het vorige voorbeeld werden de verschillende pagina’s die bij de verschillende acties horen, aangeroepen in de procedure [Application_BeginRequest] van het bestand [global.asax.vb]:


    Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
        ' de uit te voeren actie wordt opgehaald
        Dim action As String
        If Request.QueryString("action") Is Nothing Then
            action = "main"
        Else
            action = Request.QueryString("action").ToString.ToLower
        End If
        ' de actie wordt in de context van de aanvraag geplaatst
        Context.Items("action") = action
        ' de actie wordt uitgevoerd
        Select Case action
            Case "main"
                Server.Transfer("main.aspx", True)
            Case "action1"
                Server.Transfer("action1.aspx", True)
            Case Else
                Server.Transfer("inconnu.aspx", True)
        End Select
    End Sub

Het blijkt dat in de procedure [Application_BeginRequest] het object [Session] niet toegankelijk is. Hetzelfde geldt voor de pagina waarnaar de uitvoering wordt doorgestuurd. Dit model is daarom niet bruikbaar voor een applicatie met sessie. We kunnen elke pagina, bijvoorbeeld [default.aspx], de rol van controller laten vervullen. De bestanden [global.asax, global.asax.vb] verdwijnen dan en worden vervangen door de bestanden [default.aspx, default.aspx.vb]:

[default.aspx]

<%@ Page codebehind="default.aspx.vb" Inherits="vs.controleur" %>

[default.aspx.vb]


Imports System
Imports System.Web
Imports System.Web.SessionState

Public Class controleur
    Inherits System.Web.UI.Page

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' de uit te voeren actie wordt opgehaald
        Dim action As String
        If Request.QueryString("action") Is Nothing Then
            action = "main"
        Else
            action = Request.QueryString("action").ToString.ToLower
        End If

        ' de actie wordt in de context van de aanvraag geplaatst
        Context.Items("action") = action
        ' de vorige actie wordt opgehaald, indien deze bestaat
        Context.Items("actionPrec") = Session.Item("actionPrec")
        If Context.Items("actionPrec") Is Nothing Then Context.Items("actionPrec") = ""
        ' de huidige actie wordt in de sessie opgeslagen
        Session.Item("actionPrec") = action

        ' de actie wordt uitgevoerd
        Select Case action
            Case "main"
                Server.Transfer("main.aspx", True)
            Case "action1"
                Server.Transfer("action1.aspx", True)
            Case Else
                Server.Transfer("inconnu.aspx", True)
        End Select
    End Sub
End Class

Om het sessiemechanisme te illustreren, zullen de verschillende pagina's naast de huidige actie ook de voorgaande actie weergeven. Voor een reeks acties A1, A2, ..., An, wanneer actie Ai plaatsvindt, zal de bovenstaande controller:

  • de huidige actie Ai in de context plaatsen
  • zoekt in de sessie de voorgaande actie Ai-1 op. Indien deze niet aanwezig is (zoals bij actie A1), wordt de lege tekenreeks als voorgaande actie gebruikt.
  • plaatst de huidige actie Ai in de sessie ter vervanging van Ai-1
  • draagt de uitvoering over aan de juiste pagina

De drie pagina’s van de applicatie zijn de volgende:

[main.aspx]


<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<HTML>
    <HEAD>
        <title>main</title>
    </HEAD>
    <body>
        <h3>Page [main]</h3>
        Action courante :
        <% =action %>
        <br>
        Action précédente :
        <% =actionPrec %>
    </body>
</HTML>

[action1.aspx]


<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<HTML>
    <head>
        <title>action1</title></head>
    <body>
        <h3>Page [action1]</h3>
        Action courante :
        <% =action %>
        <br>
        Action précédente :
        <% =actionPrec %>
    </body>
</HTML>

[inconnu.aspx]


<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<HTML>
    <head>
        <title>inconnu</title>
    </head>
    <body>
        <h3>Page [inconnu]</h3>
        Action courante :
        <% =action %>
        <br>
        Action précédente :
        <% =actionPrec %>
    </body>
</HTML>

Omdat de drie pagina's dezelfde informatie [action, actionPrec] weergeven, kunnen ze alle drie dezelfde paginacontroller hebben. We hebben ze daarom allemaal afgeleid van de klasse [main] uit het bestand [main.aspx.vb]:


Public Class main
    Inherits System.Web.UI.Page

    Protected action As String
    Protected actionPrec As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' de huidige actie wordt opgehaald
        action = Me.Context.Items("action").ToString
        ' en de vorige actie
        actionPrec = Me.Context.Items("actionPrec").ToString
    End Sub
End Class

De bovenstaande code haalt alleen de informatie op die door de controller van de applicatie [default.aspx.vb] in de context is geplaatst.

Al deze bestanden worden in <application-path> geplaatst en Cassini wordt gestart met de parameters (<application-path>,/mvc2). Eerst wordt de URL [http://localhost/mvc2] opgevraagd:

Image

De URL [http://localhost/mvc2] verwijst naar een map. We weten dat in dit geval het document [default.aspx] uit deze map door de server wordt teruggestuurd, indien het bestaat. Hier was geen actie gespecificeerd. Daarom is de actie [main] uitgevoerd. Laten we verdergaan met de actie [action1]:

Image

De huidige actie en de vorige actie zijn correct geïdentificeerd. Laten we verdergaan met actie [xx]:

Image

4.4. Conclusion

We beschikken nu over de basiselementen waarop elke ASP.ET-toepassing is gebaseerd. Er moet echter nog één belangrijk begrip worden geïntroduceerd: dat van het formulier. Dit is het onderwerp van het volgende hoofdstuk.