1. Deel 1
De PDF van het document is beschikbaar |HIER|.
De voorbeelden uit het document zijn beschikbaar |HIER|.
1.1. Introduction
Doelstellingen van het artikel:
- een webapplicatie met drie lagen schrijven [interface utilisateur, métier, accès aux données]
- de applicatie configureren met Spring IOC
- verschillende versies schrijven door de implementatie van een van de drie lagen te wijzigen.
Gebruikte tools:
- Visual Studio.net voor de ontwikkeling – zie bijlage, paragraaf 3.1;
- Cassini-webserver voor de uitvoering – zie bijlage paragraaf 3.2;
- Nunit voor unit-tests – zie bijlage paragraaf 3.4;
- Spring voor de integratie en configuratie van de lagen van de webapplicatie – zie bijlage paragraaf 3.3;
Op een schaal van beginner-gemiddeld-gevorderd bevindt dit document zich in het gedeelte [intermédiaire-avancé]. Om het te kunnen begrijpen, zijn diverse voorkennisvereisten nodig. Sommige daarvan kunnen worden verkregen uit documenten die ik heb geschreven. In dat geval vermeld ik ze. Het spreekt voor zich dat dit slechts een suggestie is en dat de lezer zijn eigen favoriete documenten kan gebruiken.
- taal VB.net: [Inleiding tot de taal VB.NET aan de hand van voorbeelden (2004)];
- webprogrammering in VB.net: [Webontwikkeling met ASP.NET 1.1 (2004)Webontwikkeling met ASP.NET 1.1 (2004)];
- gebruik van de IoC-component van Spring: [Spring IoC voor .NET (2005)] ;
- documentatie Spring.net: [Spring.NET | Homepage ]
Dit document volgt de rode draad van een document dat voor Java [Drielaagse architecturen en MVC-architecturen met Struts, Spring en Java (2005)] is geschreven. We bouwen in VB.NET de in Java geschreven drielaagse webapplicatie MVC. Het idee dat we hier willen overbrengen, is dat de ontwikkelingsplatforms Java en .NET voldoende op elkaar lijken, zodat de vaardigheden die in een van deze twee domeinen zijn opgedaan, in het andere kunnen worden hergebruikt.
Er lijkt geen algemeen erkende MVC-ontwikkelingsoplossing te bestaan. De volgende oplossing volgt de methode die in het document [Webontwikkeling met ASP.NET 1.1 (2004)] is geïntroduceerd. Hoewel deze methode het voordeel heeft dat ze gebruikmaakt van concepten die gangbaar zijn in de J2EE-ontwikkeling, moet ze echter worden gezien voor wat ze is: c.a.d. een van de vele methoden voor MVC-ontwikkeling. Zodra een ontwikkelingsmethode MVC in ASP.NET algemeen aanvaard is, zal deze moeten worden overgenomen. De .NET-versie van Spring, die momenteel in ontwikkeling is, zou wel eens een eerste oplossing kunnen zijn.
1.2. De webapplicatie „articles“
Hier presenteren we de onderdelen van een vereenvoudigde webapplicatie voor e-commerce. Hiermee kunnen webklanten:
- een lijst met artikelen uit een database te bekijken
- bepaalde artikelen in een elektronisch winkelmandje te plaatsen
- deze te bevestigen. Deze bevestiging heeft als enig effect dat de voorraad van de gekochte artikelen in de database wordt bijgewerkt.
De gebruiker krijgt de volgende weergaven te zien:
- de weergave "LISTE" met een lijst van artikelen die te koop zijn ![]() | - de weergave [INFOS], die aanvullende informatie over een product geeft: ![]() |
- de weergaven [PANIER] en [PANIERVIDE], die de inhoud van het winkelmandje van de klant weergeven
![]() | ![]() |
- de weergave [ERREURS] die eventuele fouten in de applicatie signaleert

1.3. Algemene architectuur van de applicatie
We willen een applicatie bouwen met de volgende drielaagse structuur:
![]() |
- de drie lagen zijn onafhankelijk van elkaar gemaakt door het gebruik van interfaces
- de integratie van de verschillende lagen wordt gerealiseerd door Spring
- Elke laag heeft een eigen naamruimte: web (laag UI), domain (bedrijfslaag) en dao (gegevenslaag).
De applicatie zal een MVC-architectuur (Model - View - Controller) volgen. Als we het bovenstaande lagenmodel weergeven, past de MVC-architectuur daar als volgt in:
![]() |
De verwerking van een verzoek van een klant verloopt volgens de volgende stappen:
- de klant doet een verzoek aan de controller. Deze controller is in dit geval een .aspx-pagina die een specifieke rol vervult. Alle verzoeken van klanten komen hier langs. Dit is de toegangspoort tot de applicatie. Dit is de C van MVC.
- de controller verwerkt dit verzoek. Hiervoor kan hij de hulp nodig hebben van de businesslaag, het zogenaamde M-model in de structuur MVC.
- De controller ontvangt een antwoord van de businesslaag. Het verzoek van de klant is verwerkt. Dit kan verschillende mogelijke antwoorden opleveren. Een klassiek voorbeeld is
- een foutpagina als het verzoek niet correct kon worden verwerkt
- een bevestigingspagina in het andere geval
- de controller kiest welk antwoord (= weergave) naar de klant moet worden verzonden. Dit is meestal een pagina met dynamische elementen. De controller levert deze aan de weergave.
- De weergave wordt naar de klant verzonden. Dit is de V van MVC.
1.4. Het model
Hier bekijken we de M van MVC. Het model bestaat hier uit de volgende elementen:
- de businessklassen
- de klassen voor gegevenstoegang
- de database
1.4.1. De database
De database bevat slechts één tabel met de naam ARTICLES. Deze is gegenereerd met de volgende SQL-opdrachten:
CREATE TABLE ARTICLES (
ID INTEGER NOT NULL,
NOM VARCHAR(20) NOT NULL,
PRIX NUMERIC(15,2) NOT NULL,
STOCKACTUEL INTEGER NOT NULL,
STOCKMINIMUM INTEGER NOT NULL
);
/* beperkingen */
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_ID check (ID>0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_PRIX check (PRIX>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKACTUEL check (STOCKACTUEL>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKMINIMUM check (STOCKMINIMUM>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_NOM check (NOM<>'');
ALTER TABLE ARTICLES ADD CONSTRAINT UNQ_NOM UNIQUE (NOM);
/* primaire sleutel */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
primaire sleutel die een artikel op unieke wijze identificeert | |
naam van het artikel | |
de prijs | |
huidige voorraad | |
de voorraad onder welke een herbevoorradingsorder moet worden geplaatst |
1.4.2. De naamruimten van het model
Het model M wordt hier geleverd in de vorm van twee naamruimten:
- istia.st.articles.dao: bevat de klassen voor toegang tot de gegevens van de laag [dao]
- istia.st.articles.domain: bevat de bedrijfsspecifieke klassen van de laag [domain]
Elk van deze naamruimten wordt gegenereerd in een eigen „assembly”-bestand:
contenu | rôle | |
- [IArticlesDao]: de interface voor toegang tot de laag [dao]. Dit is de enige interface die de laag [domain] ziet. Ze ziet geen andere. - [Article]: klasse die een artikel definieert - [ArticlesDaoArrayList]: implementatieklasse van de interface [IArticlesDao] met een klasse [ArrayList] | toegangslaag - bevindt zich volledig in de [dao] van de 3-tier-architectuur van de webapplicatie | |
- [IArticlesDomain]: de interface voor toegang tot de laag [domain]. Dit is de enige interface die de weblaag ziet. De weblaag ziet geen andere interfaces. - [AchatsArticles]: een klasse die [IArticlesDomain] implementeert - [Achat]: klasse die de aankoop van een klant vertegenwoordigt - [Panier]: klasse die alle aankopen van een klant vertegenwoordigt | vertegenwoordigt het model van de aankopen op het web - bevindt zich volledig in de [domain]-laag van de architectuur 3-tier-architectuur van de webapplicatie |
1.4.3. De laag [dao]
De laag [dao] bevat de volgende elementen:
-
[IArticlesDao]: de interface voor toegang tot de laag [dao]
-
[Article]: klasse die een artikel definieert
-
[ArticlesDaoArrayList]: implementatieklasse van de interface [IArticlesDao] met een klasse [ArrayList]
De structuur van het project [Visual Studio] van de laag [dao] is als volgt:

Opmerkingen:
- het project [dao] is van het type [bibliothèque de classes]
- De klassen zijn in een boomstructuur onder de map [istia] geplaatst. Ze bevinden zich allemaal in de naamruimte [istia.st.articles.dao].
1.4.3.1. De klasse [Article]
De klasse die een artikel definieert, is de volgende:
Imports System
Namespace istia.st.articles.dao
Public Class Article
' privévelden
Private _id As Integer
Private _nom As String
Private _prix As Double
Private _stockactuel As Integer
Private _stockminimum As Integer
' artikel-id
Public Property id() As Integer
Get
Return _id
End Get
Set(ByVal Value As Integer)
If Value <= 0 Then
Throw New Exception("Le champ id [" + Value.ToString + "] est invalide")
End If
Me._id = Value
End Set
End Property
' artikelname
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
If Value Is Nothing OrElse Value.Trim.Equals("") Then
Throw New Exception("Le champ nom [" + Value + "] est invalide")
End If
Me._nom = Value
End Set
End Property
' artikelprijs
Public Property prix() As Double
Get
Return _prix
End Get
Set(ByVal Value As Double)
If Value < 0 Then
Throw New Exception("Le champ prix [" + Value.ToString + "] est invalide")
End If
Me._prix = Value
End Set
End Property
' huidige voorraad artikel
Public Property stockactuel() As Integer
Get
Return _stockactuel
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New Exception("Le champ stockActuel [" + Value.ToString + "] est invalide")
End If
Me._stockactuel = Value
End Set
End Property
' minimale voorraad artikel
Public Property stockminimum() As Integer
Get
Return _stockminimum
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New Exception("Le champ stockMinimum [" + Value.ToString + "] est invalide")
End If
Me._stockminimum = Value
End Set
End Property
' standaardfabrikant
Public Sub New()
End Sub
' fabrikant met eigenschappen
Public Sub New(ByVal id As Integer, ByVal nom As String, ByVal prix As Double, ByVal stockactuel As Integer, ByVal stockminimum As Integer)
Me.id = id
Me.nom = nom
Me.prix = prix
Me.stockactuel = stockactuel
Me.stockminimum = stockminimum
End Sub
' methode voor artikelidentificatie
Public Overrides Function ToString() As String
Return "[" + id.ToString + "," + nom + "," + prix.ToString + "," + stockactuel.ToString + "," + stockminimum.ToString + "]"
End Function
End Class
End Namespace
Deze klasse biedt:
- een constructor waarmee de 5 gegevens van een artikel kunnen worden vastgelegd: [id, nom, prix, stockactuel, stockminimum]
- openbare eigenschappen waarmee de 5 gegevens kunnen worden gelezen en geschreven.
- een controle van de gegevens die in het artikel worden ingevoerd. Bij onjuiste gegevens wordt er een uitzondering gegenereerd.
- een methode toString waarmee de waarde van een artikel als tekenreeks kan worden opgehaald. Dit is vaak handig bij het debuggen van een applicatie.
1.4.3.2. De interface [IArticlesDao]
De interface [IArticlesDao] is als volgt gedefinieerd:
Imports System
Imports System.Collections
Namespace istia.st.articles.dao
Public Interface IArticlesDao
' lijst van alle artikelen
Function getAllArticles() As IList
' voegt een artikel toe
Function ajouteArticle(ByVal unArticle As Article) As Integer
' verwijdert een artikel
Function supprimeArticle(ByVal idArticle As Integer) As Integer
' een artikel wijzigen
Function modifieArticle(ByVal unArticle As Article) As Integer
' een artikel zoeken
Function getArticleById(ByVal idArticle As Integer) As Article
' verwijdert alle artikelen
Sub clearAllArticles()
' wijzigt de voorraad van een artikel
Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
End Interface
End Namespace
De verschillende methoden van de interface hebben de volgende functies:
haalt alle artikelen uit de gegevensbron op | |
maakt de gegevensbron leeg | |
haalt het object [Article] op, geïdentificeerd aan de hand van de primaire sleutel | |
maakt het mogelijk een artikel aan de gegevensbron toe te voegen | |
maakt het mogelijk een artikel in de gegevensbron te wijzigen | |
hiermee kun je een artikel uit de gegevensbron verwijderen | |
hiermee kunt u de voorraad van een artikel in de gegevensbron wijzigen |
De interface stelt clientprogramma's een aantal methoden ter beschikking die uitsluitend door hun handtekeningen worden gedefinieerd. De interface houdt zich niet bezig met de manier waarop deze methoden daadwerkelijk worden geïmplementeerd. Dit zorgt voor flexibiliteit in een toepassing. Het clientprogramma doet zijn aanroepen op een interface en niet op een specifieke implementatie daarvan.
![]() |
De keuze voor een specifieke implementatie gebeurt via een Spring-configuratiebestand. Om aan te tonen dat voor het testen van de webapplicatie alleen de interface voor gegevenstoegang van belang is en niet de implementatieklasse ervan, gaan we de gegevensbron eerst implementeren met een eenvoudig object [ArrayList]. Later zullen we een oplossing op basis van SGBD presenteren.
1.4.3.3. De implementatieklasse [ArticlesDaoArrayList]
De implementatieklasse [ArticlesDaoArrayList] is als volgt gedefinieerd:
Imports System
Imports System.Collections
Namespace istia.st.articles.dao
Public Class ArticlesDaoArrayList
Implements istia.st.articles.dao.IArticlesDao
Private articles As New ArrayList
Private Const nbArticles As Integer = 4
' standaardfabrikant
Public Sub New()
' er worden enkele artikelen vervaardigd
For i As Integer = 1 To nbArticles
articles.Add(New Article(i, "article" + i.ToString, i * 10, i * 10, i * 10))
Next
End Sub
' lijst van alle artikelen
Public Function getAllArticles() As IList Implements IArticlesDao.getAllArticles
' de lijst met artikelen wordt weergegeven
SyncLock Me
Return articles
End SyncLock
End Function
' alle artikelen verwijderen
Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
' de lijst met artikelen leegmaken
SyncLock Me
articles.Clear()
End SyncLock
End Sub
' een artikel ophalen op basis van de sleutel
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
' het artikel wordt in de lijst gezocht
SyncLock Me
Dim ipos As Integer = posArticle(articles, idArticle)
If ipos <> -1 Then
Return CType(articles(ipos), Article)
Else
Return Nothing
End If
End SyncLock
End Function
' een artikel toevoegen aan de lijst met artikelen
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
' het artikel wordt aan de artikellijst toegevoegd
SyncLock Me
' er wordt gecontroleerd of het artikel nog niet bestaat
Dim ipos As Integer = posArticle(articles, unArticle.id)
If ipos <> -1 Then
Throw New Exception("L'article d'id [" + unArticle.id.ToString + "] existe déjà")
End If
' het artikel wordt toegevoegd
articles.Add(unArticle)
' het resultaat wordt weergegeven
Return 1
End SyncLock
End Function
' een artikel wijzigen
Public Function modifieArticle(ByVal articleNouveau As Article) As Integer Implements IArticlesDao.modifieArticle
' een artikel wordt gewijzigd
SyncLock Me
' controleer of het bestaat
Dim ipos As Integer = posArticle(articles, articleNouveau.id)
' als het niet bestaat
If ipos = -1 Then Return 0
' het bestaat – we wijzigen het
articles(ipos) = articleNouveau
' het resultaat wordt weergegeven
Return 1
End SyncLock
End Function
' een artikel verwijderen op basis van de sleutel
Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
' een artikel verwijderen
SyncLock Me
' controleer of het bestaat
Dim ipos As Integer = posArticle(articles, idArticle)
' als het niet bestaat
If ipos = -1 Then Return 0
' het bestaat – het wordt verwijderd
articles.RemoveAt(ipos)
' het resultaat wordt weergegeven
Return 1
End SyncLock
End Function
' de voorraad van een artikel, geïdentificeerd aan de hand van zijn sleutel, wijzigen
Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
' de voorraad van een artikel wijzigen
SyncLock Me
' controleren of het bestaat
Dim ipos As Integer = posArticle(articles, idArticle)
' als het niet bestaat
If ipos = -1 Then Return 0
' het artikel bestaat – de voorraad wordt aangepast indien mogelijk
Dim unArticle As Article = CType(articles(ipos), Article)
' de voorraad wordt alleen aangepast als deze voldoende is
If unArticle.stockactuel + mouvement >= 0 Then
unArticle.stockactuel += mouvement
Return 1
Else
Return 0
End If
End SyncLock
End Function
' zoek een artikel op basis van de sleutel
Private Function posArticle(ByVal listArticles As ArrayList, ByVal idArticle As Integer) As Integer
' geeft de positie van het artikel [idArticle] in de lijst weer of -1 als het niet is gevonden
Dim unArticle As Article
For i As Integer = 0 To listArticles.Count - 1
unArticle = CType(listArticles(i), Article)
If unArticle.id = idArticle Then
Return i
End If
Next
' niet gevonden
Return -1
End Function
End Class
End Namespace
Opmerkingen:
- de gegevensbron wordt gesimuleerd door het privéveld [articles] van het type [ArrayList]
- De constructor van de klasse maakt standaard 4 records aan in de gegevensbron.
- Alle methoden voor gegevenstoegang zijn gesynchroniseerd om problemen met gelijktijdige toegang tot de gegevensbron te voorkomen. Op elk moment heeft slechts één thread toegang tot een bepaalde methode.
- Met de methode [posArticle] kan de positie [0..N] in de bron [ArrayList] worden bepaald van een artikel dat wordt geïdentificeerd aan de hand van zijn nummer. Als het artikel niet bestaat, retourneert de methode de positie -1. Deze methode wordt herhaaldelijk door de andere methoden gebruikt.
- Met de methode [ajouteArticle] kan een artikel aan de artikellijst worden toegevoegd. De methode retourneert het aantal toegevoegde artikelen: 1. Als het artikel al bestond, wordt er een uitzondering gegenereerd.
- Met de methode [modifieArticle] kan een bestaand artikel worden gewijzigd. Deze methode retourneert het aantal gewijzigde artikelen: 1 als het artikel bestond, 0 anders.
- Met de methode [supprimeArticle] kun je een bestaand artikel verwijderen. Deze methode geeft het aantal verwijderde artikelen terug: 1 als het artikel bestond, 0 anders.
- De methode [getAllArticles] geeft de lijst met alle artikelen weer
- Met de methode [getArticleById] kunt u een artikel opvragen dat wordt geïdentificeerd aan de hand van zijn nummer. U krijgt de waarde [nothing] als het artikel niet bestaat.
- De code stelt geen echte moeilijkheden. We laten het aan de lezer over om deze door te nemen en te begrijpen.
1.4.3.4. Genereren van de assembly van de laag [dao]
Het Visual Studio-project is geconfigureerd om de assembly [webarticles-dao.dll] te genereren. Deze wordt gegenereerd in de map [bin] van het project:
![]() | ![]() |
1.4.3.5. NUnit-tests van de laag [dao]
In Java worden de klassen getest met het framework [Junit]. In .NET biedt het Nunit-framework dezelfde mogelijkheden voor unit-tests:

De structuur van het Visual Studio-testproject is als volgt:

Opmerkingen:
- het project [tests] is van het type [bibliothèque de classes]
- de tests [NUnit] vereisen een verwijzing naar de assembly [nunit.framework.dll]
- de testklasse [NUnit] haalt via Spring een instantie van het te testen object op. Daarom bevinden zich
- in de map [bin] de Spring-klassebestanden
- in [References] een verwijzing naar de assembly [Spring-Core.dll] uit de map [bin]
- in [bin] een configuratiebestand voor Spring
- de testklasse heeft de assembly [webarticles-dao.dll] uit de laag [dao] nodig. Deze is in de map [bin] geplaatst en de verwijzing ernaar is toegevoegd aan de projectverwijzingen.
Een testklasse [NUnit] heeft toegang nodig tot de klassen van de naamruimte [NUnit.Framework]. Daarin staat dus de volgende importinstructie:
De naamruimte [NUnit.Framework] bevindt zich in de „assembly“ [nunit.framework.dll], die aan de projectreferenties moet worden toegevoegd:
![]() | ![]() |
De assembly [nunit.framework.dll] moet in de voorgestelde lijst staan als [Nunit] is geïnstalleerd. Dubbelklik op de assembly om deze aan het project toe te voegen:

De testklasse [NUnit] van de laag [dao] zou er als volgt uit kunnen zien:
Imports System
Imports System.Collections
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports System.Threading
Imports Spring.Objects.Factory.Xml
Imports System.IO
Namespace istia.st.articles.tests
<TestFixture()> _
Public Class NunitTestArticlesArrayList
' het te testen object
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' er wordt een instantie opgehaald van de Spring-objectgenerator
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
' we vragen om het instantiëren van het object articlesdao
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
<Test()> _
Public Sub testGetAllArticles()
' visuele controle
listArticles()
End Sub
<Test()> _
Public Sub testClearAllArticles()
' alle artikelen worden verwijderd
articlesDao.clearAllArticles()
' alle artikelen opvragen
Dim articles As IList = articlesDao.getAllArticles
' controle: er moeten er 0 zijn
Assert.AreEqual(0, articles.Count)
End Sub
<Test()> _
Public Sub testAjouteArticle()
' alle artikelen verwijderen
articlesDao.clearAllArticles()
' controle: de artikeltabel moet leeg zijn
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' er worden twee artikelen toegevoegd
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' controle: er moeten twee artikelen zijn
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' visuele controle
listArticles()
End Sub
<Test()> _
Public Sub testSupprimeArticle()
' alle artikelen verwijderen
articlesDao.clearAllArticles()
' controle: de artikeltabel moet leeg zijn
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' er worden twee artikelen toegevoegd
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' controle: er moeten 2 artikelen zijn
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' artikel 4 wordt verwijderd
articlesDao.supprimeArticle(4)
' controle: er moet 1 artikel overblijven
articles = articlesDao.getAllArticles
Assert.AreEqual(1, articles.Count)
' visuele controle
listArticles()
End Sub
<Test()> _
Public Sub testModifieArticle()
' alle artikelen verwijderen
articlesDao.clearAllArticles()
' controle
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' 2 artikelen toevoegen
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' controle
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' artikel 3 zoeken
Dim unArticle As Article = articlesDao.getArticleById(3)
' controle
Assert.AreEqual(unArticle.nom, "article3")
' artikel 4 zoeken
unArticle = articlesDao.getArticleById(4)
' controle
Assert.AreEqual(unArticle.nom, "article4")
' wijziging artikel 4
articlesDao.modifieArticle(New Article(4, "article4", 44, 44, 44))
' controle
unArticle = articlesDao.getArticleById(4)
Assert.AreEqual(unArticle.prix, 44, 0.000001)
' visuele controle
listArticles()
End Sub
<Test()> _
Public Sub testGetArticleById()
' artikelen verwijderen
articlesDao.clearAllArticles()
' controle
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' toevoeging van 2 artikelen
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' controle
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' artikel 3 zoeken
Dim unArticle As Article = articlesDao.getArticleById(3)
' controle
Assert.AreEqual(unArticle.nom, "article3")
' artikel 4 zoeken
unArticle = articlesDao.getArticleById(4)
' controle
Assert.AreEqual(unArticle.nom, "article4")
End Sub
' schermweergave
Private Sub listArticles()
Dim articles As IList = articlesDao.getAllArticles
For i As Integer = 0 To articles.Count - 1
Console.WriteLine(CType(articles(i), Article).ToString)
Next
End Sub
<Test()> _
Public Sub testArticleAbsent()
' alle artikelen verwijderen
articlesDao.clearAllArticles()
' artikel zoeken 1
Dim article As article = articlesDao.getArticleById(1)
' controle
Assert.IsNull(article)
' wijziging van een niet-bestaand artikel
Dim i As Integer = articlesDao.modifieArticle(New article(1, "1", 1, 1, 1))
' heeft geen regels gewijzigd
Assert.AreEqual(i, 0)
' verwijdering van een niet-bestaand artikel
i = articlesDao.supprimeArticle(1)
' moest geen regel verwijderen
Assert.AreEqual(0, i)
End Sub
<Test()> _
Public Sub testChangerStockArticle()
' alle artikelen verwijderen
articlesDao.clearAllArticles()
' toevoeging van een artikel
Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
Assert.AreEqual(nbArticles, 1)
' een artikel toegevoegd
nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
Assert.AreEqual(nbArticles, 1)
' 100 threads aangemaakt
Dim taches(99) As Thread
For i As Integer = 0 To taches.Length - 1
' de thread i wordt aangemaakt
taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
' de naam van de thread wordt vastgelegd
taches(i).Name = "tache_" & i
' de thread i wordt gestart
taches(i).Start()
Next
' wachten tot alle threads zijn voltooid
For i As Integer = 0 To taches.Length - 1
taches(i).Join()
Next
' controles – artikel 3 moet een voorraad van 1 hebben
Dim unArticle As Article = articlesDao.getArticleById(3)
Assert.AreEqual(unArticle.nom, "article3")
Assert.AreEqual(1, unArticle.stockactuel)
' de voorraad van artikel 4 wordt verlaagd
Dim erreur As Boolean = False
Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
' controle: de voorraad mag niet zijn veranderd
Assert.AreEqual(0, nbLignes)
' visuele controle
listArticles()
End Sub
Public Sub décrémente()
' thread gestart
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
' thread verlaagt de voorraad
articlesDao.changerStockArticle(3, -1)
' thread voltooid
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
End Sub
End Class
End Namespace
Opmerkingen:
- we wilden een testprogramma schrijven voor de interface [IArticlesDao] dat onafhankelijk is van de implementatieklasse ervan. Daarom hebben we Spring gebruikt om de naam van de implementatieklasse voor het testprogramma te verbergen.
- De attribuutmethode <Setup()> haalt bij Spring een verwijzing op naar het te testen object [articlesdao]. Dit object is gedefinieerd in het volgende bestand [spring-config.xml]:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
<object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoArrayList, webarticles-dao"/>
</objects>
Dit bestand geeft de naam aan van de implementatieklasse [istia.st.articles.dao.ArticlesDaoArrayList] van de interface [IArticlesDao] en waar deze te vinden is: [ webarticles-dao.dll]. Aangezien het instantiëren geen parameters vereist, zijn hier geen parameters gedefinieerd.
- De meeste tests zijn eenvoudig te begrijpen. De lezer wordt verzocht de commentaren te lezen.
- De methode [testChangerStockArticle] behoeft enige uitleg. Deze methode maakt 100 threads aan die de voorraad van een bepaald artikel moeten verlagen.
<Test()> _
Public Sub testChangerStockArticle()
' alle artikelen verwijderen
articlesDao.clearAllArticles()
' artikel toegevoegd
Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
Assert.AreEqual(nbArticles, 1)
' artikel toevoegen
nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
Assert.AreEqual(nbArticles, 1)
' 100 threads aanmaken
Dim taches(99) As Thread
For i As Integer = 0 To taches.Length - 1
' de thread i wordt aangemaakt
taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
' de naam van de thread wordt vastgelegd
taches(i).Name = "tache_" & i
' de thread i wordt gestart
taches(i).Start()
Next
' wachten tot alle threads zijn voltooid
For i As Integer = 0 To taches.Length - 1
taches(i).Join()
Next
' controles – artikel 3 moet een voorraad van 1 hebben
Dim unArticle As Article = articlesDao.getArticleById(3)
Assert.AreEqual(unArticle.nom, "article3")
Assert.AreEqual(1, unArticle.stockactuel)
' de voorraad van artikel 4 wordt verlaagd
Dim erreur As Boolean = False
Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
' controle: de voorraad mag niet zijn veranderd
Assert.AreEqual(0, nbLignes)
' visuele controle
listArticles()
End Sub
Hier gaat het om het testen van gelijktijdige toegang tot de gegevensbron. De methode die de voorraad bijwerkt, is als volgt:
Public Sub décrémente()
' thread gestart
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
' thread verlaagt de voorraad
articlesDao.changerStockArticle(3, -1)
' thread voltooid
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
End Sub
De voorraad van artikel nr. 3 wordt met één eenheid verminderd. Als we kijken naar de code van de methode [testChangerStockArticle], zien we dat:
- de voorraad van artikel nr. 3 wordt ingesteld op 101
- de 100 threads zullen deze voorraad elk met één eenheid verminderen
- dus moet de voorraad aan het einde van de uitvoering van alle threads 1 bedragen
Bovendien wordt, nog steeds in deze methode, geprobeerd de voorraad van artikel nr. 4 op een negatieve waarde te zetten. Dit moet mislukken.
Om de laag [dao] te testen, genereren we de DLL [tests-webarticles-dao.dll] in de map [bin] van het project [tests]:
![]() | ![]() |
Vervolgens laden we met behulp van de applicatie [Nunit-Gui] dit bestand DLL en voeren we de tests uit:

In het linkervenster zien we de lijst met geteste methoden. De kleur van het puntje voor de naam van elke methode geeft aan of de methode geslaagd (groen) of mislukt (rood) is. De lezer die dit document op het scherm bekijkt, zal zien dat alle tests zijn geslaagd. Vervolgens gaan we ervan uit dat we een operationele laag [dao] hebben.
1.4.4. De laag [domain]
De laag [domain] bevat de volgende elementen:
-
[IArticlesDomain]: de interface voor toegang tot de laag [domain]
-
[Achat]: klasse die een aankoop definieert
-
[Panier]: klasse die een winkelmandje definieert
-
[AchatsArticles]: implementatieklasse van de interface [IArticlesDomain]
De structuur van de oplossing [Visual Studio] van de laag [domain] is als volgt:

Opmerkingen:
- het project [domain] is van het type [bibliothèque de classes]
- de klassen zijn in een boomstructuur onder de hoofdmap [istia] geplaatst. Ze bevinden zich allemaal in de naamruimte [istia.st.articles.domain].
- de DLL van de laag [dao] is in de map [bin] van het nieuwe project geplaatst. Daarnaast is deze DLL als referentie aan het project toegevoegd.
1.4.4.1. De interface [IArticlesDomain]
De interface [IArticlesDomain] ontkoppelt de laag [métier] van de laag [web]. Deze laatste krijgt via deze interface toegang tot de laag [métier/domain], zonder zich zorgen te maken over de klasse die deze daadwerkelijk implementeert. De interface definieert de volgende acties voor toegang tot de bedrijfslaag:
Imports Article = istia.st.articles.dao.Article
Namespace istia.st.articles.domain
Public Interface IArticlesDomain
' methoden
Sub acheter(ByVal panier As Panier)
Function getAllArticles() As IList
Function getArticleById(ByVal idArticle As Integer) As Article
ReadOnly Property erreurs() As ArrayList
End Interface
End Namespace
geeft de objectenlijst [Article] van de bijbehorende gegevensbron weer | |
geeft het object [Article] weer, geïdentificeerd door [idArticle] | |
valideert het winkelmandje van de klant door de voorraad van de gekochte artikelen met de gekochte hoeveelheid te verminderen – kan mislukken als de voorraad onvoldoende is | |
geeft de lijst met opgetreden fouten weer – leeg als er geen fouten zijn |
1.4.4.2. De klasse [Achat]
De klasse [Achat] vertegenwoordigt een aankoop van de klant:
Imports istia.st.articles.dao
Namespace istia.st.articles.domain
Public Class Achat
' privévelden
Private _article As article
Private _qte As Integer
' standaardconstructor
Public Sub New()
End Sub
' constructor met parameters
Public Sub New(ByVal unArticle As article, ByVal qte As Integer)
' via eigenschappen
Me.article = unArticle
Me.qte = qte
End Sub
' gekocht artikel
Public Property article() As article
Get
Return _article
End Get
Set(ByVal Value As article)
_article = Value
End Set
End Property
' aangekochte hoeveelheid
Public Property qte() As Integer
Get
Return _qte
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New Exception("Quantité [" + Value.ToString + "] invalide")
End If
_qte = Value
End Set
End Property
' totaal aankoop
Public ReadOnly Property totalAchat() As Double
Get
Return _qte * _article.prix
End Get
End Property
' identiteit
Public Overrides Function ToString() As String
Return "[" + _article.ToString + "," + _qte.ToString + "]"
End Function
End Class
End Namespace
Opmerkingen:
- de klasse [Achat] heeft de volgende eigenschappen en methoden:
het gekochte artikel | |
de gekochte hoeveelheid | |
het aankoopbedrag | |
identiteitsstring van het object |
- het heeft een constructor waarmee de eigenschappen [article, qte] kunnen worden geïnitialiseerd die een aankoop definiëren.
1.4.4.3. De klasse [Panier]
De klasse [Panier] vertegenwoordigt alle aankopen van de klant:
Namespace istia.st.articles.domain
Public Class Panier
' privévelden
Private _achats As New ArrayList
Private _totalPanier As Double = 0
' standaardfabrikant
Public Sub New()
End Sub
' aankooplijst
Public ReadOnly Property achats() As ArrayList
Get
Return _achats
End Get
End Property
' totaal van de aankopen
Public ReadOnly Property totalPanier() As Double
Get
Return _totalPanier
End Get
End Property
' methoden
Public Sub ajouter(ByVal unAchat As Achat)
' er wordt gecontroleerd of de aankoop al bestaat
Dim iAchat As Integer = posAchat(unAchat.article.id)
If iAchat <> -1 Then
' gevonden
Dim achatCourant As Achat = CType(_achats(iAchat), Achat)
achatCourant.qte += unAchat.qte
Else
' niet gevonden
_achats.Add(unAchat)
End If
' het totaal van het winkelmandje wordt verhoogd
_totalPanier += unAchat.totalAchat
End Sub
' een aankoop verwijderen
Public Sub enlever(ByVal idAchat As Integer)
' het artikel wordt gezocht
Dim iachat As Integer = posAchat(idAchat)
' als het is gevonden, wordt het verwijderd
If iachat <> -1 Then
Dim achatCourant As Achat = CType(_achats(iachat), Achat)
' uit het winkelmandje verwijderen
_achats.RemoveAt(iachat)
' het totaal van het winkelmandje verlagen
_totalPanier -= achatCourant.totalAchat
End If
End Sub
Private Function posAchat(ByVal idArticle As Integer) As Integer
' zoek een aankoop in de aankooplijst
' geeft de positie in de lijst terug of -1 als het niet gevonden is
Dim achatCourant As Achat
Dim trouvé As Boolean = False
Dim i As Integer = 0
While Not trouvé AndAlso i < _achats.Count
' huidige aankoop
achatCourant = CType(_achats(i), Achat)
' vergelijking met het gezochte artikel
If achatCourant.article.id = idArticle Then
Return i
End If
'volgende aankoop
i += 1
End While
' niet gevonden
Return -1
End Function
' identiteitsfunctie
Public Overrides Function ToString() As String
Return _achats.ToString
End Function
End Class
End Namespace
Opmerkingen:
- de klasse [Panier] heeft de volgende eigenschappen en methoden:
de aankooplijst van de klant – lijst met objecten van het type [Achat] | |
voegt een aankoop toe aan de aankooplijst | |
verwijdert de aankoop van het artikel idAchat | |
het totaalbedrag van de aankopen in het winkelmandje | |
geeft de identiteitsstring van het winkelmandje weer |
- de methode [posAchat] is een hulpprogramma waarmee de positie in de aankooplijst kan worden verkregen van een aankoop die wordt geïdentificeerd door het artikelnummer van het gekochte artikel. De aankooplijst wordt zo beheerd dat een artikel dat meerdere keren is gekocht slechts één positie in de lijst inneemt. Zo kan een aankoop worden geïdentificeerd aan de hand van het artikelnummer. De methode [posAchat] retourneert -1 als de gezochte aankoop niet bestaat.
- De methode [ajouter] voegt een nieuwe aankoop toe aan de aankooplijst. Dit komt neer op het toevoegen van een nieuw item aan de aankooplijst als het gekochte artikel nog niet in de lijst voorkwam, of op het verhogen van de gekochte hoeveelheid als het artikel al in de lijst stond.
- Met de methode [enlever] kan een aankoop, geïdentificeerd door een nummer, uit de aankooplijst worden verwijderd. Als de aankoop niet bestaat, reageert de methode niet en doet ze niets.
- Het aankoopbedrag van [totalPanier] blijft behouden bij het toevoegen en verwijderen van aankopen.
1.4.4.4. De klasse [AchatsArticles]
De interface [IArticlesDomain] wordt geïmplementeerd door de volgende klasse [AchatsArticles]:
Imports istia.st.articles.dao
Namespace istia.st.articles.domain
Public Class AchatsArticles
Implements IArticlesDomain
'privévelden
Private _articlesDao As IArticlesDao
Private _erreurs As ArrayList
' constructor
Public Sub New(ByVal articlesDao As IArticlesDao)
_articlesDao = articlesDao
End Sub
' foutenlijst
Public ReadOnly Property erreurs() As ArrayList Implements IArticlesDomain.erreurs
Get
Return _erreurs
End Get
End Property
' artikellijst
Public Function getAllArticles() As IList Implements IArticlesDomain.getAllArticles
' lijst van alle artikelen
Try
Return _articlesDao.getAllArticles
Catch ex As Exception
_erreurs = New ArrayList
_erreurs.Add("Erreur d'accès aux données : " + ex.Message)
End Try
End Function
' een artikel opzoeken aan de hand van het nummer
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDomain.getArticleById
' een specifiek artikel
Try
Return _articlesDao.getArticleById(idArticle)
Catch ex As Exception
_erreurs = New ArrayList
_erreurs.Add("Erreur d'accès aux données : " + ex.Message)
End Try
End Function
' een winkelmandje kopen
Public Sub acheter(ByVal panier As Panier) Implements IArticlesDomain.acheter
' een winkelmandje kopen – de voorraad van de gekochte artikelen moet worden verminderd
_erreurs = New ArrayList
Dim achat As achat
Dim achats As ArrayList = panier.achats
For i As Integer = achats.Count - 1 To 0 Step -1
' voorraad van artikel i verlagen
achat = CType(achats(i), achat)
Try
If _articlesDao.changerStockArticle(achat.article.id, -achat.qte) = 0 Then
' de bewerking kon niet worden uitgevoerd
_erreurs.Add("L'achat " + achat.ToString + " n'a pu se faire - Vérifiez les stocks")
Else
' de bewerking is uitgevoerd – de aankoop wordt uit het winkelmandje verwijderd
panier.enlever(achat.article.id)
End If
Catch ex As Exception
_erreurs = New ArrayList
_erreurs.Add("Erreur d'accès aux données : " + ex.Message)
End Try
Next
End Sub
End Class
End Namespace
Opmerkingen:
- deze klasse implementeert de vier methoden van de interface [IArticlesDomain]. Ze heeft twee privévelden:
het object voor gegevenstoegang | |
de lijst met eventuele fouten. Deze is toegankelijk via de openbare eigenschap [erreurs] |
- om een instantie van de klasse aan te maken, moet het object worden opgegeven dat toegang tot de gegevens biedt:
- De methoden [getAllArticles] en [getArticleById] zijn gebaseerd op de gelijknamige methoden van de laag [dao]
- De methode [acheter] valideert de aankoop van een winkelmandje. Deze validatie bestaat simpelweg uit het verlagen van de voorraad van de gekochte artikelen. Een artikel kan alleen worden gekocht als de voorraad dit toelaat. Als dat niet het geval is, wordt de aankoop geweigerd: het artikel blijft in het winkelmandje en er wordt een fout gemeld in de lijst [erreurs]. Een gevalideerde aankoop wordt uit het winkelmandje verwijderd en de voorraad van het betreffende artikel wordt met de gekochte hoeveelheid verminderd.
1.4.4.5. Genereren van de assembly van de laag [domain]
Het Visual Studio-project is geconfigureerd om de assembly [webarticles-domain.dll] te genereren. Deze wordt gegenereerd in de map [bin] van het project:
![]() | ![]() |
1.4.4.6. Tests NUnit van de laag [domain]
De structuur van het Visual Studio-testproject is als volgt:

Opmerkingen:
- het project [tests] is van het type [bibliothèque de classes]
- de tests [NUnit] vereisen een verwijzing naar de assembly [nunit.framework.dll]
- de testklasse [NUnit] haalt via Spring een instantie van het te testen object op. Daarom vinden we
- in de map [bin] de Spring-klassebestanden
- in [References] een verwijzing naar de assembly [Spring-Core.dll] uit de map [bin]
- in [bin] een configuratiebestand voor Spring
- de testklasse heeft de assembly [webarticles-dao.dll] uit de laag [dao] en de assembly [webarticles-domain.dll] uit de laag [domain] nodig. Deze zijn in de map [bin] geplaatst en hun verwijzingen zijn toegevoegd aan de projectverwijzingen.
Een testklasse NUnit van de laag [domain] zou er als volgt uit kunnen zien:
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports istia.st.articles.domain
Imports Spring.Objects.Factory.Xml
Imports System.IO
Namespace istia.st.articles.tests
<TestFixture()> _
Public Class NunitTestArticlesDomain
' het te testen object
Private articlesDomain As IArticlesDomain
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' we halen een instantie op van de Spring-objectgenerator
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
' we vragen om het instantiëren van het DAO-object ‘articles’
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
' en vervolgens die van het 'articlesdomain'-object
articlesDomain = CType(factory.GetObject("articlesdomain"), IArticlesDomain)
End Sub
<Test()> _
Public Sub getAllArticles()
' visuele controle
listArticles()
End Sub
<Test()> _
Public Sub getArticleById()
' artikelen verwijderen
articlesDao.clearAllArticles()
' controle
Dim articles As IList = articlesDomain.getAllArticles
Assert.AreEqual(0, articles.Count)
' 2 artikelen toevoegen
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' controle
articles = articlesDomain.getAllArticles
Assert.AreEqual(2, articles.Count)
' artikel 3 zoeken
Dim unArticle As Article = articlesDomain.getArticleById(3)
' controle
Assert.AreEqual(unArticle.nom, "article3")
' artikel 4 zoeken
unArticle = articlesDao.getArticleById(4)
' controle
Assert.AreEqual(unArticle.nom, "article4")
End Sub
<Test()> _
Public Sub acheterPanier()
' artikelen verwijderen
articlesDao.clearAllArticles()
' controle
Dim articles As IList = articlesDomain.getAllArticles
Assert.AreEqual(0, articles.Count)
' 2 artikelen toevoegen
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' controle
articles = articlesDomain.getAllArticles
Assert.AreEqual(2, articles.Count)
' aanmaken van een winkelmandje met twee aankopen
Dim panier As New panier
panier.ajouter(New Achat(New Article(3, "article3", 30, 30, 3), 10))
panier.ajouter(New Achat(New Article(4, "article4", 40, 40, 4), 10))
' controles
Assert.AreEqual(700, panier.totalPanier, 0.000001)
Assert.AreEqual(2, panier.achats.Count)
' winkelwagen bevestigen
articlesDomain.acheter(panier)
' controles
Assert.AreEqual(0, articlesDomain.erreurs.Count)
Assert.AreEqual(0, panier.achats.Count)
' artikel zoeken 3
Dim unArticle As Article = articlesDomain.getArticleById(3)
' controle
Assert.AreEqual(unArticle.stockactuel, 20)
' artikel zoeken 4
unArticle = articlesDao.getArticleById(4)
' controle
Assert.AreEqual(unArticle.stockactuel, 30)
' nieuw winkelmandje
panier.ajouter(New Achat(New Article(3, "article3", 30, 30, 3), 100))
' winkelwagen bevestigen
articlesDomain.acheter(panier)
' controles
Assert.AreEqual(1, articlesDomain.erreurs.Count)
' artikel zoeken 3
unArticle = articlesDomain.getArticleById(3)
' controle
Assert.AreEqual(unArticle.stockactuel, 20)
End Sub
<Test()> _
Public Sub testRetirerAchats()
' inhoud van ARTICLES verwijderen
articlesDao.clearAllArticles()
' leest de tabel ARTICLES
Dim articles As IList = articlesDao.getAllArticles()
Assert.AreEqual(0, articles.Count)
' invoegen
Dim article3 As New Article(3, "article3", 30, 30, 3)
articlesDao.ajouteArticle(article3)
Dim article4 As New Article(4, "article4", 40, 40, 4)
articlesDao.ajouteArticle(article4)
' leest de tabel ARTICLES
articles = articlesDomain.getAllArticles()
Assert.AreEqual(2, articles.Count)
' aanmaken van een winkelmandje met twee aankopen
Dim monPanier As New Panier
monPanier.ajouter(New Achat(article3, 10))
monPanier.ajouter(New Achat(article4, 10))
' controles
Assert.AreEqual(700.0, monPanier.totalPanier, 0.000001)
Assert.AreEqual(2, monPanier.achats.Count)
' een reeds gekocht artikel toevoegen
monPanier.ajouter(New Achat(article3, 10))
' controles
' het totaal moet worden aangepast naar 1000
Assert.AreEqual(1000.0, monPanier.totalPanier, 0.000001)
' nog steeds 2 artikelen in het winkelmandje
Assert.AreEqual(2, monPanier.achats.Count)
' aantal van artikel 3 moet worden aangepast naar 20
Dim unAchat As Achat = CType(monPanier.achats(0), Achat)
Assert.AreEqual(20, unAchat.qte)
' artikel 3 wordt uit het winkelmandje verwijderd
monPanier.enlever(3)
' controles
' het totaal moet zijn aangepast naar 400
Assert.AreEqual(400.0, monPanier.totalPanier, 0.000001)
' slechts 1 artikel in het winkelmandje
Assert.AreEqual(1, monPanier.achats.Count)
' dit moet artikel nr. 4 zijn
Assert.AreEqual(4, CType(monPanier.achats(0), Achat).article.id)
End Sub
' schermweergave
Private Sub listArticles()
Dim articles As IList = articlesDomain.getAllArticles
For i As Integer = 0 To articles.Count - 1
Console.WriteLine(CType(articles(i), Article).ToString)
Next
End Sub
End Class
End Namespace
Opmerkingen:
- we wilden een testprogramma schrijven voor de interface [IArticlesDomain] dat onafhankelijk is van de implementatieklasse ervan. Daarom hebben we Spring gebruikt om de naam van de implementatieklasse voor het testprogramma te verbergen.
- De attribuutmethode <Setup()> haalt bij Spring een verwijzing op naar de te testen objecten [articlesdomain] en [articlesdao]. Deze zijn gedefinieerd in het volgende bestand [spring-config.xml]:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
<object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoArrayList, webarticles-dao" />
<object id="articlesdomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
<constructor-arg index="0">
<ref object="articlesdao" />
</constructor-arg>
</object>
</objects>
Dit bestand geeft het volgende weer:
- (vervolg)
- voor het singleton [articlesdao]: de naam van de implementatieklasse [istia.st.articles.dao.ArticlesDaoArrayList] en waar deze te vinden is: [ webarticles-dao.dll]. Aangezien het instantiëren geen parameters vereist, zijn hier geen parameters gedefinieerd.
- voor het singleton [articlesdomain], de naam van de implementatieklasse [istia.st.articles.domain.AchatsArticles] en waar deze te vinden is: [ webarticles-domain.dll]. De klasse [AchatsArticles] heeft een constructor met één parameter: het singleton dat de toegang tot de laag [dao] beheert. Hier wordt dit gedefinieerd als het eerder gedefinieerde singleton [articlesdao].
- De testklasse verkrijgt een instantie van de te testen klasse [articlesdomain] en een instantie van de klasse voor gegevenstoegang [articlesdao]. Dit laatste punt is omstreden. De testklasse zou in theorie geen toegang moeten hebben tot de laag [dao], die ze niet eens zou mogen kennen. Hier hebben we deze „ethiek“ terzijde geschoven; als we ons hieraan hadden gehouden, zouden we nieuwe methoden in onze interface [IArticlesDomain] hebben moeten aanmaken.
Om de laag [domain] te testen, genereren we de DLL en [tests-webarticles-domain.dll] in de map [bin] van het project [tests]:
![]() | ![]() |
Vervolgens laden we met behulp van de applicatie [Nunit-Gui] dit bestand DLL en voeren we de tests uit:

De lezer die dit document op het scherm bekijkt, zal zien dat alle tests zijn geslaagd. Vervolgens gaan we ervan uit dat we een operationele [domain]-laag hebben.
1.4.5. Conclusie
Ter herinnering: we willen de volgende webapplicatie met drie lagen bouwen:
![]() |
Het M-model van onze applicatie MVC is nu geschreven en getest. Het wordt ons geleverd in twee bestanden: DLL en [webarticles-dao.dll, webarticles-domain.dll]. We kunnen nu doorgaan naar de laatste laag, de [web]-laag, die de C-controller en de V-weergaven bevat. We zullen eerst een methode bekijken die wordt beschreven in het document [Développement WEB avec ASP.NET 1.1 ]
- de C-controller wordt verzorgd door twee bestanden [global.asax, main.aspx]
- De V-weergaven worden verzorgd door aspx-pagina’s
1.5. De laag [web]
De architectuur MVC van de webapplicatie ziet er als volgt uit:
![]() |
de businessklassen [domain], de klassen voor gegevenstoegang [dao] en de gegevensbron | |
de pagina’s ASPX | |
alle verzoeken van de clients HTTP worden door de volgende twee controllers verwerkt: global.asax: verwerkt de gebeurtenissen die verband houden met de eerste start van de applicatie main.aspx: verwerkt de verzoeken van elke klant afzonderlijk |
1.5.1. De weergaven
De weergaven komen overeen met die welke aan het begin van dit document zijn gepresenteerd:
liste.aspx | De weergaven zijn verzameld in de map [vues] van de applicatie ![]() | |
infos.aspx | ||
panier.aspx | ||
paniervide.aspx | ||
erreurs.aspx |
1.5.2. De controllers
Zoals aangegeven, bestaat de controller uit twee onderdelen:
- [global.asax,global.asax.vb]: voornamelijk gebruikt om de applicatie te initialiseren en alle gegevens die tussen de verschillende clients moeten worden gedeeld in de context van de applicatie te plaatsen
- [main.aspx, main.aspx.vb]: de eigenlijke controller, die de verzoeken HTTP van de clients verwerkt.
De verschillende verzoeken van de clients worden gericht aan de controller [main.aspx] en bevatten een parameter met de naam [action], die de door de client gevraagde actie specificeert:
verzoek | betekenis | actie van de controller | mogelijke antwoorden |
de klant wil de lijst met artikelen | - vraagt de lijst met artikelen aan de bedrijfslaag | - [LISTE] - [ERREURS] | |
de klant vraagt om informatie over een van de artikelen die in de weergave [LISTE] | - vraagt het artikel op bij de bedrijfslaag | - [INFOS] - [ERREURS] | |
de klant koopt een artikel | - vraagt het artikel op bij de bedrijfslaag en voegt het toe aan het winkelmandje van de klant | - [INFOS] bij een fout in de hoeveelheid - [LISTE] indien geen fout | |
de klant wil een aankoop uit zijn winkelmandje verwijderen | - haalt het winkelmandje op uit de sessie en wijzigt deze | - [PANIER] - [PANIERVIDE] - [ERREURS] | |
de klant wil zijn winkelwagen | - haalt het winkelmandje op uit de sessie | - [PANIER] - [PANIERVIDE] - [ERREURS] | |
de klant heeft zijn aankopen afgerond en gaat over naar de betaalfase | - werkt de voorraad van de gekochte artikelen bij in de database van de gekochte artikelen - verwijdert de artikelen waarvan de aankoop is bevestigd | - [LISTE] - [ERREURS] |
1.5.3. Configuratie van de applicatie
We zullen de applicatie zo configureren dat deze zo flexibel mogelijk is ten aanzien van wijzigingen zoals:
- het wijzigen van de URL van de verschillende weergaven
- het wijzigen van de klassen die de interfaces [IArticlesDao] en [IArticlesDomain] implementeren
- het wijzigen van de SGBD, de database en de artikeltabel
1.5.3.1. De wijzigingen in URL
De namen van de weergaven URL worden samen met enkele andere parameters in het configuratiebestand [web.config] van de applicatie geplaatst:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
..
<appSettings>
<add key="urlMain" value="/webarticles/main.aspx"/>
<add key="urlInfos" value="vues/infos.aspx"/>
<add key="urlErreurs" value="vues/erreurs.aspx"/>
<add key="urlListe" value="vues/liste.aspx"/>
<add key="urlPanier" value="vues/panier.aspx"/>
<add key="urlPanierVide" value="vues/paniervide.aspx"/>
</appSettings>
</configuration>
1.5.3.2. De wijziging van de implementatieklassen van de interfaces
In de geest van drielaagse architecturen moeten de lagen onderling afgeschermd zijn. Deze afscherming wordt als volgt bereikt:
- de lagen communiceren met elkaar via interfaces en niet via concrete klassen
- de code van een laag maakt nooit zelf een instantie van de klasse van een andere laag om deze te gebruiken. Ze vraagt simpelweg aan een extern hulpmiddel, in dit geval Spring, om een implementatie-instantie van de interface van de laag die ze wil gebruiken. Hiervoor hoef ze de naam van de implementatieklasse niet te kennen, maar alleen de naam van het Spring-singleton waarnaar ze een verwijzing wil.
In onze applicatie wordt Spring in het bestand [web.config] van de webapplicatie als volgt geconfigureerd:
<?xml version="1.0" encoding="iso-8859-1" ?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
<resource uri="config://spring/objects" />
</context>
<objects>
<object id="articlesDao" type="istia.st.articles.dao.ArticlesDaoArrayList, webarticles-dao" />
<object id="articlesDomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
<constructor-arg index="0">
<ref object="articlesDao" />
</constructor-arg>
</object>
</objects>
</spring>
<appSettings>
<add key="urlMain" value="/webarticles/main.aspx"/>
<add key="urlInfos" value="vues/infos.aspx"/>
<add key="urlErreurs" value="vues/erreurs.aspx"/>
<add key="urlListe" value="vues/liste.aspx"/>
<add key="urlPanier" value="vues/panier.aspx"/>
<add key="urlPanierVide" value="vues/paniervide.aspx"/>
</appSettings>
</configuration>
Om toegang te krijgen tot de laag [métier], kan een klasse van de laag [web] het singleton [articlesDomain] opvragen. Spring zal dan een object van het type [istia.st.articles.domain.AchatsArticles] instantiëren. Voor deze instantiëring heeft het een object van het type [articlesDao] nodig, dat wil zeggen een object van het type [istia.st.articles.dao.ArticlesDaoArrayList]. Spring zal vervolgens een dergelijk object instantiëren. Aan het einde van de bewerking beschikt de laag [web], die het singleton [articlesDomain] heeft aangevraagd, over de volledige keten die haar met de gegevensbron verbindt:
![]() |
1.5.3.3. Wijzigingen met betrekking tot SGBD of de database
Dit punt wordt hier buiten beschouwing gelaten, aangezien we ons in een testomgeving bevinden zonder SGBD. In een tweede fase zullen we ingaan op de implementatie van een [dao]-laag die is gebaseerd op een SGBD.
1.5.4. De <asp:>-tagbibliotheek
Laten we eens kijken naar de weergave [ERREURS] die een lijst met fouten weergeeft:

De weergave [ERREURS] is verantwoordelijk voor het weergeven van een lijst met fouten die de controller [main.aspx] in de verzoekcontext heeft geplaatst onder de naam [context.Items("erreurs")]. Er zijn verschillende manieren om een dergelijke pagina te schrijven. We richten ons hier uitsluitend op het gedeelte waarin de fouten worden weergegeven.
Ter herinnering: een pagina ASPX bestaat uit een presentatiegedeelte HTML en een codegedeelte .NET dat de gegevens voorbereidt die het presentatiegedeelte moet weergeven. Deze twee delen kunnen in één enkel bestand [aspx] staan (oplossing WebMatrix) of in twee bestanden: [aspx] voor de weergave, [aspx.vb] voor de code. Deze laatste oplossing is die van Visual Studio. Om de zaken nog ingewikkelder te maken, kan het presentatiegedeelte HTML ook .NET-code bevatten, wat de scheiding tussen [contrôleur] en [présentation] in de weergave vertroebelt. Van deze oplossing wordt over het algemeen sterk afgeraden. Om alle code uit het gedeelte [présentation] te verwijderen, moesten er tagbibliotheken worden aangemaakt. Deze ‘verbergen’ de code onder de vorm van tags die lijken op de HTML-tags. We presenteren twee mogelijke oplossingen voor de pagina [ERREURS].
Onze eerste oplossing maakt gebruik van de code .NET in het gedeelte [présentation] van de pagina. De pagina ASPX haalt de lijst met fouten uit het verzoek op in het controllergedeelte [erreurs.aspx.vb]:
Protected erreurs As ArrayList
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
' de fouten worden opgehaald
erreurs = CType(context.Items("erreurs"), ArrayList)
End Sub
en geeft dit vervolgens weer in het gedeelte [présentation, erreurs.aspx]:
<h2>Les erreurs suivantes se sont produites :</h2>
<ul>
<%
for i as integer=0 to erreurs.count-1
response.write("<li>" & erreurs(i).ToString & "</li>")
next
%>
</ul>
De tweede oplossing maakt gebruik van de tag <asp:repeater> uit de <asp:>-tagbibliotheek van ASP.NET. Als men een pagina ASPX grafisch opbouwt, is deze tag beschikbaar in de vorm van een servercomponent die men op het ontwerpformulier kan plaatsen. Als men de code ASPX handmatig opstelt, kan men spreken van een tagbibliotheek.
Met de <asp:>-tagbibliotheek wordt de code ASPX van de vorige weergave [ERREURS] als volgt:
<asp:Repeater id="rptErreurs" runat="server">
<HeaderTemplate>
<h3>Les erreurs suivantes se sont produites :
</h3>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<%# Container.DataItem %>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
De tag
wordt gebruikt om een patroon HTML te herhalen op de verschillende elementen van een gegevensbron. De verschillende elementen zijn als volgt:
het patroon HTML dat moet worden weergegeven voordat de elementen van de gegevensbron worden weergegeven | |
het patroon HTML dat voor elk element van de gegevensbron moet worden herhaald. De uitdrukking [<%# Container.DataItem %>] wordt gebruikt om de waarde van het huidige element van de gegevensbron weer te geven | |
het patroon HTML dat moet worden weergegeven nadat de elementen van de gegevensbron zijn weergegeven |
De gegevensbron wordt meestal in het gedeelte [contrôleur] van de pagina aan de tag gekoppeld:
Protected WithEvents rptErreurs As System.Web.UI.WebControls.Repeater
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
..
' de fouten worden gekoppeld aan rptErreurs
With rptErreurs
.DataSource = context.Items("erreurs")
.DataBind()
End With
End Sub
Deze koppeling kan ook al tijdens het ontwerpen van de pagina worden gemaakt als de gegevensbron al bekend is, bijvoorbeeld een bestaande database.
In onze weergaven gebruiken we een andere tag: <asp:datagrid>, waarmee een gegevensbron in de vorm van een tabel kan worden weergegeven.
1.5.5. Structuur van de Visual Studio-oplossing van de applicatie [webarticles]
Een webtoepassing is een puzzel met talrijke elementen. Door er een architectuur aan toe te voegen, neemt het aantal elementen doorgaans toe. De structuur van de toepassing [webarticles] onder [Visual Studio] is als volgt:
![]() | ![]() | ![]() |
![]() |
Opmerkingen:
- het project [web] is van het type [bibliothèque de classes] en niet van het type [Application web ASP.NET], zoals men logischerwijs zou verwachten. Het type [Application web ASP.NET] vereist dat de webserver IIS aanwezig is op de ontwikkelingsmachine of op een externe machine. De server IIS is standaard niet aanwezig op Windows-computers met de versie XP Home. Toch worden veel computers met de versie PC met deze versie verkocht. Om lezers met Windows XP in staat te stellen de besproken toepassing te implementeren, zullen we gebruikmaken van de Cassini-webserver (zie bijlage), die gratis beschikbaar is bij Microsoft, en zullen we het project [Application web ASP.NET] vervangen door een project [bibliothèque de classes]. Dit brengt enkele nadelen met zich mee, die in de bijlagen worden toegelicht.
- De door de applicatie gebruikte DLL-bestanden zijn de volgende:
bevat de klassen van de gegevenslaag | |
bevat de klassen van de bedrijfslaag | |
bevat de Spring-klassen waarmee we de web-, domein- en DAO-lagen kunnen integreren | |
logklassen – gebruikt door Spring |
Deze DLL worden in de map [bin] geplaatst en toegevoegd aan de projectreferenties.
1.5.6. De weergaven ASPX
Zoals eerder aanbevolen, zullen we de <asp:>-tagbibliotheek gebruiken in onze weergaven ASPX.
1.5.6.1. De gebruikerscomponent [entete.ascx]
Om de verschillende weergaven een zekere uniformiteit te geven, zullen deze dezelfde koptekst delen, namelijk die waarin de naam van de applicatie en het menu worden weergegeven:
![]() | ![]() |
Het menu is dynamisch en wordt bepaald door de controller. Deze voegt in de verzoek die naar de pagina ASPX wordt verzonden, een sleutelattribuut "actions" toe met als bijbehorende waarde een array van elementen van het type Hashtable(). Elk element van deze array is een woordenboek dat bedoeld is om een optie in het koptekstmenu te genereren. Elk woordenboek heeft twee sleutels:
- href: de URL die bij de menuoptie hoort
- link: de tekst van het menu
We maken van de koptekst een gebruikersbesturingselement. Een gebruikersbesturingselement kapselt een deel van de pagina (weergave en bijbehorende code) in een component die vervolgens in andere pagina's kan worden hergebruikt. Hier willen we de component [entete] hergebruiken in de andere weergaven van de applicatie. De presentatiecode komt in [entete.ascx] en de bijbehorende besturingscode in [entete.ascx.vb]. De presentatiecode gebruikt een <asp:repeater>-component om de tabel met menuopties weer te geven:
![]() |
n° | type | nom | rôle |
1 | repeater | rptMenu gegevensbron: een tabel met woordenboeken met twee sleutels: href, link | menuopties weergeven |
De opmaakcode van de pagina ziet er als volgt uit:
Opmerkingen:
- de component [repeater] wordt gedefinieerd in de regels 6-14
- elk element van de gegevensbron die aan de repeater is gekoppeld, is een woordenboek met twee sleutels: href - regel 9 en link - regel 10
De bijbehorende controlecode is als volgt:
Opmerkingen:
- de component van het type [EnteteWebArticles] heeft een openbare eigenschap [actions] die alleen-schrijven is – regel 7
- met deze eigenschap kan de door de applicatiecontroller berekende optietabel – regels 11-12 – worden gekoppeld aan de <asp:repeater>-component met de naam [rptMenu] – regel 10.
De andere weergaven van de applicatie zullen de door [entete.ascx] gedefinieerde koptekst gebruiken. De pagina [erreurs.aspx] zal bijvoorbeeld de koptekst opnemen met behulp van de volgende code:
Opmerkingen:
- Regel 1 geeft aan dat de tag <WA:entete> moet worden gekoppeld aan de component die is gedefinieerd in het bestand [entete.ascx]. De attributen [TagPrefix] en [TagName] kunnen vrij worden gekozen.
- Zodra dit is gebeurd, wordt de component met regel 9 in de presentatiecode van de pagina ingevoegd. Bij uitvoering zorgt deze tag ervoor dat de code van de pagina ASPX, waarin deze tag is opgenomen, de code van de pagina [entete.ascx] opneemt. De besturingscode [erreurs.aspx.vb] zorgt ervoor dat deze component wordt geïnitialiseerd. Dit kan op de volgende manier gebeuren:
Opmerkingen:
- regel 6 maakt een object van het type [EnteteWebArticles] aan, wat het type is van de aangemaakte component
- regel 11 initialiseert de eigenschap [actions] van dit object
1.5.6.2. De weergave [liste.aspx]
1.5.6.2.1. Introduction
Deze weergave toont de lijst met artikelen die te koop zijn:
![]() | ![]() |
Deze wordt weergegeven na een verzoek /main?action=lijst of /main?action=winkelwagenbevestiging. De elementen van het verzoek aan de controller zijn als volgt:
Hashtable()-object – de lijst met menuopties | |
ArrayList objecten van het type [Article] | |
String-object – bericht dat onderaan de pagina moet worden weergegeven |
Elke link [Infos] uit de tabel HTML met artikelen heeft een URL in de vorm [?action=infos&id=ID], waarbij ID het id-veld is van hetweergegeven artikel.
1.5.6.2.2. Paginaonderdelen
![]() |
n° | type | nom | rôle |
gebruikerscomponent | koptekst | koptekst weergeven | |
DataGrid | DataGridArticles 3 - gerelateerde kolom: koptekst: Naam, veld: naam 4 - gerelateerde kolom: koptekst: Prijs, veld: prijs 5 - hypertekstkolom: tekst: Info, URL-veld: id, formaat URL: /webarticles/main.aspx?action=infos&id={0} | artikelen te koop weergeven | |
label | lblMessage | een bericht weergeven |
Laten we nog eens bekijken hoe u deze eigenschappen kunt instellen:
- in Visual Studio selecteer je [DataGrid] om toegang te krijgen tot het eigenschappenvenster:

- gebruik de bovenstaande link [Mise en forme automatique] om de vorm van de weergegeven tabel te beheren
- en de link [Générateur de propriétés] om de inhoud ervan te beheren
1.5.6.2.3. Weergavecode [liste.aspx]
Opmerkingen:
- regel 9 definieert de koptekst van de pagina
- de regels 12-24 definiëren de kenmerken van [DataGrid]
- regel 26 definieert het label [lblMessage]
1.5.6.2.4. Controlecode [liste.aspx.vb]
Opmerkingen:
- De componenten van de pagina staan in de regels 13-15. Merk op dat we een object [EnteteWebArticles] moesten aanmaken met een operator [new], terwijl dit bij de andere componenten niet nodig was. Zonder deze expliciete aanmaak kregen we een uitvoeringsfout die aangaf dat het object [entete] nergens naar verwees. Dit punt zou nader onderzocht moeten worden. Dat is niet gebeurd.
- de tabel met menuopties in de koptekst wordt uit de context gehaald om de component [entete] van de pagina te initialiseren – regel 20
- de lijst met artikelen wordt uit de context gehaald – regel 22
- om de component [DataGridArticles] te initialiseren – regels 24-27
- het onderdeel [lblMessage] wordt geïnitialiseerd met een bericht dat in de context is geplaatst – regel 29
1.5.6.3. De weergave [infos.aspx]
1.5.6.3.1. Introduction
Deze weergave toont informatie over een artikel en maakt het ook mogelijk om het te kopen:

Deze wordt weergegeven na een verzoek /main?action=infos&id=ID of een verzoek /main?action=achat&id=ID wanneer de gekochte hoeveelheid onjuist is. De elementen van het verzoek van de controller zijn als volgt:
Hashtable()-object – de lijst met menuopties | |
object van het type [Article] – weer te geven artikel | |
String-object - bericht dat moet worden weergegeven bij een fout in de hoeveelheid | |
String-object - waarde die in het invoerveld moet worden weergegeven [Qte] |
De velden [msg] en [qte] worden gebruikt bij een invoerfout in de hoeveelheid:

Deze pagina bevat een formulier dat wordt verzonden via de knop [Acheter]. De bestemming van URL vanuit POST is [?action=achat&id=ID], waarbij ID de ID is van het gekochte artikel.
1.5.6.3.2. De onderdelen van de pagina
![]() |
n° | type | nom | rôle |
gebruikerscomponent | koptekst | koptekst weergeven | |
letterlijke waarde | litID | artikelnummer weergeven | |
DataGrid | DataGridArticle 3 - gerelateerde kolom: koptekst: Naam, veld: naam 4 - gerelateerde kolom: koptekst: Prijs, veld: prijs 5 - gerelateerde kolom: koptekst: Huidige voorraad, veld: stockActuel 6 - gerelateerde kolom: koptekst: Minimale voorraad, veld: stockMinimum | een artikel weergeven | |
HTML Verzenden | formulier verzenden | ||
HTML Input runat=server | txtQte | de gekochte hoeveelheid invoeren | |
label | lblMsgQte | eventuele foutmelding |
1.5.6.3.3. De presentatiecode [infos.aspx]
Opmerkingen:
- de koptekst is opgenomen in de pagina - regel 9
- de letterlijke waarde [litId] is gedefinieerd op regel 10
- de DataGrid [DataGridArticles] wordt gedefinieerd op regels 12-34
- het formulier wordt gedefinieerd op de regels 36-46. Het heeft het type POST.
- het doel van POST wordt geleverd door een variabele [strAction] – regel 36. Deze variabele moet door de controller worden gedefinieerd.
- Het invoerveld voor de gekochte hoeveelheid is gedefinieerd in regel 41. Dit is een servercomponent HTML (runat=server). In de code is deze toegankelijk via een object.
- Op regel 42 wordt het label [lblMsgQte] gedefinieerd, dat een eventuele foutmelding over de ingevoerde hoeveelheid zal bevatten
1.5.6.3.4. De controlecode [infos.aspx.vb]
Opmerkingen:
- de componenten van de pagina worden gedefinieerd in de regels 10-14
- De klasse definieert een openbare eigenschap [strAction] die dient om het doel van POST van het formulier te definiëren – regels 17-25
- het weer te geven artikel wordt opgehaald uit de context van de applicatie – regel 30
- de tabel met opties voor het koptekstmenu wordt uit de context gehaald om de component [entete] van de pagina te initialiseren – regel 32
- regels 33-39: de component [DataGridArticle] wordt gekoppeld aan een gegevensbron van het type [ArrayList] die uitsluitend het in regel 30 opgehaalde artikel bevat
- de componenten [lblMsgQte, txtQte] worden geïnitialiseerd met informatie uit de context – regels 42-45
- de eigenschap [straction] wordt eveneens geïnitialiseerd met informatie uit de context – regel 47. Deze variabele wordt gebruikt om het attribuut [action] te genereren van het formulier HTML dat op de pagina staat:
1.5.6.4. De weergave [panier.aspx]
1.5.6.4.1. Introduction
Deze weergave toont de inhoud van het winkelmandje:

Deze wordt weergegeven na een verzoek /main?action=panier of /main?action=retirerachat&id=ID. De elementen van het verzoek aan de controller zijn als volgt:
Hashtable()-object – de lijst met menuopties | |
object van het type [Panier] – het weer te geven winkelmandje |
Elke koppeling [Retirer] in de tabel HTML met de aankopen in het winkelmandje heeft een URL in de vorm [?action=retirerachat&id=ID], waarbij ID het veld [id] van het artikel dat uit het winkelmandje moet worden verwijderd.
1.5.6.4.2. De onderdelen van de pagina
![]() |
n° | type | nom | rôle |
gebruikerscomponent | koptekst | koptekst weergeven | |
DataGrid | DataGridAchats 3 - gerelateerde kolom - koptekst: Artikel, veld: naam 4 - gerelateerde kolom - koptekst: Aantal, veld: aantal 5 - gekoppelde kolom - koptekst: Prijs, veld: prijs 6 - gekoppelde kolom - koptekst: Totaal, veld: totaal, opmaak {0:C} 7 - hypertekstkolom - Tekst: Verwijderen, URL: id, URL-formaat: /webarticles/main.aspx?action=retirerachat&id={0} | de lijst met gekochte artikelen weergeven | |
label | lblTotal | het te betalen bedrag weergeven |
1.5.6.4.3. De weergavecode [panier.aspx]
Opmerkingen
- regel 9 bevat de koptekst
- in de regels 12-27 wordt de component [DataGridAchats] gedefinieerd
- in regel 29 wordt de component [lblTotal] gedefinieerd
1.5.6.4.4. De controlecode [panier.aspx.vb]
Opmerkingen:
- de componenten van de pagina worden gedeclareerd in de regels 11-13
- de initialisatie van de component [entete] is identiek aan die in de reeds besproken pagina's – regel 17
- het weer te geven winkelmandje wordt opgehaald uit de sessie – regel 19
- het weergeven van dit winkelmandje met behulp van de component [DataGridAchats] levert problemen op. De moeilijkheid zit hem in de initialisatie van de component. Laten we de kolommen ervan nog eens op een rijtje zetten:
- kolom [Article] gekoppeld aan veld [nom] van de gegevensbron
- kolom [Qté], gekoppeld aan veld [qte] van de gegevensbron
- kolom [Prix] gekoppeld aan veld [prix] van de gegevensbron
- kolom [Total], gekoppeld aan het veld [total] van de gegevensbron
De gegevensbron waarover we beschikken is het winkelmandje en de bijbehorende boodschappenlijst. Deze lijst zal de gegevensbron zijn voor [DataGrid]. Alleen de objecten [Achat] die de rijen van [DataGrid] zullen vullen, beschikken niet over de eigenschappen [nom, qte, prix, total] die door [DataGrid] worden verwacht. Daarom wordt hier, speciaal voor [DataGrid], een gegevensbron aangemaakt waarvan de elementen de kenmerken hebben die door [DataGrid] worden verwacht. Deze elementen zullen van het type [LigneAchat] zijn, een klasse die speciaal voor deze gelegenheid is aangemaakt en is afgeleid van de klasse [Achat] – regels 36-66
- Zodra de klasse [LigneAchat] is gedefinieerd, wordt de gegevensbron van [DataGridAchats] samengesteld op basis van het winkelmandje dat in de sessie is gevonden – regels 20-30
- het aankoopbedrag wordt weergegeven via de eigenschap [totalPanier] van de klasse [Panier] – regel 32
1.5.6.5. De weergave [paniervide.aspx]
1.5.6.5.1. Introduction
Deze weergave toont de informatie dat het winkelmandje leeg is:

Dit wordt weergegeven na een verzoek /main?action=winkelwagen of /main?action=aankoop annuleren&id=ID. De elementen van het verzoek aan de controller zijn als volgt:
Hashtable()-object – de tabel met menuopties |
1.5.6.5.2. De componenten van de pagina
![]() |
n° | type | nom | rôle |
gebruikerscomponent | koptekst | koptekst weergeven |
1.5.6.5.3. De weergavecode [paniervide.aspx]
Opmerkingen:
- de koptekst is opgenomen op regel 9
1.5.6.5.4. De controlecode [paniervide.aspx.vb]
Opmerkingen:
- we initialiseren alleen de enige dynamische component van de pagina - regel 10
1.5.6.6. De weergave [erreurs.aspx]
1.5.6.6.1. Introduction
Dit scherm wordt weergegeven als er fouten optreden:

Deze wordt weergegeven na elke aanvraag die tot een fout leidt, behalve bij een aankoopactie met een onjuiste hoeveelheid, die wordt afgehandeld door de weergave [INFOS]. De elementen van de aanvraag van de controller zijn als volgt:
object Hashtable() – de lijst met menuopties | |
ArrayList van objecten [String] die de weer te geven foutmeldingen vertegenwoordigen |
1.5.6.6.2. De componenten van de pagina
![]() |
n° | type | nom | rôle |
gebruikerscomponent | koptekst | koptekst weergeven | |
herhaling | rptErreurs | de foutenlijst weergeven |
1.5.6.6.3. De weergavecode [erreurs.aspx]
Opmerkingen:
- de header is gedefinieerd op regel 9
- De component [rptErreurs] is gedefinieerd op regels 13-19. De inhoud ervan is afkomstig van een gegevensbron van het type [ArrayList] met objecten van het type [String].
1.5.6.6.4. De controlecode [erreurs.aspx.vb]
Opmerkingen:
- de component [entete] wordt zoals gebruikelijk geïnitialiseerd, regels 9 en 14
- de component [rptErreurs] wordt geïnitialiseerd met de foutenlijst van het type [ArrayList] die in de context is aangetroffen – regels 16-19
1.5.7. De controllers global.asax, main.aspx
Nu moeten we nog de kern van onze webapplicatie schrijven: de controller. Deze heeft als taak:
- het verzoek van de klant ophalen,
- de door de klant gevraagde actie te verwerken met behulp van de businessklassen,
- de juiste weergave als antwoord terugsturen.
1.5.7.1. De controller [global.asax.vb]
Wanneer de applicatie haar allereerste verzoek ontvangt, wordt de procedure [Application_Start] uit het bestand [global.asax.vb] uitgevoerd. Dit gebeurt slechts één keer. De procedure [Application_Start] heeft tot doel de objecten te initialiseren die nodig zijn voor de webapplicatie en die door alle client-threads als-alleen-lezen worden gedeeld. Deze gedeelde objecten kunnen op twee plaatsen worden ondergebracht:
- de privévelden van de controller
- de uitvoeringscontext van de applicatie (Application)
De methode [Application_Start] van de applicatie [global.asax.vb] voert de volgende acties uit:
- controleren of de parameters die nodig zijn voor de goede werking van de applicatie aanwezig zijn in het bestand [web.config]. Deze zijn beschreven in paragraaf 1.5.3.
- voegt de lijst met eventuele fouten in de vorm van een object [ArrayList erreurs] toe aan de context van de applicatie. Deze lijst is leeg als er geen fouten zijn, maar bestaat niettemin.
- Als er fouten zijn opgetreden, stopt de methode [Application_Start] hier. Anders vraagt deze om de referentie van een singleton van het type [IArticlesDomain], wat het bedrijfsobject is dat de controller voor zijn doeleinden zal gebruiken. Zoals uitgelegd in 1.5.3.2, zal de controller dit singleton opvragen bij het Spring-framework. Deze instantiëring kan verschillende fouten opleveren. Als dat het geval is, worden deze opnieuw opgeslagen in het object [erreurs] van de applicatiecontext.
De controller [global.asax.vb] beschikt over een procedure [Session_Start] die telkens wordt uitgevoerd wanneer er een nieuwe klant binnenkomt. In deze procedure wordt een leeg winkelmandje voor de klant aangemaakt. Dit winkelmandje blijft behouden gedurende alle verzoeken van deze specifieke klant. De code zou er als volgt uit kunnen zien:
Opmerkingen:
- de verwachte parameters in [web.config] worden gedefinieerd in een array – regel 18
- ze worden opgezocht in [web.config]. Als ze aanwezig zijn, worden ze opgeslagen in de applicatiecontext; anders wordt er een fout geregistreerd in de foutenlijst [erreurs] – regels 21-33
- als er geen fouten zijn, wordt bij Spring een verwijzing opgevraagd naar het singleton [articlesDomain] dat de toegang tot de laag [domain] van de applicatie beheert – regels 35-47. Eventuele fouten worden vastgelegd in [erreurs].
- De fouten worden vastgelegd in de context van de applicatie – regel 49
- de procedure wordt verlaten als er fouten zijn opgetreden – regel 51
- er wordt een array met drie woordenboeken aangemaakt. Elk daarvan heeft twee sleutels: href en link. Deze array vertegenwoordigt de drie mogelijke menuopties – regels 52-71
- deze array wordt opgeslagen in de applicatiecontext – regel 73
- Bij elke nieuwe klant wordt de procedure [Session_Start] uitgevoerd. Daarin wordt een leeg winkelmandje aangemaakt in de sessie van de klant – regels 78-81
1.5.7.2. De controller [main.aspx.vb]
De controller [main.aspx.vb] verwerkt alle verzoeken van klanten. Deze hebben namelijk allemaal de vorm [/webarticles/main.aspx?action=XX]. Een verzoek wordt als volgt verwerkt:
- het object [erreurs] uit de applicatiecontext wordt gecontroleerd. Als dit niet leeg is, betekent dit dat er fouten zijn opgetreden tijdens het initialiseren van de applicatie en dat deze niet kan functioneren. In dat geval wordt als antwoord de weergave [ERREURS] verzonden.
- de parameter [action] van het verzoek wordt opgehaald en gecontroleerd. Als deze niet overeenkomt met een bekende actie, wordt de weergave [ERREURS] verzonden met een passend foutbericht.
- Als de parameter [action] geldig is, wordt het verzoek van de klant doorgegeven aan een actiespecifieke procedure voor verwerking:
methode | verzoek | verwerking | mogelijke antwoorden |
GET /main?action=lijst | - de lijst met artikelen opvragen bij de businessklasse - weergeven | [LISTE] of [ERREURS] | |
GET /main?action=infos&id=ID | - het artikel met id=ID opvragen bij de businessklasse - het weergeven | [INFOS] of [ERREURS] | |
POST /main?action=aankoop&id=ID - de gekochte hoeveelheid maakt deel uit van de verzonden parameters | - vraag het artikel met id=ID op bij de businessklasse - voeg het toe aan het winkelmandje in de klantsessie | [LISTE] of [INFOS] of [ERREURS] | |
GET /main?action=retirerachat&id=ID | - het artikel met id=ID verwijderen uit de aankooplijst van het winkelmandje van de klantsessie | [PANIER] | |
GET /main?action=winkelwagen | - het winkelmandje van de klantsessie | [PANIER] of [PANIERVIDE] | |
GET /main?action=winkelwagenbevestiging | - de voorraad van alle artikelen die zich in het winkelmandje van sessie van de klant | [LISTE] of [ERREURS] |
Het raamwerk van de controller [main.aspx.vb] zou er als volgt uit kunnen zien:
Opmerkingen:
- de klasse heeft twee privévelden die door de methoden worden gedeeld – regels 15-16:
- articlesDomain: de singleton voor toegang tot de laag [domain]
- opties: de array met woordenboeken van de menuopties
- de procedure [Page_Load]:
- initialiseert de twee privévelden van de klasse
- haalt de parameter [action] uit de aanvraag op en voert de verwerkingsmethode voor deze actie uit.
1.5.7.3. De methode [Page_Load]
Dit is de eerste gebeurtenis die op de pagina plaatsvindt. De code is als volgt:
Opmerkingen:
- bij elke paginalading wordt gecontroleerd of de initialisatie van de applicatie door [global.asax] correct is verlopen.
- Hiervoor wordt in de context van de applicatie de lijst met fouten opgehaald die daar door [global.asax] is geplaatst – regel 4
- Als deze lijst niet leeg is, wordt de weergave [ERREURS] weergegeven – regels 6-10
- we halen het singleton [articlesDomain] op dat door [global.asax] in de applicatiecontext is geplaatst en slaan dit op in het privéveld [articlesDomain], zodat het beschikbaar is voor de verschillende methoden van de klasse – regel 12
- we doen iets soortgelijks met de array met menuopties – regel 14
- de parameter [action] wordt uit het verzoek opgehaald – regel 16
- we laten de methode uitvoeren die overeenkomt met de gevraagde actie. Een niet-voorziene actie wordt beschouwd als de actie [liste] – regels 16-36
1.5.7.4. Verwerking van de actie [liste]
Het gaat om het weergeven van de artikellijst:

De code is als volgt:
Opmerkingen:
- eventuele fouten worden opgeslagen in een [ArrayList] - regel 4
- de lijst met artikelen wordt opgevraagd bij het singleton [articlesDomain] – regels 5-12
- als er fouten zijn opgetreden, wordt de weergave [ERREURS] verzonden – regels 13-19
- anders wordt de weergave [LISTE] verzonden – regels 20-24
1.5.7.5. Verwerking van de actie [infos]
De klant heeft informatie aangevraagd over een bepaald artikel:

De code is als volgt:
Opmerkingen:
- eventuele fouten worden in een [ArrayList] geplaatst – regel 4
- de ID van het opgevraagde artikel wordt uit de aanvraag gehaald – regel 6
- deze identificatiecode wordt gecontroleerd. Deze moet aanwezig zijn en het moet een geheel getal zijn. Als dit niet het geval is, wordt de weergave [ERREURS] verzonden met de bijbehorende foutmelding – regels 7-27
- zodra de ID is gecontroleerd, wordt het artikel opgevraagd bij het singleton [articlesDomain]. Als er een uitzondering optreedt, wordt de weergave [ERREURS] verzonden - regels 29-39
- als het artikel niet is gevonden, wordt de weergave [ERREURS] verzonden – regels 41-48
- als het artikel is gevonden, wordt het in de sessie van de gebruiker geplaatst en vervolgens weergegeven in de weergave [INFOS] – regels 50-56
1.5.7.6. Verwerking van de actie [achat]
De klant heeft het artikel gekocht dat wordt weergegeven in de weergave [INFOS].

De code is als volgt:
Opmerkingen:
- het artikel dat in de sessie is opgeslagen, wordt opgehaald – regel 5
- als het er niet is (de sessie is mogelijk verlopen), wordt de weergave [LISTE] weergegeven – regels 7-10
- de gekochte hoeveelheid wordt uit de query opgehaald – regel 12
- de geldigheid ervan wordt gecontroleerd – regels 13-29
- in geval van ongeldigheid wordt, afhankelijk van de situatie, de weergave [LISTE] verzonden – regel 16 of de weergave [INFOS] – regels 24-28
- als alles in orde is, wordt de aankoop in het winkelmandje opgeslagen – regels 31-32
- vervolgens wordt het scherm [LISTE] verzonden – regel 34
1.5.7.7. Verwerking van de actie [panier]
De klant heeft verschillende aankopen gedaan en vraagt om het winkelmandje te bekijken:

De code is als volgt:
Opmerkingen:
- het winkelmandje wordt opgehaald uit de sessie – regel 4. Er wordt hier niet gecontroleerd of er daadwerkelijk iets wordt opgehaald. Dit zou wel moeten gebeuren, omdat de sessie mogelijk is verlopen.
- Als het winkelmandje leeg is, wordt de weergave [PANIERVIDE] verzonden – regels 6-10
- anders wordt de weergave [PANIER] verzonden – regels 11-14
1.5.7.8. Verwerking van de actie [retirerachat]
De klant wil een aankoop uit zijn winkelmandje verwijderen:

De code is als volgt:
Opmerkingen:
- het winkelmandje wordt opgehaald uit de sessie – regel 4. Er wordt hier niet gecontroleerd of er daadwerkelijk iets wordt opgehaald. Dit zou wel moeten gebeuren, omdat de sessie mogelijk is verlopen.
- In de query wordt de ID [id] van het te verwijderen artikel opgehaald – regel 8.
- De bijbehorende aankoop wordt uit het winkelmandje verwijderd – regel 10
- De geldigheid van de ID van het gekochte artikel is hier niet gecontroleerd. Als deze een ongeldig type heeft, treedt er een uitzondering op die wordt afgehandeld in de regels 11-13. Als deze geldig is maar niet bestaat, doet de methode [panier.enlever] – regel 10 – niets.
- het nieuwe winkelmandje wordt weergegeven – regel 16
1.5.7.9. Verwerking van de actie [validerpanier]
De klant wil zijn winkelmandje bevestigen:

De code is als volgt:
Opmerkingen:
- het winkelmandje wordt opgehaald uit de sessie – regel 6. Er wordt hier niet gecontroleerd of er daadwerkelijk iets wordt opgehaald. Dit zou wel moeten gebeuren, omdat de sessie mogelijk is verlopen.
- Als de sessie is verlopen, hebben we een pointer [nothing] voor het winkelmandje en zal de methode [acheter] – regel 9 – een uitzondering genereren en wordt de weergave [ERREURS] verzonden. Alleen zal de foutmelding voor de gebruiker weinig duidelijk zijn.
- In de regels 8-16 wordt geprobeerd het uit de sessie opgehaalde winkelmandje te valideren. Sommige aankopen kunnen worden afgewezen als de gevraagde hoeveelheid de voorraad van het betreffende artikel overschrijdt. Deze gevallen worden door de methode [acheter] opgeslagen in een foutenlijst die in regel 11 wordt opgehaald.
- Als er fouten zijn, wordt de weergave [ERREURS] verzonden – regels 18-23
- anders wordt de weergave [LISTE] verzonden – regels 25-26
1.6. Conclusion
We hebben hier een applicatie ontwikkeld volgens een MVC-model. Er lijken (april 2005) geen professionele ontwikkelingsframeworks voor MVC en ASP.NET te bestaan, zoals die wel in Java bestaan (Struts, Spring, ...). Het [Spring.net]-project zou er binnenkort een moeten aanbieden. In afwachting daarvan maakt de hierboven beschreven methode een haalbare MVC-ontwikkeling mogelijk voor middelgrote applicaties.































