Skip to content

1. Teil 1

Die PDF-Datei des Dokuments ist verfügbar |HIER|.

Die Beispiele des Dokuments sind verfügbar |HIER|.

1.1. Introduction

Ziele des Artikels:

  • eine dreischichtige Webanwendung schreiben [interface utilisateur, métier, accès aux données]
  • die Anwendung mit Spring konfigurieren IOC
  • verschiedene Versionen zu erstellen, indem die Implementierung einer der drei Schichten geändert wird.

Verwendete Tools:

  • Visual Studio.net für die Entwicklung – siehe Anhang, Abschnitt 3.1;
  • Cassini-Webserver für die Ausführung – siehe Anhang, Abschnitt 3.2;
  • Nunit für Unit-Tests – siehe Anhang, Abschnitt 3.4;
  • Spring für die Integration und Konfiguration der Schichten der Webanwendung – siehe Anhang, Abschnitt 3.3;

Auf einer Skala von Anfänger über Fortgeschrittene bis hin zu Experten befindet sich dieses Dokument im Bereich [intermédiaire-avancé]. Zum Verständnis sind verschiedene Voraussetzungen erforderlich. Einige davon können in Dokumenten erworben werden, die ich verfasst habe. In diesem Fall verweise ich darauf. Es versteht sich von selbst, dass dies nur ein Vorschlag ist und der Leser seine bevorzugten Dokumente verwenden kann.

Dieses Dokument folgt dem roten Faden eines für Java verfassten Dokuments ([Architectures à 3 couches et architectures MVC avec Struts, Spring et Java ]). Wir erstellen in VB.NET die in Java geschriebene dreischichtige Webanwendung MVC. Die Idee, die wir hier vermitteln möchten, ist, dass die Entwicklungsplattformen Java und .NET einander so ähnlich sind, dass die in einem dieser beiden Bereiche erworbenen Kenntnisse im anderen wiederverwendet werden können.

Es scheint keine weithin anerkannte MVC-ASP.NET-Entwicklungslösung zu geben. Die folgende Lösung greift die im Dokument [Webentwicklung mit ASP.NET 1.1 (2004)] vorgestellte Methode auf. Diese hat zwar den Vorteil, dass sie in der J2EE-Entwicklung verbreitete Konzepte nutzt, sollte jedoch nur als das betrachtet werden, was sie ist: c.a.d – eine von vielen Methoden der MVC-Entwicklung. Sobald sich eine Entwicklungsmethode von MVC zu ASP.NET allgemein durchgesetzt hat, sollte diese übernommen werden. Die derzeit in Entwicklung befindliche Version .NET von Spring könnte durchaus eine erste Lösung sein.

1.2. Die Webanwendung „webarticles“

Wir stellen hier die Elemente einer vereinfachten E-Commerce-Webanwendung vor. Diese ermöglicht es Webkunden:

  • eine Liste von Artikeln aus einer Datenbank einzusehen
  • bestimmte Artikel in einen elektronischen Warenkorb zu legen
  • diesen zu bestätigen. Diese Bestätigung hat lediglich zur Folge, dass die Bestände der gekauften Artikel in der Datenbank aktualisiert werden.

Dem Benutzer werden folgende Ansichten angezeigt:

- die Ansicht „LISTE“, die eine Liste der zum Verkauf stehenden Artikel anzeigt
- die Ansicht „[INFOS]“, die zusätzliche Informationen zu einem Produkt liefert:
  • die Ansichten „[PANIER]“ und „[PANIERVIDE]“, die den Inhalt des Warenkorbs des Kunden anzeigen
  • die Ansicht [ERREURS], die etwaige Fehler der Anwendung meldet

Image

1.3. Allgemeine Architektur der Anwendung

Wir möchten eine Anwendung mit der folgenden dreischichtigen Struktur erstellen:

  • Die drei Schichten werden durch die Verwendung von Schnittstellen voneinander unabhängig gemacht
  • Die Integration der verschiedenen Schichten erfolgt über Spring
  • Jede Schicht verfügt über separate Namespaces: Web (Schicht UI), Domain (Geschäftslogik-Schicht) und DAO (Datenzugriffsschicht).

Die Anwendung folgt einer MVC-Architektur (Model-View-Controller). Wenn wir das obige Schichtschema heranziehen, fügt sich die Architektur wie folgt ein:

Die Bearbeitung einer Anfrage eines Kunden erfolgt in folgenden Schritten:

  1. Der Kunde sendet eine Anfrage an den Controller. Dieser Controller ist hier eine .aspx-Seite, der eine besondere Rolle zukommt. Sie empfängt alle Anfragen der Kunden. Sie ist das Eingangstor der Anwendung. Sie entspricht dem C in MVC.
  1. Der Controller verarbeitet diese Anfrage. Dazu benötigt er möglicherweise die Unterstützung der Geschäftslogikschicht, die in der Struktur MVC als Modell M bezeichnet wird.
  2. Der Controller erhält eine Antwort von der Geschäftslogikschicht. Die Anfrage des Kunden wurde bearbeitet. Dies kann zu mehreren möglichen Antworten führen. Ein klassisches Beispiel ist
    • eine Fehlerseite, falls die Anfrage nicht korrekt verarbeitet werden konnte
    • ansonsten eine Bestätigungsseite
  3. Der Controller wählt die Antwort (= Ansicht) aus, die an den Client gesendet werden soll. Dabei handelt es sich meist um eine Seite mit dynamischen Elementen. Der Controller stellt diese der Ansicht zur Verfügung.
  4. Die Ansicht wird an den Client gesendet. Dies ist das „V“ in MVC.

1.4. Das Modell

Wir befassen uns hier mit dem M von MVC. Das Modell besteht hier aus folgenden Elementen:

  1. die Geschäftsklassen
  2. die Datenzugriffsklassen
  3. die Datenbank

1.4.1. Die Datenbank

Die Datenbank enthält nur eine Tabelle namens ARTICLES. Diese wurde mit den folgenden Befehlen SQL generiert:

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
);
/* Einschränkungen */
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);
/* Primärschlüssel */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
id
Primärschlüssel, der einen Artikel eindeutig identifiziert
nom
Name des Artikels
prix
sein Preis
stockactuel
Aktueller Lagerbestand
stockminimum
der Lagerbestand, bei dessen Unterschreitung eine Nachbestellung erfolgen muss

1.4.2. Die Namensräume des Modells

Das Modell M wird hier in Form von zwei Namensräumen bereitgestellt:

  • istia.st.articles.dao: enthält die Klassen für den Datenzugriff der Schicht [dao]
  • istia.st.articles.domain: enthält die Fachklassen der Schicht [domain]

Jeder dieser Namespaces wird in einer eigenen „Assembly“-Datei generiert:

assembly
contenu
rôle
webarticles-dao
- [IArticlesDao]: die Schnittstelle für den Zugriff auf die Ebene [dao].
Dies ist die einzige Schnittstelle, die die Schicht [domain] sieht. Sie sieht keine andere.
- [Article]: Klasse zur Definition eines Artikels
- [ArticlesDaoArrayList]: Implementierungsklasse der
der Schnittstelle [IArticlesDao] mit einer Klasse [ArrayList]
Datenzugriffsschicht
- befindet sich vollständig in der
[dao] der 3-Tier-Architektur der
der Webanwendung
webarticles-domain
- [IArticlesDomain]: die Schnittstelle für den Zugriff auf die Schicht [domain]. Dies ist die einzige Schnittstelle, die die Webschicht sieht. Sie sieht keine andere.
- [AchatsArticles]: eine Klasse, die [IArticlesDomain] implementiert
- [Achat]: Klasse, die den Einkauf eines Kunden repräsentiert
- [Panier]: Klasse, die die Gesamtheit der Einkäufe eines Kunden darstellt
stellt das Modell der Einkäufe im
Web – befindet sich vollständig in der
Schicht [domain] der Architektur
3-Tier-Architektur der Webanwendung

1.4.3. Die Schicht [dao]

Die Schicht [dao] enthält folgende Elemente:

  • [IArticlesDao]: Schnittstelle für den Zugriff auf die Schicht [dao]

  • [Article]: Klasse zur Definition eines Artikels

  • [ArticlesDaoArrayList]: Implementierungsklasse der Schnittstelle [IArticlesDao] mit einer Klasse [ArrayList]

Die Struktur des Projekts [Visual Studio] der Schicht [dao] ist wie folgt:

Image

Anmerkungen:

  • Das Projekt [dao] ist vom Typ [bibliothèque de classes]
  • Die Klassen wurden in einer Baumstruktur mit dem Stammordner „[istia]“ abgelegt. Sie befinden sich alle im Namensraum „[istia.st.articles.dao]“.

1.4.3.1. Die Klasse [Article]

Die Klasse, die einen Artikel definiert, lautet wie folgt:

Imports System

Namespace istia.st.articles.dao

    Public Class Article

         ' private Felder
        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

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

         ' aktueller Artikelbestand
        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

         ' Mindestbestand des Artikels
        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

         ' Standardhersteller
        Public Sub New()
        End Sub

         ' Hersteller mit Eigenschaften
        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

         ' Artikelidentifikationsmethode
        Public Overrides Function ToString() As String
            Return "[" + id.ToString + "," + nom + "," + prix.ToString + "," + stockactuel.ToString + "," + stockminimum.ToString + "]"
        End Function
    End Class
End Namespace

Diese Klasse bietet:

  1. einen Konstruktor, mit dem die 5 Informationen eines Artikels festgelegt werden können: [id, nom, prix, stockactuel, stockminimum]
  2. öffentliche Eigenschaften zum Lesen und Schreiben der 5 Informationen.
  3. eine Überprüfung der in den Artikel eingegebenen Daten. Bei fehlerhaften Daten wird eine Ausnahme ausgelöst.
  4. eine Methode toString, mit der der Wert eines Artikels als Zeichenkette abgerufen werden kann. Dies ist häufig für die Fehlersuche in einer Anwendung nützlich.

1.4.3.2. Die Schnittstelle [IArticlesDao]

Die Schnittstelle [IArticlesDao] ist wie folgt definiert:

Imports System
Imports System.Collections

Namespace istia.st.articles.dao

    Public Interface IArticlesDao
         ' Liste aller Artikel
        Function getAllArticles() As IList
         ' Artikel hinzufügen
        Function ajouteArticle(ByVal unArticle As Article) As Integer
         ' Artikel löschen
        Function supprimeArticle(ByVal idArticle As Integer) As Integer
         ' Artikel bearbeiten
        Function modifieArticle(ByVal unArticle As Article) As Integer
         ' Artikel suchen
        Function getArticleById(ByVal idArticle As Integer) As Article
         ' Löscht alle Artikel
        Sub clearAllArticles()
         ' ändert den Lagerbestand eines Artikels
        Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
    End Interface
End Namespace

Die verschiedenen Methoden der Schnittstelle haben folgende Funktionen:

getAllArticles
gibt alle Artikel aus der Datenquelle zurück
clearAllArticles
leert die Datenquelle
getArticleById
gibt das durch seinen Primärschlüssel identifizierte Objekt [Article] zurück
ajouteArticle
ermöglicht das Hinzufügen eines Artikels zur Datenquelle
modifieArticle
ermöglicht die Änderung eines Artikels in der Datenquelle
supprimerArticle
ermöglicht das Löschen eines Artikels aus der Datenquelle
changerStockArticle
ermöglicht die Änderung des Bestands eines Artikels in der Datenquelle

Die Schnittstelle stellt Client-Programmen eine Reihe von Methoden zur Verfügung, die ausschließlich durch ihre Signaturen definiert sind. Sie kümmert sich nicht darum, wie diese Methoden tatsächlich implementiert werden. Dies sorgt für Flexibilität in einer Anwendung. Das Client-Programm ruft die Schnittstelle auf und nicht eine bestimmte Implementierung davon.

Die Auswahl einer bestimmten Implementierung erfolgt über eine Spring-Konfigurationsdatei. Um zu zeigen, dass zum Testen der Webanwendung ausschließlich die Schnittstelle für den Datenzugriff von Bedeutung ist und nicht deren Implementierungsklasse, werden wir die Datenquelle zunächst durch ein einfaches Objekt [ArrayList] implementieren. Zu einem späteren Zeitpunkt werden wir eine Lösung auf Basis von SGBD vorstellen.

1.4.3.3. Die Implementierungsklasse [ArticlesDaoArrayList]

Die Implementierungsklasse [ArticlesDaoArrayList] ist wie folgt definiert:

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

         ' Standardhersteller
        Public Sub New()
             ' Es werden einige Artikel erstellt
            For i As Integer = 1 To nbArticles
                articles.Add(New Article(i, "article" + i.ToString, i * 10, i * 10, i * 10))
            Next
        End Sub

         ' Liste aller Artikel
        Public Function getAllArticles() As IList Implements IArticlesDao.getAllArticles
             ' Die Liste der Artikel wird zurückgegeben
            SyncLock Me
                Return articles
            End SyncLock
        End Function

         ' Alle Artikel löschen
        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
             ' Die Artikelliste wird geleert
            SyncLock Me
                articles.Clear()
            End SyncLock
        End Sub

         ' einen Artikel anhand seines Schlüssels abrufen
        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
             ' Artikel in der Liste suchen
            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

         ' einen Artikel zur Artikelliste hinzufügen
        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
             ' Der Artikel wird zur Artikelliste hinzugefügt
            SyncLock Me
                 ' Es wird überprüft, ob er nicht bereits vorhanden ist
                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
                 ' Der Artikel wird hinzugefügt
                articles.Add(unArticle)
                 ' das Ergebnis wird zurückgegeben
                Return 1
            End SyncLock
        End Function

         ' einen Artikel bearbeiten
        Public Function modifieArticle(ByVal articleNouveau As Article) As Integer Implements IArticlesDao.modifieArticle
             ' einen Artikel bearbeiten
            SyncLock Me
                 ' Es wird geprüft, ob er existiert
                Dim ipos As Integer = posArticle(articles, articleNouveau.id)
                 ' falls er nicht existiert
                If ipos = -1 Then Return 0
                 ' er existiert – man bearbeitet ihn
                articles(ipos) = articleNouveau
                 ' das Ergebnis wird zurückgegeben
                Return 1
            End SyncLock
        End Function

         ' einen durch seinen Schlüssel identifizierten Artikel löschen
        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
             ' Löschen eines Artikels
            SyncLock Me
                 ' Es wird geprüft, ob er vorhanden ist
                Dim ipos As Integer = posArticle(articles, idArticle)
                 ' Falls er nicht existiert
                If ipos = -1 Then Return 0
                 ' er existiert – er wird gelöscht
                articles.RemoveAt(ipos)
                 ' das Ergebnis wird zurückgegeben
                Return 1
            End SyncLock
        End Function

         ' den Bestand eines Artikels ändern, der durch seinen Schlüssel identifiziert wird
        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
             ' Bestand eines Artikels ändern
            SyncLock Me
                 ' Es wird geprüft, ob er vorhanden ist
                Dim ipos As Integer = posArticle(articles, idArticle)
                 ' falls er nicht vorhanden ist
                If ipos = -1 Then Return 0
                 ' Er existiert – sein Bestand wird geändert, sofern dies möglich ist
                Dim unArticle As Article = CType(articles(ipos), Article)
                 ' Der Bestand wird nur geändert, wenn er ausreichend ist
                If unArticle.stockactuel + mouvement >= 0 Then
                    unArticle.stockactuel += mouvement
                    Return 1
                Else
                    Return 0
                End If
            End SyncLock
        End Function

         ' einen Artikel anhand seines Schlüssels suchen
        Private Function posArticle(ByVal listArticles As ArrayList, ByVal idArticle As Integer) As Integer
             ' gibt die Position des Artikels [idArticle] in der Liste zurück oder -1, falls er nicht gefunden wurde
            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
             ' nicht gefunden
            Return -1
        End Function
    End Class

End Namespace

Anmerkungen:

  • Die Datenquelle wird durch das private Feld [articles] vom Typ [ArrayList] simuliert
  • Der Konstruktor der Klasse legt standardmäßig 4 Einträge in der Datenquelle an.
  • Alle Methoden für den Datenzugriff wurden synchronisiert, um Probleme durch gleichzeitigen Zugriff auf die Datenquelle zu vermeiden. Zu einem bestimmten Zeitpunkt hat jeweils nur ein Thread Zugriff auf eine bestimmte Methode.
  • Die Methode [posArticle] ermöglicht es, die Position [0..N] eines Artikels, der durch seine Nummer identifiziert wird, in der Quelle [ArrayList] zu ermitteln. Existiert der Artikel nicht, gibt die Methode die Position -1 zurück. Diese Methode wird wiederholt von den anderen Methoden aufgerufen.
  • Die Methode [ajouteArticle] ermöglicht es, einen Artikel zur Artikelliste hinzuzufügen. Sie gibt die Anzahl der eingefügten Artikel zurück: 1. Wenn der Artikel bereits existierte, wird eine Ausnahme ausgelöst.
  • Die Methode [modifieArticle] ermöglicht es, einen vorhandenen Artikel zu ändern. Sie gibt die Anzahl der geänderten Artikel zurück: 1, wenn der Artikel vorhanden war, andernfalls 0.
  • Die Methode [supprimeArticle] ermöglicht das Löschen eines vorhandenen Artikels. Sie gibt die Anzahl der gelöschten Artikel zurück: 1, wenn der Artikel vorhanden war, andernfalls 0.
  • Die Methode [getAllArticles] gibt die Liste aller Artikel zurück.
  • Die Methode [getArticleById] ermöglicht es, einen Artikel anhand seiner Nummer abzurufen. Der Wert [nothing] wird zurückgegeben, wenn der Artikel nicht existiert.
  • Der Code stellt keine wirkliche Schwierigkeit dar. Wir überlassen es dem Leser, ihn durchzugehen und zu verstehen.

1.4.3.4. Erstellung der Assembly der Schicht [dao]

Das Visual Studio-Projekt ist so konfiguriert, dass die Assembly [webarticles-dao.dll] generiert wird. Diese wird im Ordner [bin] des Projekts generiert:

1.4.3.5. NUnit-Tests der Schicht [dao]

In Java werden die Klassen mit dem Framework [Junit] getestet. In .NET bietet das Nunit-Framework dieselben Möglichkeiten für Unit-Tests:

Image

Die Struktur des Visual Studio-Testprojekts sieht wie folgt aus:

Image

Anmerkungen:

  • Das Projekt [tests] ist vom Typ [bibliothèque de classes]
  • Die Tests [NUnit] erfordern einen Verweis auf die Assembly [nunit.framework.dll]
  • Die Testklasse [NUnit] ruft über Spring eine Instanz des zu testenden Objekts ab. Daher befinden sich
    • im Ordner [bin] die Spring-Klassendateien
    • in [References] einen Verweis auf die Assembly [Spring-Core.dll] aus dem Ordner [bin]
    • in [bin] eine Konfigurationsdatei für Spring
  • Die Testklasse benötigt die Assembly [webarticles-dao.dll] aus der Ebene [dao]. Diese wurde im Ordner [bin] abgelegt und ihr Verweis zu den Projektverweisen hinzugefügt.

Eine Testklasse [NUnit] benötigt Zugriff auf die Klassen des Namensraums [NUnit.Framework]. Daher enthält sie die folgende Importanweisung:

Imports NUnit.Framework

Der Namensraum [NUnit.Framework] befindet sich in der „Assembly“ [nunit.framework.dll], die zu den Projektverweisen hinzugefügt werden muss:

Die Assembly [nunit.framework.dll] sollte in der vorgeschlagenen Liste enthalten sein, wenn die Installation von [Nunit] bereits erfolgt ist. Ein Doppelklick auf die Assembly reicht aus, um sie dem Projekt hinzuzufügen:

Image

Die Testklasse [NUnit] der Schicht [dao] könnte wie folgt aussehen:

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
         ' das zu testende Objekt
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
             ' Es wird eine Instanz des Spring-Objektgenerators abgerufen
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
             ' die Instanziierung des Objekts articlesdao wird angefordert
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
        End Sub

        <Test()> _
        Public Sub testGetAllArticles()
             ' Sichtprüfung
            listArticles()
        End Sub

        <Test()> _
        Public Sub testClearAllArticles()
             ' Alle Artikel werden gelöscht
            articlesDao.clearAllArticles()
             ' Alle Artikel werden abgefragt
            Dim articles As IList = articlesDao.getAllArticles
             ' Überprüfung: Es müssen 0 vorhanden sein
            Assert.AreEqual(0, articles.Count)
        End Sub

        <Test()> _
        Public Sub testAjouteArticle()
             ' Alle Artikel löschen
            articlesDao.clearAllArticles()
             ' Überprüfung: Die Artikeltabelle muss leer sein
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' Es werden zwei Artikel hinzugefügt
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' Überprüfung: Es müssen zwei Artikel vorhanden sein
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' Sichtprüfung
            listArticles()
        End Sub

        <Test()> _
        Public Sub testSupprimeArticle()
             ' Alle Artikel werden gelöscht
            articlesDao.clearAllArticles()
             ' Überprüfung: Die Artikeltabelle muss leer sein
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' Es werden zwei Artikel hinzugefügt
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' Überprüfung: Es müssen 2 Artikel vorhanden sein
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' Artikel 4 wird gelöscht
            articlesDao.supprimeArticle(4)
             ' Überprüfung: Es muss 1 Artikel übrig bleiben
            articles = articlesDao.getAllArticles
            Assert.AreEqual(1, articles.Count)
             ' Sichtprüfung
            listArticles()
        End Sub

        <Test()> _
        Public Sub testModifieArticle()
             ' Alle Artikel löschen
            articlesDao.clearAllArticles()
             ' Überprüfung
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' Hinzufügen von 2 Artikeln
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' Überprüfung
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' Suche nach Artikel 3
            Dim unArticle As Article = articlesDao.getArticleById(3)
             ' Überprüfung
            Assert.AreEqual(unArticle.nom, "article3")
             ' Suche nach Artikel 4
            unArticle = articlesDao.getArticleById(4)
             ' Überprüfung
            Assert.AreEqual(unArticle.nom, "article4")
             ' Änderung von Artikel 4
            articlesDao.modifieArticle(New Article(4, "article4", 44, 44, 44))
             ' Überprüfung
            unArticle = articlesDao.getArticleById(4)
            Assert.AreEqual(unArticle.prix, 44, 0.000001)
             ' Sichtprüfung
            listArticles()
        End Sub

        <Test()> _
        Public Sub testGetArticleById()
             ' Artikel löschen
            articlesDao.clearAllArticles()
             ' Überprüfung
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' Hinzufügen von 2 Artikeln
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' Überprüfung
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' Suche nach Artikel 3
            Dim unArticle As Article = articlesDao.getArticleById(3)
             ' Überprüfung
            Assert.AreEqual(unArticle.nom, "article3")
             ' Suche nach Artikel 4
            unArticle = articlesDao.getArticleById(4)
             ' Überprüfung
            Assert.AreEqual(unArticle.nom, "article4")
        End Sub

         ' Bildschirmauflistung
        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()
             ' Löschen aller Artikel
            articlesDao.clearAllArticles()
             ' Artikelsuche 1
            Dim article As article = articlesDao.getArticleById(1)
             ' Überprüfung
            Assert.IsNull(article)
             ' Änderung eines nicht vorhandenen Artikels
            Dim i As Integer = articlesDao.modifieArticle(New article(1, "1", 1, 1, 1))
             ' Es durften keine Zeilen geändert werden
            Assert.AreEqual(i, 0)
             ' Löschen eines nicht vorhandenen Artikels
            i = articlesDao.supprimeArticle(1)
             ' Es mussten keine Zeilen gelöscht werden
            Assert.AreEqual(0, i)
        End Sub

        <Test()> _
        Public Sub testChangerStockArticle()
             ' Löschen aller Artikel
            articlesDao.clearAllArticles()
             ' Hinzufügen eines Artikels
            Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
            Assert.AreEqual(nbArticles, 1)
             ' Hinzufügen eines Artikels
            nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            Assert.AreEqual(nbArticles, 1)
             ' Erstellung von 100 Threads
            Dim taches(99) As Thread
            For i As Integer = 0 To taches.Length - 1
                 ' Der Thread i wird erstellt
                taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
                 ' Der Name des Threads wird festgelegt
                taches(i).Name = "tache_" & i
                 ' Der Thread i wird gestartet
                taches(i).Start()
            Next
             ' Es wird auf das Ende aller Threads gewartet
            For i As Integer = 0 To taches.Length - 1
                taches(i).Join()
            Next
             ' Prüfungen  Artikel 3 muss einen Bestand von 1 haben
            Dim unArticle As Article = articlesDao.getArticleById(3)
            Assert.AreEqual(unArticle.nom, "article3")
            Assert.AreEqual(1, unArticle.stockactuel)
             ' Der Lagerbestand von Artikel 4 wird verringert
            Dim erreur As Boolean = False
            Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
             ' Prüfung: Der Bestand sollte sich nicht verändert haben
            Assert.AreEqual(0, nbLignes)
             ' Sichtprüfung
            listArticles()
        End Sub

        Public Sub décrémente()
             ' Thread gestartet
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
             ' Thread verringert den Lagerbestand
            articlesDao.changerStockArticle(3, -1)
             ' Thread beendet
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
        End Sub

    End Class
End Namespace

Anmerkungen:

  • Wir wollten ein Testprogramm für die Schnittstelle [IArticlesDao] schreiben, das unabhängig von der Implementierungsklasse dieser Schnittstelle ist. Daher haben wir Spring verwendet, um den Namen der Implementierungsklasse vor dem Testprogramm zu verbergen.
  • Die Attributmethode <Setup()> ruft von Spring eine Referenz auf das zu testende Objekt [articlesdao] ab. Dieses ist in der folgenden Datei [spring-config.xml] definiert:
<?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>

Diese Datei gibt den Namen der Implementierungsklasse [istia.st.articles.dao.ArticlesDaoArrayList] der Schnittstelle [IArticlesDao] an und wo sie zu finden ist: [ webarticles-dao.dll]. Da die Instanziierung keine Parameter erfordert, sind hier keine definiert.

  • Die meisten Tests sind leicht verständlich. Der Leser wird gebeten, die Kommentare zu lesen.
  • Die Methode [testChangerStockArticle] bedarf einiger Erläuterungen. Sie erstellt 100 Threads, die den Lagerbestand eines bestimmten Artikels verringern sollen.
        <Test()> _
        Public Sub testChangerStockArticle()
             ' Alle Artikel löschen
            articlesDao.clearAllArticles()
             ' Artikel hinzufügen
            Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
            Assert.AreEqual(nbArticles, 1)
             ' Artikel hinzufügen
            nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            Assert.AreEqual(nbArticles, 1)
             ' Erstellung von 100 Threads
            Dim taches(99) As Thread
            For i As Integer = 0 To taches.Length - 1
                 ' Der Thread i wird erstellt
                taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
                 ' Der Name des Threads wird festgelegt
                taches(i).Name = "tache_" & i
                 ' Start der Ausführung von Thread i
                taches(i).Start()
            Next
             ' Warten auf das Ende aller Threads
            For i As Integer = 0 To taches.Length - 1
                taches(i).Join()
            Next
             ' Prüfungen – Artikel 3 muss einen Bestand von 1 haben
            Dim unArticle As Article = articlesDao.getArticleById(3)
            Assert.AreEqual(unArticle.nom, "article3")
            Assert.AreEqual(1, unArticle.stockactuel)
             ' Der Lagerbestand von Artikel 4 wird verringert
            Dim erreur As Boolean = False
            Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
             ' Prüfung: Der Bestand sollte sich nicht verändert haben
            Assert.AreEqual(0, nbLignes)
             ' Sichtprüfung
            listArticles()
        End Sub

Hier geht es darum, gleichzeitige Zugriffe auf die Datenquelle zu testen. Die Methode zur Aktualisierung des Lagerbestands lautet wie folgt:

        Public Sub décrémente()
             ' Thread gestartet
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
             ' Thread verringert den Lagerbestand
            articlesDao.changerStockArticle(3, -1)
             ' Thread beendet
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
        End Sub

Sie verringert den Bestand von Artikel Nr. 3 um eine Einheit. Wenn man sich den Code der Methode [testChangerStockArticle] ansieht, stellt man fest, dass:

  • der Bestand des Artikels Nr. 3 auf 101 initialisiert wird
  • die 100 Threads verringern diesen Bestand jeweils um eine Einheit
  • Am Ende der Ausführung aller Threads sollte der Bestand daher bei 1 liegen

Außerdem wird in derselben Methode versucht, den Bestand von Artikel Nr. 4 auf einen negativen Wert zu setzen. Dies muss fehlschlagen.

Um die Schicht [dao] zu testen, generieren wir die Dateien DLL und [tests-webarticles-dao.dll] im Ordner [bin] des Projekts [tests]:

Anschließend laden wir mithilfe der Anwendung [Nunit-Gui] diese DLL und führen die Tests durch:

Image

Im linken Fenster sehen wir die Liste der getesteten Methoden. Die Farbe des Punktes vor dem Namen jeder Methode zeigt an, ob die Methode erfolgreich (grün) oder fehlgeschlagen (rot) ist. Der Leser, der dieses Dokument auf dem Bildschirm betrachtet, kann sehen, dass alle Tests erfolgreich waren. Im weiteren Verlauf gehen wir davon aus, dass wir über eine funktionsfähige Schicht [dao] verfügen.

1.4.4. Die Schicht [domain]

Die Schicht [domain] enthält folgende Elemente:

  • [IArticlesDomain]: die Schnittstelle für den Zugriff auf die Schicht [domain]

  • [Achat]: Klasse, die einen Einkauf definiert

  • [Panier]: Klasse, die einen Warenkorb definiert

  • [AchatsArticles]: Implementierungsklasse der Schnittstelle [IArticlesDomain]

Die Struktur der Lösung [Visual Studio] der Schicht [domain] ist wie folgt:

Image

Anmerkungen:

  • Das Projekt [domain] ist vom Typ [bibliothèque de classes]
  • Die Klassen wurden in einer Baumstruktur mit dem Stammordner „[istia]“ abgelegt. Sie befinden sich alle im Namensraum „[istia.st.articles.domain]“.
  • Die Klasse DLL aus der Ebene [dao] wurde in den Ordner [bin] des neuen Projekts verschoben. Außerdem wurde diese Klasse DLL als Referenz zum Projekt hinzugefügt.

1.4.4.1. Die Schnittstelle [IArticlesDomain]

Die Schnittstelle [IArticlesDomain] entkoppelt die Schicht [métier] von der Schicht [web]. Letztere greift über diese Schnittstelle auf die Schicht [métier/domain] zu, ohne sich um die Klasse zu kümmern, die sie tatsächlich implementiert. Die Schnittstelle definiert die folgenden Aktionen für den Zugriff auf die Geschäftsschicht:

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
Function getAllArticles() As IList
gibt die Objektliste [Article] der zugehörigen Datenquelle zurück
Function getArticleById(ByVal idArticle As Integer) As Article
gibt das durch [idArticle] identifizierte Objekt [Article] zurück
acheter(ByVal panier As Panier)
validiert den Warenkorb des Kunden, indem die Bestände der gekauften Artikel um die gekaufte Menge verringert werden – kann fehlschlagen, wenn der Bestand nicht ausreicht
ReadOnly Property erreurs() As ArrayList
gibt die Liste der aufgetretenen Fehler zurück – leer, wenn keine Fehler vorliegen

1.4.4.2. Die Klasse [Achat]

Die Klasse [Achat] repräsentiert einen Kauf des Kunden:

Imports istia.st.articles.dao

Namespace istia.st.articles.domain

    Public Class Achat

         ' private Felder
        Private _article As article
        Private _qte As Integer

         ' Standardkonstruktor
        Public Sub New()
        End Sub

         ' Konstruktor mit Parametern
        Public Sub New(ByVal unArticle As article, ByVal qte As Integer)
             ' über Eigenschaften
            Me.article = unArticle
            Me.qte = qte
        End Sub

         ' gekaufter Artikel
        Public Property article() As article
            Get
                Return _article
            End Get
            Set(ByVal Value As article)
                _article = Value
            End Set
        End Property

         ' gekaufte Menge
        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

         ' Gesamtbetrag des Kaufs
        Public ReadOnly Property totalAchat() As Double
            Get
                Return _qte * _article.prix
            End Get
        End Property

         ' Identität
        Public Overrides Function ToString() As String
            Return "[" + _article.ToString + "," + _qte.ToString + "]"
        End Function
    End Class

End Namespace

Anmerkungen:

  • Die Klasse [Achat] verfügt über die folgenden Eigenschaften und Methoden:
Public Property article() As article
Der gekaufte Artikel
Public Property qte() As Integer
die gekaufte Menge
Public ReadOnly Property totalAchat() As Double
Kaufbetrag
Public Overrides Function ToString() As String
Identitätszeichenfolge des Objekts
  • Sie verfügt über einen Konstruktor, mit dem die Eigenschaften [article, qte] initialisiert werden können, die einen Kauf definieren.

1.4.4.3. Die Klasse [Panier]

Die Klasse [Panier] repräsentiert die Gesamtheit der Einkäufe des Kunden:

Namespace istia.st.articles.domain

Public Class Panier

     ' Private Felder
    Private _achats As New ArrayList
        Private _totalPanier As Double = 0

     ' Standardhersteller
    Public Sub New()
    End Sub

     ' Einkaufsliste
    Public ReadOnly Property achats() As ArrayList
        Get
            Return _achats
        End Get
    End Property

     ' Gesamtbetrag der Einkäufe
    Public ReadOnly Property totalPanier() As Double
        Get
            Return _totalPanier
        End Get
    End Property

     ' Methoden
    Public Sub ajouter(ByVal unAchat As Achat)
         ' Es wird geprüft, ob der Einkauf bereits vorhanden ist
        Dim iAchat As Integer = posAchat(unAchat.article.id)
        If iAchat <> -1 Then
             ' Es wurde gefunden
                Dim achatCourant As Achat = CType(_achats(iAchat), Achat)
            achatCourant.qte += unAchat.qte
        Else
             ' nicht gefunden
            _achats.Add(unAchat)
        End If
         ' Die Warenkorb-Gesamtsumme wird erhöht
        _totalPanier += unAchat.totalAchat
    End Sub

     ' einen Artikel aus dem Warenkorb entfernen
    Public Sub enlever(ByVal idAchat As Integer)
         ' Nach dem Artikel suchen
            Dim iachat As Integer = posAchat(idAchat)
         ' Wenn gefunden, wird der Artikel entfernt
        If iachat <> -1 Then
                Dim achatCourant As Achat = CType(_achats(iachat), Achat)
             ' aus dem Warenkorb entfernen
            _achats.RemoveAt(iachat)
             ' Warenkorb-Gesamtbetrag verringern
            _totalPanier -= achatCourant.totalAchat
        End If
    End Sub

    Private Function posAchat(ByVal idArticle As Integer) As Integer
         ' Sucht einen Kauf in der Kaufliste
         ' gibt die Position in der Liste zurück oder -1, wenn nicht gefunden
        Dim achatCourant As Achat
        Dim trouvé As Boolean = False
        Dim i As Integer = 0
            While Not trouvé AndAlso i < _achats.Count
                 ' aktueller Einkauf
                achatCourant = CType(_achats(i), Achat)
                 ' Vergleich mit dem gesuchten Artikel
                If achatCourant.article.id = idArticle Then
                    Return i
                End If
                 'nächster Kauf
                i += 1
            End While
             ' nicht gefunden
            Return -1
    End Function

     ' Identitätsfunktion
    Public Overrides Function ToString() As String
        Return _achats.ToString
    End Function
End Class

End Namespace

Anmerkungen:

  • Die Klasse [Panier] verfügt über die folgenden Eigenschaften und Methoden:
ReadOnly Property achats() As ArrayList
Die Einkaufsliste des Kunden – Liste von Objekten vom Typ [Achat]
ajouter(ByVal unAchat As Achat)
Fügt einen Einkauf zur Einkaufsliste hinzu
enlever(ByVal idAchat As Integer)
Entfernt den Kauf des Artikels idAchat
ReadOnly Property totalPanier() As Double
Gesamtbetrag der Einkäufe im Warenkorb
Function ToString() As String
gibt die Identitätszeichenfolge des Warenkorbs zurück
  • Die Methode [posAchat] ist eine Hilfsmethode, mit der die Position eines durch die Artikelnummer identifizierten Kaufs in der Einkaufsliste ermittelt werden kann. Die Einkaufsliste wird so verwaltet, dass ein mehrfach gekaufter Artikel nur eine Position in der Liste einnimmt. Somit kann ein Kauf anhand der Artikelnummer identifiziert werden. Die Methode [posAchat] gibt -1 zurück, wenn der gesuchte Kauf nicht vorhanden ist.
  • Die Methode [ajouter] fügt der Einkaufsliste einen neuen Einkauf hinzu. Dies bedeutet entweder, dass ein neuer Eintrag zur Einkaufsliste hinzugefügt wird, falls der gekaufte Artikel noch nicht in der Liste vorhanden war, oder dass die gekaufte Menge erhöht wird, falls er bereits vorhanden war.
  • Die Methode [enlever] ermöglicht es, einen durch eine Nummer identifizierten Kauf aus der Kaufliste zu entfernen. Wenn der Kauf nicht vorhanden ist, führt die Methode keine Aktion aus.
  • Der Gesamtbetrag der Einkäufe ([totalPanier]) wird bei Hinzufügungen und Entfernungen von Einkäufen beibehalten.

1.4.4.4. Die Klasse [AchatsArticles]

Die Schnittstelle [IArticlesDomain] wird durch die folgende Klasse [AchatsArticles] implementiert:

Imports istia.st.articles.dao

Namespace istia.st.articles.domain
    Public Class AchatsArticles
        Implements IArticlesDomain

         'Private Felder
        Private _articlesDao As IArticlesDao
        Private _erreurs As ArrayList

         ' Konstruktor
        Public Sub New(ByVal articlesDao As IArticlesDao)
            _articlesDao = articlesDao
        End Sub

         ' Fehlerliste
        Public ReadOnly Property erreurs() As ArrayList Implements IArticlesDomain.erreurs
            Get
                Return _erreurs
            End Get
        End Property

         ' Artikelübersicht
        Public Function getAllArticles() As IList Implements IArticlesDomain.getAllArticles
             ' Liste aller Artikel
            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

         ' einen Artikel anhand seiner Nummer abrufen
        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDomain.getArticleById
             ' einen bestimmten 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


         ' Warenkorb kaufen
        Public Sub acheter(ByVal panier As Panier) Implements IArticlesDomain.acheter
             ' Warenkorb kaufen – die Bestände der gekauften Artikel müssen verringert werden
            _erreurs = New ArrayList
            Dim achat As achat
            Dim achats As ArrayList = panier.achats
            For i As Integer = achats.Count - 1 To 0 Step -1
                 ' Bestand von Artikel i verringern
                achat = CType(achats(i), achat)
                Try
                    If _articlesDao.changerStockArticle(achat.article.id, -achat.qte) = 0 Then
                         ' Der Vorgang konnte nicht ausgeführt werden
                        _erreurs.Add("L'achat " + achat.ToString + " n'a pu se faire - Vérifiez les stocks")
                    Else
                         ' Der Vorgang wurde ausgeführt – der Kauf wird aus dem Warenkorb entfernt
                        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

Anmerkungen:

  • Diese Klasse implementiert die vier Methoden der Schnittstelle [IArticlesDomain]. Sie verfügt über zwei private Felder:
_articlesDao As IArticlesDao
das Datenzugriffsobjekt
_erreurs As ArrayList
die Liste eventueller Fehler. Sie ist über die öffentliche Eigenschaft [erreurs] zugänglich
  • Um eine Instanz der Klasse zu erstellen, muss das Objekt bereitgestellt werden, das den Zugriff auf die Daten ermöglicht:
Sub New(ByVal articlesDao As IArticlesDao)
  • Die Methoden [getAllArticles] und [getArticleById] basieren auf den gleichnamigen Methoden der Schicht [dao]
  • Die Methode [acheter] bestätigt den Kauf eines Warenkorbs. Diese Bestätigung besteht lediglich darin, die Bestände der gekauften Artikel zu verringern. Der Kauf eines Artikels ist nur möglich, wenn der Lagerbestand dies zulässt. Ist dies nicht der Fall, wird der Kauf abgelehnt: Der Artikel verbleibt im Warenkorb und in der Liste [erreurs] wird ein Fehler gemeldet. Ein bestätigter Kauf wird aus dem Warenkorb entfernt und der Lagerbestand des entsprechenden Artikels um die gekaufte Menge verringert.

1.4.4.5. Erstellung der Assembly der Schicht [domain]

Das Visual Studio-Projekt ist so konfiguriert, dass die Assembly [webarticles-domain.dll] generiert wird. Diese wird im Ordner [bin] des Projekts generiert:

1.4.4.6. Tests NUnit der Schicht [domain]

Die Struktur des Visual Studio-Testprojekts sieht wie folgt aus:

Image

Anmerkungen:

  • Das Projekt [tests] ist vom Typ [bibliothèque de classes]
  • Die Tests [NUnit] erfordern einen Verweis auf die Assembly [nunit.framework.dll]
  • Die Testklasse [NUnit] ruft über Spring eine Instanz des zu testenden Objekts ab. Daher befinden sich
  • im Ordner „[bin]“ die Spring-Klassendateien
  • in [References] einen Verweis auf die Assembly [Spring-Core.dll] aus dem Ordner [bin]
  • in [bin] eine Konfigurationsdatei für Spring
  • Die Testklasse benötigt die Assembly [webarticles-dao.dll] aus der Ebene [dao] und die Assembly [webarticles-domain.dll] aus der Ebene [domain]. Diese wurden im Ordner „[bin]“ abgelegt und ihre Verweise zu den Projektverweisen hinzugefügt.

Eine Testklasse NUnit der Ebene [domain] könnte wie folgt aussehen:

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

         ' das zu testende Objekt
        Private articlesDomain As IArticlesDomain
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
             ' Eine Instanz des Spring-Objektgenerators wird abgerufen
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
             ' Die Instanziierung des DAO-Objekts articles wird angefordert
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
             ' anschließend die des articlesdomain-Objekts
            articlesDomain = CType(factory.GetObject("articlesdomain"), IArticlesDomain)
        End Sub

        <Test()> _
        Public Sub getAllArticles()
             ' Sichtprüfung
            listArticles()
        End Sub

        <Test()> _
        Public Sub getArticleById()
             ' Löschen der Artikel
            articlesDao.clearAllArticles()
             ' Überprüfung
            Dim articles As IList = articlesDomain.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' Hinzufügen von 2 Artikeln
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' Überprüfung
            articles = articlesDomain.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' Suche nach Artikel 3
            Dim unArticle As Article = articlesDomain.getArticleById(3)
             ' Überprüfung
            Assert.AreEqual(unArticle.nom, "article3")
             ' Suche nach Artikel 4
            unArticle = articlesDao.getArticleById(4)
             ' Überprüfung
            Assert.AreEqual(unArticle.nom, "article4")
        End Sub

        <Test()> _
        Public Sub acheterPanier()
             ' Artikel löschen
            articlesDao.clearAllArticles()
             ' Überprüfung
            Dim articles As IList = articlesDomain.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' Hinzufügen von 2 Artikeln
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' Überprüfung
            articles = articlesDomain.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' Erstellung eines Warenkorbs mit zwei Artikeln
            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))
             ' Überprüfungen
            Assert.AreEqual(700, panier.totalPanier, 0.000001)
            Assert.AreEqual(2, panier.achats.Count)
             ' Warenkorb bestätigen
            articlesDomain.acheter(panier)
             ' Prüfungen
            Assert.AreEqual(0, articlesDomain.erreurs.Count)
            Assert.AreEqual(0, panier.achats.Count)
             ' Artikelsuche 3
            Dim unArticle As Article = articlesDomain.getArticleById(3)
             ' Überprüfung
            Assert.AreEqual(unArticle.stockactuel, 20)
             ' Artikelsuche 4
            unArticle = articlesDao.getArticleById(4)
             ' Überprüfung
            Assert.AreEqual(unArticle.stockactuel, 30)
             ' Neuer Warenkorb
            panier.ajouter(New Achat(New Article(3, "article3", 30, 30, 3), 100))
             ' Warenkorb bestätigen
            articlesDomain.acheter(panier)
             ' Überprüfungen
            Assert.AreEqual(1, articlesDomain.erreurs.Count)
             ' Artikelsuche 3
            unArticle = articlesDomain.getArticleById(3)
             ' Überprüfung
            Assert.AreEqual(unArticle.stockactuel, 20)
        End Sub

        <Test()> _
        Public Sub testRetirerAchats()
             ' Inhalt von ARTICLES löschen
            articlesDao.clearAllArticles()
             ' liest die Tabelle ARTICLES
            Dim articles As IList = articlesDao.getAllArticles()
            Assert.AreEqual(0, articles.Count)
             ' Einfügen
            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)
             ' liest die Tabelle ARTICLES
            articles = articlesDomain.getAllArticles()
            Assert.AreEqual(2, articles.Count)
             ' Erstellung eines Warenkorbs mit zwei Artikeln
            Dim monPanier As New Panier
            monPanier.ajouter(New Achat(article3, 10))
            monPanier.ajouter(New Achat(article4, 10))
             ' Prüfungen
            Assert.AreEqual(700.0, monPanier.totalPanier, 0.000001)
            Assert.AreEqual(2, monPanier.achats.Count)
             ' einen bereits gekauften Artikel hinzufügen
            monPanier.ajouter(New Achat(article3, 10))
             ' Überprüfungen
             ' Der Gesamtbetrag muss auf 1000 geändert werden
            Assert.AreEqual(1000.0, monPanier.totalPanier, 0.000001)
             ' Immer noch 2 Artikel im Warenkorb
            Assert.AreEqual(2, monPanier.achats.Count)
             ' Die Stückzahl von Artikel 3 muss auf 20 geändert werden
            Dim unAchat As Achat = CType(monPanier.achats(0), Achat)
            Assert.AreEqual(20, unAchat.qte)
             ' Artikel 3 wird aus dem Warenkorb entfernt
            monPanier.enlever(3)
             ' Überprüfungen
             ' Der Gesamtbetrag muss auf 400 geändert werden
            Assert.AreEqual(400.0, monPanier.totalPanier, 0.000001)
             ' Nur 1 Artikel im Warenkorb
            Assert.AreEqual(1, monPanier.achats.Count)
             ' Es muss sich um Artikel Nr. 4 handeln
            Assert.AreEqual(4, CType(monPanier.achats(0), Achat).article.id)
        End Sub

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

Anmerkungen:

  • Wir wollten ein Testprogramm für die Schnittstelle [IArticlesDomain] schreiben, das unabhängig von der Implementierungsklasse dieser Schnittstelle ist. Daher haben wir Spring verwendet, um den Namen der Implementierungsklasse vor dem Testprogramm zu verbergen.
  • Die Attributmethode <Setup()> ruft von Spring eine Referenz auf die zu testenden Objekte [articlesdomain] und [articlesdao] ab. Diese sind in der folgenden Datei [spring-config.xml] definiert:
<?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>

Diese Datei enthält:

  • (Fortsetzung)
    • für das Singleton [articlesdao] den Namen der Implementierungsklasse [istia.st.articles.dao.ArticlesDaoArrayList] und wo diese zu finden ist: [ webarticles-dao.dll]. Da die Instanziierung keine Parameter erfordert, sind hier keine definiert.
    • Für das Singleton [articlesdomain] lautet der Name der Implementierungsklasse [istia.st.articles.domain.AchatsArticles] und sie ist in [ webarticles-domain.dll] zu finden. Die Klasse [AchatsArticles] verfügt über einen Konstruktor mit einem Parameter: das Singleton, das den Zugriff auf die Schicht [dao] verwaltet. Hier ist dieses als das zuvor definierte Singleton [articlesdao] definiert.
  • Die Testklasse erhält eine Instanz der zu testenden Klasse [articlesdomain] sowie eine Instanz der Datenzugriffsklasse [articlesdao]. Dieser letzte Punkt ist umstritten. Theoretisch sollte die Testklasse keinen Zugriff auf die Schicht [dao] benötigen, die sie gar nicht kennen sollte. Hier haben wir diese „Ethik“ außer Acht gelassen, deren Einhaltung uns dazu gezwungen hätte, neue Methoden in unserer Schnittstelle [IArticlesDomain] zu erstellen.

Um die Schicht [domain] zu testen, generieren wir die Dateien DLL und [tests-webarticles-domain.dll] im Ordner [bin] des Projekts [tests]:

Anschließend laden wir mithilfe der Anwendung [Nunit-Gui] diese Datei DLL und führen die Tests durch:

Image

Der Leser, der dieses Dokument auf dem Bildschirm betrachtet, wird sehen, dass alle Tests erfolgreich waren. Im weiteren Verlauf gehen wir davon aus, dass wir über eine funktionsfähige [domain]-Schicht verfügen.

1.4.5. Fazit

Zur Erinnerung: Wir möchten die folgende dreischichtige Webanwendung erstellen:

Das Modell M unserer Anwendung MVC ist nun geschrieben und getestet. Es wird uns in zwei Dateien bereitgestellt: DLL und [webarticles-dao.dll, webarticles-domain.dll]. Wir können nun zur letzten Schicht übergehen, der Schicht [web], die den Controller C und die Views V enthält. Wir betrachten zunächst eine Methode, die im Dokument [Développement WEB avec ASP.NET 1.1 ] vorgestellt wird

  • Der Controller C wird durch zwei Dateien bereitgestellt: [global.asax, main.aspx]
  • Die V-Ansichten werden durch ASPX-Seiten bereitgestellt

1.5. Die Schicht [web]

Die Architektur MVC der Webanwendung sieht wie folgt aus:

M=modèle
die Fachklassen [domain], die Datenzugriffsklassen [dao] und die Datenquelle
V=vues
die Seiten ASPX
C=contrôleur
alle Client-Anfragen HTTP werden über die folgenden beiden Controller geleitet:
global.asax: Verarbeitet Ereignisse im Zusammenhang mit dem ersten Start der Anwendung
main.aspx: verarbeitet die Anfragen der einzelnen Kunden

1.5.1. Die Ansichten

Die Ansichten entsprechen denen, die zu Beginn des Dokuments vorgestellt wurden:

LISTE
liste.aspx
Die Ansichten sind im Ordner „[vues]“ der Anwendung zusammengefasst
INFOS
infos.aspx
PANIER
panier.aspx
PANIERVIDE
paniervide.aspx
ERREURS
erreurs.aspx

1.5.2. Die Steuergeräte

Wie bereits erwähnt, besteht der Controller aus zwei Komponenten:

  1. [global.asax,global.asax.vb]: wird hauptsächlich dazu verwendet, die Anwendung zu initialisieren und alle Daten, die zwischen den verschiedenen Clients ausgetauscht werden sollen, in den Kontext der Anwendung einzubinden
  2. [main.aspx, main.aspx.vb]: der eigentliche Controller, der die Anfragen HTTP der Clients verarbeitet.

Die verschiedenen Anfragen der Clients werden an den Controller [main.aspx] gerichtet und enthalten einen Parameter namens [action], der die vom Client angeforderte Aktion angibt:

Anfrage
Bedeutung
Aktion des Controllers
Mögliche Antworten
action=liste
Der Kunde möchte die Liste der
Artikel
– fordert die Liste der Artikel bei der
an
– [LISTE]
- [ERREURS]
action=infos
Der Kunde fragt
Informationen zu einem der
in der Ansicht angezeigten Artikel
[LISTE]
– fordert den Artikel bei der Geschäftsebene an
- [INFOS]
- [ERREURS]
action=achat
Der Kunde kauft einen Artikel
- fordert den Artikel bei der Geschäftsebene an
und legt ihn in den Warenkorb des Kunden
- [INFOS] bei Mengenfehler
- [LISTE], wenn kein Fehler vorliegt
action=retirerachat
Der Kunde möchte einen
Artikel aus seinem Warenkorb entfernen
- ruft den Warenkorb aus der Sitzung ab
und ändert ihn
- [PANIER]
- [PANIERVIDE]
- [ERREURS]
action=panier
Der Kunde möchte seinen
Warenkorb
- ruft den Warenkorb aus der Sitzung ab
- [PANIER]
- [PANIERVIDE]
- [ERREURS]
action=validationpanier
Der Kunde hat seinen Einkauf abgeschlossen
und wechselt zur Zahlungsphase
- aktualisiert die Bestände der gekauften Artikel
der gekauften Artikel
- leert den Warenkorb des Kunden von den Artikeln,
, deren Kauf bestätigt wurde
- [LISTE]
- [ERREURS]

1.5.3. Konfiguration der Anwendung

Wir werden versuchen, die Anwendung so zu konfigurieren, dass sie gegenüber Änderungen wie den folgenden möglichst flexibel ist:

  1. Änderungen an den URL der verschiedenen Ansichten
  2. die Änderung der Klassen, die die Schnittstellen [IArticlesDao] und [IArticlesDomain] implementieren
  3. Änderung des SGBD, der Datenbank und der Artikeltabelle

1.5.3.1. Änderungen an URL

Die Namen der Ansichten URL werden zusammen mit einigen weiteren Parametern in die Anwendungskonfigurationsdatei [web.config] aufgenommen:

<?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. Änderung der Implementierungsklassen von Schnittstellen

Im Sinne der dreischichtigen Architektur müssen die Schichten voneinander isoliert sein. Diese Isolierung wird wie folgt erreicht:

  • Die Schichten kommunizieren untereinander über Schnittstellen und nicht über konkrete Klassen
  • Der Code einer Schicht instanziiert niemals selbst die Klasse einer anderen Schicht, um sie zu verwenden. Er fordert lediglich von einem externen Tool – in diesem Fall Spring – eine Instanz der Implementierung der Schnittstelle der Schicht an, die er nutzen möchte. Dazu muss er nicht den Namen der Implementierungsklasse kennen, sondern lediglich den Namen des Spring-Singletons, auf das er verweisen möchte.

In unserer Anwendung wird Spring in der Datei [web.config] der Webanwendung wie folgt konfiguriert:

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

Um Zugriff auf die Schicht [métier] zu erhalten, kann eine Klasse der Schicht [web] das Singleton [articlesDomain] anfordern. Spring instanziiert dann ein Objekt vom Typ [istia.st.articles.domain.AchatsArticles]. Für diese Instanziierung benötigt es ein Objekt vom Typ [articlesDao], d. h. ein Objekt vom Typ [istia.st.articles.dao.ArticlesDaoArrayList]. Spring instanziiert dann ein solches Objekt. Am Ende des Vorgangs verfügt die Schicht [web], die das Singleton [articlesDomain] angefordert hat, über die gesamte Kette, die sie mit der Datenquelle verbindet:

1.5.3.3. Änderungen im Zusammenhang mit SGBD oder der Datenbank

Dieser Punkt wird hier außer Acht gelassen, da wir uns in einer Testanwendung ohne SGBD befinden. In einem zweiten Schritt werden wir die Implementierung einer Schicht [dao] behandeln, die auf einem SGBD basiert.

1.5.4. Die <asp:>-Tag-Bibliothek

Betrachten wir die Ansicht [ERREURS], die eine Fehlerliste anzeigt:

Image

Die Ansicht [ERREURS] ist dafür zuständig, eine Fehlerliste anzuzeigen, die das Controller-Element [main.aspx] unter dem Namen [context.Items("erreurs")] in den Anforderungskontext gestellt hat. Es gibt mehrere Möglichkeiten, eine solche Seite zu schreiben. Wir befassen uns hier ausschließlich mit dem Teil zur Anzeige der Fehler.

Zur Erinnerung: Eine Seite ASPX besteht aus einem Layout-Teil HTML und einem Code-Teil .NET, der die Daten aufbereitet, die der Layout-Teil anzeigen soll. Diese beiden Teile können sich in derselben Datei [aspx] (Lösung WebMatrix) oder in zwei Dateien befinden: [aspx] für die Darstellung, [aspx.vb] für den Code. Letztere Lösung wird von Visual Studio verwendet. Um die Sache noch komplizierter zu machen, kann der Layout-Teil HTML ebenfalls Code enthalten (NET), was die Trennung zwischen [contrôleur] und [présentation] aus der Ansicht heraus verwischt. Von dieser Vorgehensweise wird im Allgemeinen dringend abgeraten. Um den gesamten Code aus dem [présentation]-Teil zu entfernen, mussten Tag-Bibliotheken erstellt werden. Diese „verbergen“ den Code unter dem Deckmantel von Tags, die den HTML-Tags ähneln. Wir stellen zwei mögliche Lösungen für die Seite [ERREURS] vor.

Unsere erste Lösung verwendet Code aus .NET im Teil [présentation] der Seite. Die Seite ASPX ruft die in der Anfrage enthaltene Fehlerliste in ihrem Controller-Teil [erreurs.aspx.vb] ab:

        Protected erreurs As ArrayList

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
             ' Fehler werden abgerufen
            erreurs = CType(context.Items("erreurs"), ArrayList)
        End Sub

und zeigt es dann im Bereich [présentation, erreurs.aspx] an:

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

Die zweite Lösung nutzt das Tag <asp:repeater> aus der Tag-Bibliothek <asp:> von ASP.NET. Wenn man eine Seite ASPX grafisch erstellt, steht dieses Tag als Serverkomponente zur Verfügung, die man auf das Entwurfsformular zieht. Wenn man den Code ASPX manuell erstellt, spricht man von einer Tag-Bibliothek.

Mit der Tag-Bibliothek <asp:> sieht der Code ASPX der vorherigen Ansicht [ERREURS] wie folgt aus:

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

Das Tag

        <asp:Repeater id="rptErreurs" runat="server">

dient dazu, ein Muster HTML auf die verschiedenen Elemente einer Datenquelle anzuwenden. Die verschiedenen Elemente lauten wie folgt:

HeaderTemplate
das Muster HTML, das angezeigt werden soll, bevor die Elemente der Datenquelle angezeigt werden
ItemTemplate
das Muster HTML, das für jedes Element der Datenquelle wiederholt werden soll. Der Ausdruck [<%# Container.DataItem %>] dient dazu, den Wert des aktuellen Elements der Datenquelle anzuzeigen
FooterTemplate
Das Muster HTML wird angezeigt, nachdem die Elemente der Datenquelle angezeigt wurden

Die Datenquelle wird in der Regel im Abschnitt [contrôleur] der Seite mit dem Tag verknüpft:

        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
..
             ' Die Fehler werden mit rptErreurs verknüpft
            With rptErreurs
                .DataSource = context.Items("erreurs")
                .DataBind()
            End With
        End Sub

Diese Verknüpfung kann auch bereits bei der Gestaltung der Seite hergestellt werden, wenn die Datenquelle bereits bekannt ist, beispielsweise eine bestehende Datenbank.

In unseren Ansichten verwenden wir ein anderes Tag: <asp:datagrid>, mit dem eine Datenquelle in Form einer Tabelle angezeigt werden kann.

1.5.5. Struktur der Visual Studio-Lösung der Anwendung [webarticles]

Eine Webanwendung ist wie ein Puzzle mit vielen Elementen. Die Verwendung einer MVC-Architektur erhöht in der Regel deren Anzahl. Die Struktur der Anwendung [webarticles] unter [Visual Studio] sieht wie folgt aus:

  

Anmerkungen:

  • Das Projekt [web] ist vom Typ [bibliothèque de classes] und nicht vom Typ [Application web ASP.NET], wie man logischerweise erwarten würde. Der Typ [Application web ASP.NET] setzt voraus, dass der Webserver IIS auf dem Entwicklungsrechner oder auf einem Remote-Rechner vorhanden ist. Der Server IIS ist standardmäßig auf Windows-Rechnern der Version XP „Familial“ nicht vorhanden. Viele Rechner der Version PC werden jedoch mit dieser Version verkauft. Damit Leser mit Windows XP die behandelte Anwendung implementieren können, verwenden wir den Webserver Cassini (siehe Anhang), der kostenlos bei Microsoft erhältlich ist, und ersetzen das Projekt [Application web ASP.NET] durch ein Projekt [bibliothèque de classes]. Dies bringt einige Nachteile mit sich, die im Anhang erläutert werden.
  • Die von der Anwendung verwendeten DLL-Dateien sind folgende:
webarticles-dao.dll
umfasst die Klassen der Datenzugriffsebene
webarticles-domain.dll
fasst die Klassen der Geschäftssschicht zusammen
Spring.Core.dll
enthält die Spring-Klassen, mit denen wir die Web-, Domain- und DAO-Schichten integrieren können
log4net.dll
Protokollierungsklassen – werden von Spring verwendet

Diese DLL-Dateien werden im Ordner [bin] abgelegt und den Projektverweisen hinzugefügt.

1.5.6. Die Ansichten ASPX

Wie bereits empfohlen, werden wir in unseren Ansichten ASPX die <asp:>-Tag-Bibliothek verwenden.

1.5.6.1. Die Benutzerkomponente [entete.ascx]

Um den verschiedenen Ansichten eine gewisse Einheitlichkeit zu verleihen, werden diese denselben Kopfbereich verwenden, in dem der Name der Anwendung zusammen mit dem Menü angezeigt wird:

Das Menü ist dynamisch und wird vom Controller festgelegt. Dieser fügt in die an die Seite ASPX übermittelte Anfrage ein Schlüsselattribut „actions“ ein, dessen zugehöriger Wert ein Array von Elementen vom Typ Hashtable() ist. Jedes Element dieses Arrays ist ein Dictionary, das dazu dient, eine Option des Kopfzeilenmenüs zu generieren. Jedes Dictionary hat zwei Schlüssel:

  • href: die mit der Menüoption verknüpfte URL
  • link: der Menütext

Wir werden die Kopfzeile zu einem Benutzer-Steuerelement machen. Ein Benutzer-Steuerelement kapselt einen Teil der Seite (Darstellung und zugehöriger Code) in einer Komponente, die anschließend auf anderen Seiten wiederverwendet werden kann. Hier möchten wir die Komponente [entete] in den anderen Ansichten der Anwendung wiederverwenden. Der Layout-Code befindet sich in [entete.ascx] und der zugehörige Steuerungscode in [entete.ascx.vb]. Der Layout-Code verwendet eine <asp:repeater>-Komponente, um die Tabelle mit den Menüoptionen anzuzeigen:

type
nom
rôle
1
Repeater
rptMenu
Datenquelle: ein Array von Dictionaries
mit zwei Schlüsseln: href, Link
Menüoptionen anzeigen

Der Code zur Darstellung der Seite lautet wie folgt:

<%@ Control codebehind="entete.ascx.vb" Language="vb" autoeventwireup="false" inherits="istia.st.articles.web.EnteteWebArticles" %>
        <table>
            <tr>
                <td>
                    <h2>Magasin virtuel</h2></td>
                <asp:Repeater id="rptMenu" runat="server">
                    <ItemTemplate>
                        <td>
                            |<a href='<%# Container.DataItem("href") %>'>
                                <%# Container.DataItem("Link") %>
                            </a>
                        </td>
                    </ItemTemplate>
                </asp:Repeater>
            </tr>
        </table>
        <hr>

Anmerkungen:

  • Die Komponente [repeater] ist in den Zeilen 6–14 definiert
  • Jedes Element der dem Repeater zugeordneten Datenquelle ist ein Wörterbuch mit zwei Schlüsseln: href – Zeile 9 und link – Zeile 10

Der zugehörige Steuerungscode lautet wie folgt:

Namespace istia.st.articles.web
    Public Class EnteteWebArticles
        Inherits System.Web.UI.UserControl

        Protected WithEvents rptMenu As System.Web.UI.WebControls.Repeater

        Public WriteOnly Property actions() As Hashtable()
            Set(ByVal Value As Hashtable())
                ' Das Aktionsarray wird seiner Komponente zugeordnet
                With rptMenu
                    .DataSource = Value
                    .DataBind()
                End With
            End Set
        End Property
    End Class
End Namespace

Anmerkungen:

  • Die Komponente vom Typ [EnteteWebArticles] verfügt über eine öffentliche, schreibgeschützte Eigenschaft [actions] – Zeile 7
  • Diese Eigenschaft ermöglicht es, der <asp:repeater>-Komponente mit dem Namen [rptMenu] – Zeile 10 – das vom Anwendungscontroller berechnete Optionsarray – Zeilen 11–12 – zuzuordnen.

Die anderen Ansichten der Anwendung verwenden den von [entete.ascx] definierten Header. Die Seite [erreurs.aspx] beispielsweise bindet den Header mithilfe des folgenden Codes ein:

<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx" %>
<%@ Page inherits="istia.st.articles.web.ErreursWebarticles" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h3>Les erreurs suivantes se sont produites :
        </h3>    

Anmerkungen:

  • Zeile 1 legt fest, dass das Tag <WA:entete> der durch die Datei [entete.ascx] definierten Komponente zugeordnet werden soll. Die Attribute [TagPrefix] und [TagName] können frei gewählt werden.
  • Anschließend erfolgt das Einfügen der Komponente in den Layout-Code der Seite mit Zeile 9. Bei der Ausführung bewirkt dieses Tag, dass in den Code der Seite ASPX, die sie enthält, der Code der Seite [entete.ascx] eingebunden wird. Der Steuerungscode [erreurs.aspx.vb] sorgt für die Initialisierung dieser Komponente. Dies kann auf folgende Weise erfolgen:
    Public Class ErreursWebarticles
        Inherits System.Web.UI.Page

         ' Seitenkomponenten
...
        Protected WithEvents entete As New EnteteWebArticles

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
             ' Die Menüoptionen werden mit rptmenu verknüpft
            entete.actions = CType(context.Items("options"), Hashtable())
...
        End Sub
    End Class

Anmerkungen:

  • In Zeile 6 wird ein Objekt vom Typ [EnteteWebArticles] erstellt, was dem Typ der erstellten Komponente entspricht
  • Zeile 11 initialisiert die Eigenschaft [actions] dieses Objekts

1.5.6.2. Die Ansicht [liste.aspx]

1.5.6.2.1. Introduction

Diese Ansicht zeigt die Liste der zum Verkauf verfügbaren Artikel an:

Sie wird nach einer Anfrage an /main?action=liste oder /main?action=validationpanier angezeigt. Die Elemente der Controller-Anfrage lauten wie folgt:

actions
Objekt Hashtable() – die Tabelle der Menüoptionen
listarticles
ArrayList Objekte vom Typ [Article]
message
String-Objekt – am Seitenende anzuzeigende Meldung

Jeder Link [Infos] aus der Tabelle HTML der Artikel hat eine URL in der Form [?action=infos&id=ID], wobei ID das Feld „id“ desangezeigten Artikels ist.

1.5.6.2.2. Seitenkomponenten
type
nom
rôle
1
Benutzerkomponente
Kopfzeile
Kopfzeile anzeigen
2
DataGrid
DataGridArticles
3 – zugehörige Spalte: Überschrift: Name, Feld: Name
4 – zugehörige Spalte: Überschrift: Preis, Feld: Preis
5 – Hypertext-Spalte: Text: Infos, URL-Feld: id,
Format URL: /webarticles/main.aspx?action=infos&id={0}
Artikel im Verkauf anzeigen
6
Bezeichnung
lblMessage
Eine Nachricht anzeigen

Zur Erinnerung: So legen Sie diese Eigenschaften fest:

  • In Visual Studio wählen Sie „[DataGrid]“ aus, um Zugriff auf dessen Eigenschaftenblatt zu erhalten:

Image

  • Verwenden Sie den oben genannten Link [Mise en forme automatique], um das Layout der angezeigten Tabelle zu verwalten
  • und den Link „[Générateur de propriétés]“, um dessen Inhalt zu bearbeiten
1.5.6.2.3. Darstellungscode [liste.aspx]
<%@ Page codebehind="liste.aspx.vb" inherits="istia.st.articles.web.ListeWebarticles" autoeventwireup="false" Language="vb" %>
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx"%>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h2>Liste des articles</h2>
        <P>
            <asp:DataGrid id="DataGridArticles" runat="server" ForeColor="Black" BackColor="LightGoldenrodYellow"
                BorderColor="Tan" CellPadding="2" BorderWidth="1px" GridLines="None" AutoGenerateColumns="False">
                <SelectedItemStyle ForeColor="GhostWhite" BackColor="DarkSlateBlue"></SelectedItemStyle>
                <AlternatingItemStyle BackColor="PaleGoldenrod"></AlternatingItemStyle>
                <HeaderStyle Font-Bold="True" BackColor="Tan"></HeaderStyle>
                <FooterStyle BackColor="Tan"></FooterStyle>
                <Columns>
                    <asp:BoundColumn DataField="nom" HeaderText="Nom"></asp:BoundColumn>
                    <asp:BoundColumn DataField="prix" HeaderText="Prix" DataFormatString="{0:C}"></asp:BoundColumn>
                    <asp:HyperLinkColumn Text="Infos" DataNavigateUrlField="id" DataNavigateUrlFormatString="/webarticles/main.aspx?action=infos&amp;id={0}"></asp:HyperLinkColumn>
                </Columns>
                <PagerStyle HorizontalAlign="Center" ForeColor="DarkSlateBlue" BackColor="PaleGoldenrod"></PagerStyle>
            </asp:DataGrid></P>
        <P>
            <asp:Label id="lblMessage" runat="server" BackColor="#FFC080"></asp:Label></P>
    </body>
</HTML>

Anmerkungen:

  • Zeile 9 definiert die Kopfzeile der Seite
  • Die Zeilen 12–24 definieren die Merkmale von [DataGrid]
  • Zeile 26 definiert die Bezeichnung [lblMessage]
1.5.6.2.4. Controller-Code [liste.aspx.vb]
Imports System
Imports System.Collections
Imports System.Data
Imports istia.st.articles.dao

Namespace istia.st.articles.web

     ' verwaltet die Seite zur Anzeige der Artikelliste
    Public Class ListeWebarticles
        Inherits System.Web.UI.Page

         ' Seitenkomponenten
        Protected WithEvents lblMessage As System.Web.UI.WebControls.Label
        Protected WithEvents DataGridArticles As System.Web.UI.WebControls.DataGrid
        Protected WithEvents entete As New EnteteWebArticles

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' die Ansicht [liste] wird anhand der Kontextinformationen vorbereitet
             ' Die Menüoptionen werden mit rptmenu verknüpft
            entete.actions = CType(context.Items("options"), Hashtable())
             ' Die Artikel werden in eine Datentabelle geladen
            Dim articles As ArrayList = CType(context.Items("articles"), ArrayList)
             ' sie werden mit der Komponente [DataGrid] der Seite verknüpft
            With DataGridArticles
                .DataSource = articles
                .DataBind()
            End With
             ' Die Meldung wird angezeigt
            lblMessage.Text = context.Items("message").ToString
        End Sub
    End Class
End Namespace

Anmerkungen:

  • Die Komponenten der Seite erscheinen in den Zeilen 13–15. Zu beachten ist, dass ein Objekt [EnteteWebArticles] mit einem Operator [new] angelegt werden musste, während dies bei den anderen Komponenten nicht erforderlich war. Ohne diese explizite Erstellung trat ein Laufzeitfehler auf, der darauf hinwies, dass das Objekt [entete] auf nichts verwies. Dieser Punkt wäre einer genaueren Untersuchung wert gewesen. Dies geschah jedoch nicht.
  • Die Tabelle mit den Optionen des Kopfmenüs wird aus dem Kontext übernommen, um die Komponente [entete] der Seite zu initialisieren – Zeile 20
  • Die Artikelliste wird aus dem Kontext übernommen – Zeile 22
  • zur Initialisierung der Komponente [DataGridArticles] – Zeilen 24–27
  • Die Komponente [lblMessage] wird mit einer im Kontext hinterlegten Meldung initialisiert – Zeile 29

1.5.6.3. Die Ansicht [infos.aspx]

1.5.6.3.1. Introduction

Diese Ansicht zeigt Informationen zu einem Artikel an und ermöglicht zudem dessen Kauf:

Image

Sie wird nach einer Anfrage /main?action=infos&id=ID oder einer Anfrage /main?action=achat&id=ID angezeigt, wenn die gekaufte Menge falsch ist. Die Elemente der Controller-Anfrage lauten wie folgt:

actions
Objekt Hashtable() – die Tabelle der Menüoptionen
article
Objekt vom Typ [Article] – anzuzeigender Artikel
msg
String-Objekt – Meldung, die bei einem Fehler bezüglich der Menge angezeigt werden soll
qte
String-Objekt – im Eingabefeld anzuzeigender Wert [Qte]

Die Felder [msg] und [qte] werden bei einem Eingabefehler bei der Menge verwendet:

Image

Diese Seite enthält ein Formular, das über die Schaltfläche [Acheter] übermittelt wird. Das Ziel von URL ist POST, wobei [?action=achat&id=ID] die ID des gekauften Artikels ist.

1.5.6.3.2. Die Komponenten der Seite
type
nom
rôle
1
Benutzerkomponente
Kopfzeile
Kopfzeile anzeigen
2
Literal
litID
Artikelnummer anzeigen
3 à 6
DataGrid
DataGridArticle
3 – zugehörige Spalte: Überschrift: Name, Feld: Name
4 – zugehörige Spalte: Überschrift: Preis, Feld: Preis
5 – zugehörige Spalte: Überschrift: Aktueller Bestand, Feld: stockActuel
6 – zugehörige Spalte: Überschrift: Mindestbestand, Feld: stockMinimum
Artikel anzeigen
7
HTML Absenden
 
Formular absenden
8
HTML Eingabe
runat=server
txtQte
Gekaufte Menge eingeben
9
Bezeichnung
lblMsgQte
Eventuelle Fehlermeldung
1.5.6.3.3. Der Präsentationscode [infos.aspx]
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx" %>
<%@ Page codebehind="infos.aspx.vb" inherits="istia.st.articles.web.InfosWebarticles" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h2>Article d'id [<asp:Literal id="litId" runat="server"></asp:Literal>]</h2>
        <P>
            <asp:DataGrid id="DataGridArticle" runat="server" BackColor="White" BorderColor="#E7E7FF" CellPadding="3"
                BorderWidth="1px" BorderStyle="None" GridLines="Horizontal" AutoGenerateColumns="False">
                <SelectedItemStyle Font-Bold="True" ForeColor="#F7F7F7" BackColor="#738A9C"></SelectedItemStyle>
                <AlternatingItemStyle BackColor="#F7F7F7"></AlternatingItemStyle>
                <ItemStyle HorizontalAlign="Center" ForeColor="#4A3C8C" BackColor="#E7E7FF"></ItemStyle>
                <HeaderStyle Font-Bold="True" HorizontalAlign="Center" ForeColor="#F7F7F7" BackColor="#4A3C8C"></HeaderStyle>
                <FooterStyle ForeColor="#4A3C8C" BackColor="#B5C7DE"></FooterStyle>
                <Columns>
                    <asp:BoundColumn DataField="nom" HeaderText="Nom">
                        <HeaderStyle HorizontalAlign="Center"></HeaderStyle>
                    </asp:BoundColumn>
                    <asp:BoundColumn DataField="prix" HeaderText="Prix" DataFormatString="{0:C}">
                        <HeaderStyle HorizontalAlign="Center"></HeaderStyle>
                    </asp:BoundColumn>
                    <asp:BoundColumn DataField="stockactuel" HeaderText="Stock actuel">
                        <HeaderStyle HorizontalAlign="Center"></HeaderStyle>
                    </asp:BoundColumn>
                    <asp:BoundColumn DataField="stockminimum" HeaderText="Stock minimum">
                        <HeaderStyle HorizontalAlign="Center"></HeaderStyle>
                    </asp:BoundColumn>
                </Columns>
                <PagerStyle HorizontalAlign="Right" ForeColor="#4A3C8C" BackColor="#E7E7FF" Mode="NumericPages"></PagerStyle>
            </asp:DataGrid></P>
        <HR width="100%" SIZE="1">
        <form method="post" action="<%=strAction%>">
            <table>
                <tr>
                    <td><input type="submit" value="Acheter"></td>
                    <td>Qté</td>
                    <td><INPUT type="text" maxLength="3" size="3" id="txtQte" runat="server"></td>
                    <td><asp:Label id="lblMsgQte" runat="server" />
                    </td>
                </tr>
            </table>
        </form>
    </body>
</HTML>

Anmerkungen:

  • Der Header ist in der Seite enthalten – Zeile 9
  • Die Konstante [litId] ist in Zeile 10 definiert
  • DataGrid und [DataGridArticles] sind in den Zeilen 12–34 definiert
  • Das Formular ist in den Zeilen 36–46 definiert. Es hat den Typ POST.
  • Das Ziel von POST wird durch die Variable [strAction] bereitgestellt – Zeile 36. Diese Variable muss vom Controller definiert werden.
  • Das Eingabefeld für die gekaufte Menge ist in Zeile 41 definiert. Es handelt sich um eine Serverkomponente HTML (runat=server). Im Code kann über ein Objekt darauf zugegriffen werden.
  • In Zeile 42 wird die Beschriftung [lblMsgQte] definiert, die eine eventuelle Fehlermeldung zur eingegebenen Menge enthält.
1.5.6.3.4. Der Steuerungscode [infos.aspx.vb]
Imports istia.st.articles.dao
Imports System
Imports System.Collections

Namespace istia.st.articles.web

     ' verwaltet die Informationsseite zu einem Artikel
    Public Class InfosWebarticles
        Inherits System.Web.UI.Page
        Protected WithEvents lblMsgQte As System.Web.UI.WebControls.Label
        Protected WithEvents litId As System.Web.UI.WebControls.Literal
        Protected WithEvents txtQte As System.Web.UI.HtmlControls.HtmlInputText
        Protected WithEvents DataGridArticle As System.Web.UI.WebControls.DataGrid
        Protected WithEvents entete As New EnteteWebArticles

         ' die Seite URL, auf der das Formular übermittelt wird
        Private _strAction As String
        Public Property strAction() As String
            Get
                Return _strAction
            End Get
            Set(ByVal Value As String)
                _strAction = Value
            End Set
        End Property

         ' Seite anzeigen
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' die Informationen aus der Anfrage werden abgerufen
            Dim unArticle As Article = CType(Session.Item("article"), Article)
             ' Die Menüoptionen werden mit rptmenu verknüpft
            entete.actions = CType(context.Items("options"), Hashtable())
             ' Der Artikel wird mit [DataGrid] verknüpft
            Dim articles As New ArrayList
            articles.Add(unArticle)
            With DataGridArticle
                .DataSource = articles
                .DataBind()
            End With
             ' die Label-ID
            litId.Text = unArticle.id.ToString
             ' die Fehlermeldung
            lblMsgQte.Text = context.Items("msg").ToString
             ' die vorherige Menge
            txtQte.Value = context.Items("qte").ToString
             ' die Aktion
            strAction = "?action=achat&id=" + unArticle.id.ToString
        End Sub
End Namespace

Anmerkungen:

  • Die Komponenten der Seite werden in den Zeilen 10–14 definiert
  • Die Klasse definiert eine öffentliche Eigenschaft [strAction], die dazu dient, das Ziel des POST des Formulars festzulegen – Zeilen 17–25
  • Der anzuzeigende Artikel wird aus dem Anwendungskontext abgerufen – Zeile 30
  • Die Tabelle mit den Optionen für das Kopfmenü wird aus dem Kontext übernommen, um die Komponente [entete] der Seite zu initialisieren – Zeile 32
  • Zeilen 33–39: Die Komponente [DataGridArticle] wird mit einer Datenquelle vom Typ [ArrayList] verknüpft, die ausschließlich den in Zeile 30 abgerufenen Artikel enthält
  • Die Komponenten [lblMsgQte, txtQte] werden mit Informationen aus dem Kontext initialisiert – Zeilen 42–45
  • Die Eigenschaft [straction] wird ebenfalls mit einer aus dem Kontext entnommenen Information initialisiert – Zeile 47. Diese Variable dient dazu, das Attribut [action] des Formulars HTML zu generieren, das auf der Seite vorhanden ist:
        <form method="post" action="<%=strAction%>">
....
        </form>

1.5.6.4. Die Ansicht [panier.aspx]

1.5.6.4.1. Introduction

Diese Ansicht zeigt den Inhalt des Warenkorbs an:

Image

Sie wird nach einer Anfrage an /main?action=panier oder /main?action=retirerachat&id=ID angezeigt. Die Elemente der Controller-Anfrage lauten wie folgt:

actions
Objekt „Hashtable()“ – die Tabelle mit den Menüoptionen
panier
Objekt vom Typ [Panier] – der anzuzeigende Warenkorb

Jeder Link [Retirer] aus dem Array HTML der Warenkorb-Artikel hat einen URL der Form [?action=retirerachat&id=ID], wobei ID das Feld [id] des Artikels, den man aus dem Warenkorb entfernen möchte.

1.5.6.4.2. Die Komponenten der Seite
type
nom
rôle
1
Benutzerkomponente
Kopfzeile
Kopfzeile anzeigen
2
DataGrid
DataGridAchats
3 – zugehörige Spalte – Überschrift: Artikel, Feld: Name
4 – Verknüpfte Spalte – Überschrift: Menge, Feld: Menge
5 – zugehörige Spalte – Überschrift: Preis, Feld: Preis
6 – zugehörige Spalte – Überschrift: Gesamt, Feld: Gesamt, Formatierung {0:C}
7 – Hypertext-Spalte – Text: Entfernen, URL: id, URL-Format: /webarticles/main.aspx?action=retirerachat&id={0}
Liste der gekauften Artikel anzeigen
8
Bezeichnung
lblTotal
Zu zahlenden Betrag anzeigen
1.5.6.4.3. Der Darstellungscode [panier.aspx]
<%@ Page codebehind="panier.aspx.vb" inherits="istia.st.articles.web.PanierWebarticles" autoeventwireup="false" Language="vb" %>
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h2>Contenu de votre panier</h2>
        <P>
            <asp:DataGrid id="DataGridAchats" runat="server" BorderWidth="1px" GridLines="Vertical" CellPadding="4"
                BackColor="White" BorderStyle="None" BorderColor="#DEDFDE" ForeColor="Black" AutoGenerateColumns="False">
                <SelectedItemStyle Font-Bold="True" ForeColor="White" BackColor="#CE5D5A"></SelectedItemStyle>
                <AlternatingItemStyle BackColor="White"></AlternatingItemStyle>
                <ItemStyle BackColor="#F7F7DE"></ItemStyle>
                <HeaderStyle Font-Bold="True" ForeColor="White" BackColor="#6B696B"></HeaderStyle>
                <FooterStyle BackColor="#CCCC99"></FooterStyle>
                <Columns>
                    <asp:BoundColumn DataField="nom" HeaderText="Article"></asp:BoundColumn>
                    <asp:BoundColumn DataField="qte" HeaderText="Qt&#233;"></asp:BoundColumn>
                    <asp:BoundColumn DataField="prix" HeaderText="Prix"></asp:BoundColumn>
                    <asp:BoundColumn DataField="totalAchat" HeaderText="Total" DataFormatString="{0:C}"></asp:BoundColumn>
                    <asp:HyperLinkColumn Text="Retirer" DataNavigateUrlField="id" DataNavigateUrlFormatString="/webarticles/main.aspx?action=retirerachat&amp;id={0}"></asp:HyperLinkColumn>
                </Columns>
                <PagerStyle HorizontalAlign="Right" ForeColor="Black" BackColor="#F7F7DE" Mode="NumericPages"></PagerStyle>
            </asp:DataGrid></P>
        <P>Total de la commande :
            <asp:Label id="lblTotal" runat="server"></asp:Label>&nbsp;euros</P>
    </body>
</HTML>

Anmerkungen

  • Zeile 9 enthält die Kopfzeile
  • In den Zeilen 12–27 wird die Komponente [DataGridAchats] definiert
  • In Zeile 29 wird die Komponente [lblTotal] definiert
1.5.6.4.4. Der Prüfcode [panier.aspx.vb]
Imports System
Imports System.Collections
Imports System.Data
Imports istia.st.articles.dao
Imports istia.st.articles.domain

Namespace istia.st.articles.web
     ' verwaltet die Warenkorb-Anzeigeseite
    Public Class PanierWebarticles
        Inherits System.Web.UI.Page
        Protected WithEvents DataGridAchats As System.Web.UI.WebControls.DataGrid
        Protected WithEvents lblTotal As System.Web.UI.WebControls.Label
        Protected WithEvents entete As New EnteteWebArticles

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' die Menüoptionen werden mit rptmenu verknüpft
            entete.actions = CType(context.Items("options"), Hashtable())
             ' Der Warenkorb wird abgerufen
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' die Einkäufe werden in eine Tabelle mit Einkaufszeilen übertragen
            Dim achats(unPanier.achats.Count - 1) As LigneAchat
             ' Der Typ der Elemente von ArrayList wird geändert
            For i As Integer = 0 To achats.Length - 1
                achats(i) = New LigneAchat(CType(unPanier.achats(i), Achat))
            Next
             ' Die Daten werden mit den Komponenten [DataGrid] der Seite verknüpft
            With DataGridAchats
                .DataSource = achats
                .DataBind()
            End With
             ' Die zu zahlende Gesamtsumme wird angezeigt
            lblTotal.Text = unPanier.totalPanier.ToString
        End Sub

         ' Kaufzeile, die aus einem Kaufobjekt erstellt wurde
        Private Class LigneAchat
            Inherits Achat

             ' Der Konstruktor erhält einen Kauf
            Public Sub New(ByVal unAchat As Achat)
                Me.article = unAchat.article
                Me.qte = unAchat.qte
            End Sub

             ' ID: Gibt die ID des gekauften Artikels zurück
            Public ReadOnly Property id() As Integer
                Get
                    Return article.id
                End Get
            End Property

             ' Name: Name des gekauften Artikels
            Public ReadOnly Property nom() As String
                Get
                    Return article.nom
                End Get
            End Property

             ' Preis des gekauften Artikels
            Public ReadOnly Property prix() As Double
                Get
                    Return article.prix
                End Get
            End Property

        End Class
    End Class
End Namespace

Anmerkungen:

  • Die Komponenten der Seite werden in den Zeilen 11–13 deklariert
  • Die Initialisierung der Komponente [entete] entspricht derjenigen auf den bereits behandelten Seiten – Zeile 17
  • Der anzuzeigende Warenkorb wird aus der Sitzung abgerufen – Zeile 19
  • Die Anzeige dieses Warenkorbs mithilfe der Komponente [DataGridAchats] bereitet Probleme. Die Schwierigkeit liegt in der Initialisierung der Komponente. Erinnern wir uns an deren Spalten:
    • Spalte [Article], die dem Feld [nom] der Datenquelle zugeordnet ist
    • Spalte [Qté], die dem Feld [qte] der Datenquelle zugeordnet ist
    • Spalte [Prix], zugeordnet zu einem Feld [prix] der Datenquelle
    • Spalte [Total], die dem Feld [total] der Datenquelle zugeordnet ist

Die uns zur Verfügung stehende Datenquelle ist der Warenkorb und die dazugehörige Einkaufsliste. Letztere dient als Datenquelle für [DataGrid]. Nur die Objekte [Achat], die die Zeilen des [DataGrid] füllen sollen, weisen nicht die vom [DataGrid] erwarteten Eigenschaften des [nom, qte, prix, total] auf. Daher wird hier speziell für das Objekt [DataGrid] eine Datenquelle angelegt, deren Elemente die vom Objekt [DataGrid] erwarteten Eigenschaften aufweisen. Diese Elemente werden vom Typ [LigneAchat] sein, einer eigens für diesen Zweck erstellten Klasse, die von der Klasse [Achat] abgeleitet ist – Zeilen 36–66

  • Nachdem die Klasse [LigneAchat] definiert wurde, wird die Datenquelle von [DataGridAchats] aus dem in der Sitzung gefundenen Warenkorb erstellt – Zeilen 20–30
  • Der Einkaufsbetrag wird mithilfe der Eigenschaft [totalPanier] der Klasse [Panier] angezeigt – Zeile 32

1.5.6.5. Die Ansicht [paniervide.aspx]

1.5.6.5.1. Introduction

Diese Ansicht zeigt die Information an, dass der Warenkorb leer ist:

Image

Sie wird nach einer Anfrage an /main?action=warenkorb oder /main?action=retirerachat&id=ID angezeigt. Die Elemente der Controller-Anfrage lauten wie folgt:

actions
Objekt „Hashtable()“ – die Tabelle mit den Menüoptionen
1.5.6.5.2. Die Komponenten der Seite
type
nom
rôle
1
Benutzerkomponente
Kopfzeile
Kopfzeile anzeigen
1.5.6.5.3. Der Darstellungscode [paniervide.aspx]
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx"%>
<%@ Page codebehind="paniervide.aspx.vb" inherits="istia.st.articles.web.PaniervideWebarticles" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h2>Contenu de votre panier</h2>
        <P>Votre panier est vide</P>
    </body>
</HTML>

Anmerkungen:

  • Die Kopfzeile ist in Zeile 9 enthalten
1.5.6.5.4. Der Prüfcode [paniervide.aspx.vb]
Namespace istia.st.articles.web
     ' verwaltet die Anzeige eines leeren Warenkorbs
    Public Class PaniervideWebarticles
        Inherits System.Web.UI.Page
        Protected WithEvents entete As New EnteteWebArticles

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' Die Ansicht [paniervide] wird anhand der Kontextinformationen vorbereitet
             ' Die Menüoptionen werden mit rptmenu verknüpft
            entete.actions = CType(context.Items("options"), Hashtable())
        End Sub
    End Class
End Namespace

Kommentare:

  • Hier wird lediglich die einzige dynamische Komponente der Seite initialisiert – Zeile 10

1.5.6.6. Die Ansicht [erreurs.aspx]

1.5.6.6.1. Introduction

Diese Ansicht wird bei Fehlern angezeigt:

Image

Sie wird nach jeder Anfrage angezeigt, die zu einem Fehler führt, mit Ausnahme der Kaufaktion mit einer falschen Menge, die hingegen von der Ansicht [INFOS] verarbeitet wird. Die Elemente der Anfrage des Controllers sind folgende:

actions
Objekt Hashtable() – die Tabelle der Menüoptionen
erreurs
ArrayList Objekte vom Typ [String], die die anzuzeigenden Fehlermeldungen darstellen
1.5.6.6.2. Die Komponenten der Seite
type
nom
rôle
1
Benutzerkomponente
Kopfzeile
Kopfzeile anzeigen
2
Repeater
rptErreurs
Fehlerliste anzeigen
1.5.6.6.3. Der Darstellungscode [erreurs.aspx]
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx" %>
<%@ Page codebehind="erreurs.aspx.vb" inherits="istia.st.articles.web.ErreursWebarticles" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h3>Les erreurs suivantes se sont produites :
        </h3>
        <ul>
            <asp:Repeater id="rptErreurs" runat="server">
                <ItemTemplate>
                    <li>
                        <%# Container.DataItem %>
                    </li>
                </ItemTemplate>
            </asp:Repeater></ul>
    </body>
</HTML>

Anmerkungen:

  • Der Header ist in Zeile 9 definiert
  • Die Komponente [rptErreurs] ist in den Zeilen 13–19 definiert. Ihr Inhalt stammt aus einer Datenquelle vom Typ [ArrayList] mit Objekten vom Typ [String].
1.5.6.6.4. Der Prüfcode [erreurs.aspx.vb]
Namespace istia.st.articles.web

     ' verwaltet die Fehlerseite
    Public Class ErreursWebarticles
        Inherits System.Web.UI.Page

         ' Seitenkomponenten
        Protected WithEvents rptErreurs As System.Web.UI.WebControls.Repeater
        Protected WithEvents entete As New EnteteWebArticles

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' Die Ansicht [erreurs] wird anhand der Kontextinformationen vorbereitet
             ' Die Menüoptionen werden mit rptmenu verknüpft
            entete.actions = CType(context.Items("options"), Hashtable())
             ' Die Fehler werden mit rptErreurs verknüpft
            With rptErreurs
                .DataSource = context.Items("erreurs")
                .DataBind()
            End With
        End Sub
    End Class

End Namespace

Anmerkungen:

  • Die Komponente [entete] wird wie üblich initialisiert (Zeilen 9 und 14)
  • Die Komponente [rptErreurs] wird mit der im Kontext gefundenen Fehlerliste vom Typ [ArrayList] initialisiert – Zeilen 16–19

1.5.7. Die Controller global.asax, main.aspx

Nun muss noch das Herzstück unserer Webanwendung geschrieben werden: der Controller. Seine Aufgabe besteht darin:

  • die Anfrage des Clients entgegenzunehmen,
  • die vom Kunden angeforderte Aktion mithilfe der Geschäftsklassen zu verarbeiten,
  • als Antwort die entsprechende Ansicht zurückzusenden.

1.5.7.1. Der Controller [global.asax.vb]

Wenn die Anwendung ihre allererste Anfrage erhält, wird die Prozedur [Application_Start] aus der Datei [global.asax.vb] ausgeführt. Dies geschieht nur ein einziges Mal. Die Prozedur [Application_Start] dient dazu, die für die Webanwendung erforderlichen Objekte zu initialisieren, die von allen Client-Threads schreibgeschützt gemeinsam genutzt werden. Diese gemeinsam genutzten Objekte können an zwei Stellen abgelegt werden:

  • in den privaten Feldern des Controllers
  • im Ausführungskontext der Anwendung (Application)

Die Methode [Application_Start] der Anwendung [global.asax.vb] führt folgende Aktionen durch:

  • Sie prüft, ob in der Datei [web.config] die für den ordnungsgemäßen Betrieb der Anwendung erforderlichen Parameter vorhanden sind. Diese wurden in Abschnitt 1.5.3 beschrieben.
  • überträgt die Liste eventueller Fehler in Form eines Objekts vom Typ [ArrayList erreurs] in den Anwendungskontext. Diese Liste ist leer, wenn keine Fehler vorliegen, existiert aber dennoch.
  • Wenn Fehler aufgetreten sind, endet die Methode [Application_Start] an dieser Stelle. Andernfalls fordert sie die Referenz eines Singletons vom Typ [IArticlesDomain] an, bei dem es sich um das Geschäftsobjekt handelt, das der Controller für seine Zwecke verwenden wird. Wie in 1.5.3.2 erläutert, fordert der Controller dieses Singleton vom Spring-Framework an. Bei dieser Instanziierung können verschiedene Fehler auftreten. Ist dies der Fall, werden diese wiederum im Objekt [erreurs] des Anwendungskontexts gespeichert.

Der Controller [global.asax.vb] verfügt über eine Prozedur [Session_Start], die bei jedem neuen Kunden aufgerufen wird. In dieser Prozedur wird ein leerer Warenkorb für den Kunden angelegt. Dieser Warenkorb wird über alle Anfragen dieses bestimmten Kunden hinweg beibehalten. Der Code könnte wie folgt aussehen:

Imports System
Imports System.Web
Imports System.Web.SessionState
Imports System.Configuration
Imports istia.st.articles.domain
Imports System.Collections
Imports Spring.Context

Namespace istia.st.articles.web

    Public Class GlobalWebArticles
        Inherits System.Web.HttpApplication

         ' Anwendung initialisieren
        Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)

             ' lokale Daten
            Dim parameters() As String = {"urlMain", "urlErreurs", "urlInfos", "urlListe", "urlPanier", "urlPanierVide"}
            Dim erreurs As New ArrayList

             ' Die Initialisierungsparameter der Anwendung werden abgerufen
            Dim param As String
            For i As Integer = 0 To parameters.Length - 1
                 ' Aus der Konfigurationsdatei lesen
                param = ConfigurationSettings.AppSettings(parameters(i))
                If param Is Nothing Then
                     ' Der Fehler wird protokolliert
                    erreurs.Add("Paramètre [" + parameters(i) + "] absent dans le fichier [web.config]")
                Else
                     ' Der Parameter wird in der Anwendung gespeichert
                    Application.Item(parameters(i)) = param
                End If
            Next
             ' Gibt es Fehler?
            If erreurs.Count = 0 Then
                 ' Ein Objekt IArticlesDomain für den Zugriff auf die Geschäftsebene wird erstellt
                Dim contexte As IApplicationContext = CType(ConfigurationSettings.GetConfig("spring/context"), IApplicationContext)
                Dim articlesDomain As IArticlesDomain
                Try
                    articlesDomain = CType(contexte.GetObject("articlesDomain"), IArticlesDomain)
                     ' Das Objekt wird in der Anwendung gespeichert
                    Application.Item("articlesDomain") = articlesDomain
                Catch ex As Exception
                     ' Der Fehler wird gespeichert
                    erreurs.Add("Erreur lors de la construction de l'objet d'accès à la couche métier [" + ex.ToString + "]")
                End Try
            End If
             ' Die Fehler werden in die Anwendung übernommen
            Application.Item("erreurs") = erreurs
             ' Wenn Fehler aufgetreten sind, ist der Vorgang beendet
            If erreurs.Count <> 0 Then Return
             ' Es wird ein Array mit Menüoptionen erstellt
            Dim options As New Hashtable
             ' Das URL wird vom Controller abgerufen
            Dim urlMain As String = CType(Application.Item("urlMain"), String)
            Dim uneOption As Hashtable
             ' Liste der Artikel
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=liste")
            uneOption.Add("lien", "Liste des articles")
            options.Add("liste", uneOption)
             ' Warenkorb
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=panier")
            uneOption.Add("lien", "Voir le panier")
            options.Add("panier", uneOption)
             ' Warenkorb bestätigen
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=validationpanier")
            uneOption.Add("lien", "Valider le panier")
            options.Add("validationpanier", uneOption)
             ' Die Menüoptionen werden in die Anwendung übernommen
            Application.Item("options") = options
            Return
        End Sub

         ' Sitzung initialisieren
        Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
             ' Warenkorb für den Kunden anlegen
            Session.Item("panier") = New Panier
        End Sub
    End Class
End Namespace

Anmerkungen:

  • Die in [web.config] erwarteten Parameter sind in einem Array definiert – Zeile 18
  • Sie werden in [web.config] gesucht. Sind sie vorhanden, werden sie im Anwendungskontext gespeichert, andernfalls wird ein Fehler in der Fehlerliste [erreurs] – Zeilen 21–33 – protokolliert
  • Wenn keine Fehler vorliegen, wird von Spring eine Referenz auf das Singleton [articlesDomain] angefordert, das den Zugriff auf die Anwendungsschicht [domain] verwaltet – Zeilen 35–47. Eventuelle Fehler werden in [erreurs] protokolliert.
  • Die Fehler werden im Anwendungskontext gespeichert – Zeile 49
  • Die Prozedur wird bei Fehlern beendet – Zeile 51
  • Es wird ein Array aus drei Dictionaries erstellt. Jedes davon hat zwei Schlüssel: href und link. Dieses Array repräsentiert die drei möglichen Menüoptionen – Zeilen 52–71
  • Dieses Array wird im Anwendungskontext gespeichert – Zeile 73
  • Bei jedem neuen Kunden wird die Prozedur [Session_Start] ausgeführt. Dort wird in der Sitzung des Kunden ein leerer Warenkorb angelegt – Zeilen 78–81

1.5.7.2. Der Controller [main.aspx.vb]

Der Controller [main.aspx.vb] empfängt alle Anfragen der Kunden. Diese haben nämlich alle die Form [/webarticles/main.aspx?action=XX]. Eine Anfrage wird wie folgt verarbeitet:

  • Das Objekt [erreurs] aus dem Anwendungskontext wird überprüft. Ist es nicht leer, bedeutet dies, dass bei der Initialisierung der Anwendung Fehler aufgetreten sind und diese nicht funktionieren kann. Als Antwort wird dann die Ansicht [ERREURS] gesendet.
  • Der Parameter [action] der Anfrage wird abgerufen und überprüft. Entspricht er keiner bekannten Aktion, wird die Ansicht [ERREURS] mit einer entsprechenden Fehlermeldung gesendet.
  • Ist der Parameter [action] gültig, wird die Anfrage des Clients zur Bearbeitung an eine aktionsspezifische Prozedur weitergeleitet:
Methode
Anfrage
Verarbeitung
Mögliche Antworten
doListe
GET /main?action=liste
- Liste der Artikel
bei der Fachklasse
- anzeigen
[LISTE] oder [ERREURS]
doInfos
GET /main?action=infos&id=ID
- den Artikel mit der ID = ID bei
der Fachklasse
- anzeigen
[INFOS] oder [ERREURS]
doAchat
POST /main?action=Kauf&id=ID
- Die gekaufte Menge ist Teil der übermittelten Parameter
- den Artikel mit der ID=ID bei
der Fachklasse abfragen
- in den Warenkorb aufnehmen, und zwar
der Kundensitzung
[LISTE] oder [INFOS] oder [ERREURS]
doRetirerAchat
GET /main?action=retirerachat&id=ID
- Artikel mit der ID=ID aus der
Einkaufsliste des Warenkorbs
der Kundensitzung
[PANIER]
doPanier
GET /main?action=warenkorb
- Warenkorb der
Kundensitzung
[PANIER] oder [PANIERVIDE]
doValidationPanier
GET /main?action=warenkorbbestätigung
- die Lagerbestände aller Artikel in der Datenbank verringern
Bestände aller Artikel
, die sich im Warenkorb der
Kunden-Sitzung
[LISTE] oder [ERREURS]

Das Grundgerüst des Controllers [main.aspx.vb] könnte wie folgt aussehen:

Imports System.Collections
Imports System
Imports System.Data

Imports istia.st.articles.dao
Imports istia.st.articles.domain

Namespace istia.st.articles.web

     ' Controller-Klasse der Webanwendung
    Public Class MainWebArticles
        Inherits System.Web.UI.Page

         ' Private Felder
        Private articlesDomain As IArticlesDomain
        Private options As Hashtable

         ' Seite wird geladen
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
....
        End Sub

         ' Methoden zur Verarbeitung von Aktionen

         ' Artikelübersicht
        Public Sub doListe()
...
        End Sub

         ' Informationen zu einem Artikel
        Public Sub doInfos()
...
        End Sub

         ' Kauf eines Artikels
        Public Sub doAchat()
...
        End Sub

         ' Kauf stornieren
        Public Sub doRetirerAchat()
...
        End Sub

         ' Warenkorb anzeigen
        Public Sub doPanier()
...
        End Sub

         ' Warenkorb kaufen
        Public Sub doValidationPanier()
...
        End Sub

    End Class

End Namespace

Anmerkungen:

  • Die Klasse verfügt über zwei private Felder, die von den Methoden gemeinsam genutzt werden – Zeilen 15–16:
    • articlesDomain: das Singleton für den Zugriff auf die Schicht [domain]
    • Optionen: das Array der Dictionaries für die Menüoptionen
  • Die Prozedur [Page_Load]:
    • initialisiert die beiden privaten Felder der Klasse
    • ruft den Parameter [action] aus der Anfrage ab und lässt die Verarbeitungsmethode für diese Aktion ausführen.

1.5.7.3. Die Methode [Page_Load]

Dieses Ereignis tritt als erstes auf der Seite auf. Der Code lautet wie folgt:

         ' Seite wird geladen
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' Es wird überprüft, ob die Anwendung korrekt gestartet wurde
            Dim erreurs As ArrayList = CType(Application.Item("erreurs"), ArrayList)
             ' Bei Fehlern wird die Ansicht [erreurs] angezeigt
            If erreurs.Count <> 0 Then
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {}
                Server.Transfer(CType(Application("urlErreurs"), String))
            End If
             ' Das Zugriffsobjekt auf die Geschäftsklasse wird abgerufen
            articlesDomain = CType(Application.Item("articlesDomain"), IArticlesDomain)
             ' sowie die Menüoptionen
            options = CType(Application.Item("options"), Hashtable)
             ' Die auszuführende Aktion wird abgerufen
            Dim action As String = Request.QueryString("action")
            If action Is Nothing Then
                action = "liste"
            End If
             ' Die Aktion wird ausgeführt
            Select Case action
                Case "liste"
                    doListe()
                Case "infos"
                    doInfos()
                Case "achat"
                    doAchat()
                Case "panier"
                    doPanier()
                Case "retirerachat"
                    doRetirerAchat()
                Case "validationpanier"
                    doValidationPanier()
                Case Else
                    doListe()
            End Select
        End Sub

Anmerkungen:

  • Bei jedem Laden der Seite wird sichergestellt, dass die von [global.asax] durchgeführte Initialisierung der Anwendung erfolgreich verlaufen ist.
  • Dazu wird aus dem Anwendungskontext die von [global.asax] – Zeile 4 – dort hinterlegte Fehlerliste abgerufen.
  • Ist diese Liste nicht leer, wird die Ansicht [ERREURS] angezeigt – Zeilen 6–10
  • Man ruft das von [global.asax] im Anwendungskontext abgelegte Singleton [articlesDomain] ab und speichert es im privaten Feld [articlesDomain], damit es den verschiedenen Methoden der Klasse zur Verfügung steht – Zeile 12
  • Ähnliches wird mit dem Array der Menüoptionen durchgeführt – Zeile 14
  • Der Parameter [action] wird aus der Anfrage abgerufen – Zeile 16
  • Die der angeforderten Aktion entsprechende Methode wird ausgeführt. Eine nicht vorgesehene Aktion wird als Aktion [liste] betrachtet – Zeilen 16–36

1.5.7.4. Verarbeitung der Aktion [liste]

Hierbei geht es um die Anzeige der Artikelliste:

Image

Der Code lautet wie folgt:

         ' Liste der Artikel
        Public Sub doListe()
             ' Fehlerbehandlung
            Dim erreurs As New ArrayList
             ' die Liste der Artikel wird angefordert
            Dim articles As IList
            Try
                articles = articlesDomain.getAllArticles
            Catch ex As Exception
                 ' Der Fehler wird vermerkt
                erreurs.Add(ex.ToString)
            End Try
             ' Gibt es Fehler?
            If erreurs.Count <> 0 Then
                 ' Ansicht anzeigen [erreurs]
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
            End If
             ' Ansicht anzeigen [liste]
            context.Items("articles") = articles
            context.Items("options") = New Hashtable() {CType(options("panier"), Hashtable)}
            If context.Items("message") Is Nothing Then context.Items("message") = ""
            Server.Transfer(CType(Application.Item("urlListe"), String))
        End Sub

Anmerkungen:

  • Eventuelle Fehler werden in ein [ArrayList] geschrieben – Zeile 4
  • Die Artikelliste wird vom Singleton [articlesDomain] angefordert – Zeilen 5–12
  • Wenn Fehler aufgetreten sind, wird die Ansicht [ERREURS] gesendet – Zeilen 13–19
  • ansonsten wird die Ansicht [LISTE] gesendet – Zeilen 20–24

1.5.7.5. Verarbeitung der Aktion [infos]

Der Kunde hat Informationen zu einem bestimmten Artikel angefordert:

Image

Der Code lautet wie folgt:

         ' Informationen zu einem Artikel
        Public Sub doInfos()
             ' Fehlerbehandlung
            Dim erreurs As New ArrayList
             ' Die ID des angeforderten Artikels wird abgerufen
            Dim strId As String = Request.QueryString("id")
             ' Gibt es etwas?
            If strId Is Nothing Then
                 ' Nicht normal – die Fehlerseite wird angezeigt
                erreurs.Add("action incorrecte (action=infos, id=rien)")
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
                Exit Sub
            End If
             ' Gibt es eine ganze Zahl?
            Dim id As Integer
            Try
                id = Integer.Parse(strId)
            Catch ex As Exception
                 ' Nicht normal – die Fehlerseite wird gesendet
                erreurs.Add("action incorrecte (action=infos, id[" + strId + "] invalide)")
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
                Exit Sub
            End Try
             ' Der Artikel mit der Schlüssel-ID wird angefordert
            Dim unArticle As Article
            Try
                unArticle = articlesDomain.getArticleById(id)
            Catch ex As Exception
                 ' Problem beim Zugriff auf die Daten
                erreurs.Add("Problème d'accès aux données (" + ex.ToString + ")")
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
                Exit Sub
            End Try
             ' Wurde ein Artikel abgerufen?
            If unArticle Is Nothing Then
                 ' Der Artikel existiert nicht
                erreurs.Add("L'article d'id=" + id.ToString + " n'existe pas")
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
                Exit Sub
            End If
             ' Der Artikel ist vorhanden – er wird in die aktuelle Sitzung aufgenommen
            Session.Item("article") = unArticle
             ' Anzeige wird vorbereitet
            context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
            context.Items("msg") = ""
            context.Items("qte") = ""
            Server.Transfer(CType(Application.Item("urlInfos"), String))
        End Sub

Anmerkungen:

  • Eventuelle Fehler werden in ein [ArrayList] – Zeile 4 – geschrieben
  • Die ID des angeforderten Artikels wird aus der Anfrage abgerufen – Zeile 6
  • Diese ID wird überprüft. Sie muss vorhanden sein und es muss sich um eine Ganzzahl handeln. Ist dies nicht der Fall, wird die Ansicht [ERREURS] mit der entsprechenden Fehlermeldung gesendet – Zeilen 7–27
  • Sobald die ID überprüft wurde, wird der Artikel beim Singleton [articlesDomain] angefordert. Tritt dabei eine Ausnahme auf, wird die Ansicht [ERREURS] gesendet – Zeilen 29–39
  • Wurde der Artikel nicht gefunden, wird die Ansicht [ERREURS] gesendet – Zeilen 41–48
  • Wenn der Artikel gefunden wurde, wird er in die Sitzung des Benutzers aufgenommen und anschließend in der Ansicht [INFOS] angezeigt – Zeilen 50–56

1.5.7.6. Verarbeitung der Aktion [achat]

Der Kunde hat den in der Ansicht [INFOS] angezeigten Artikel gekauft.

Image

Der Code lautet wie folgt:

         ' Kauf eines Artikels
        Public Sub doAchat()
             ' Kauf eines Artikels
             ' Der Artikel aus der Sitzung wird abgerufen
            Dim unArticle As Article = CType(Session.Item("article"), Article)
             ' Gibt es etwas?
            If unArticle Is Nothing Then
                 ' Nicht normal – die Artikelliste wird angezeigt
                doListe()
            End If
             ' Die gebuchte Menge wird abgerufen
            Dim strQte As String = Request.Form("txtQte")
             ' Gibt es etwas?
            If strQte Is Nothing Then
                 ' Nicht normal – die Artikelliste wird gesendet
                doListe()
            End If
             ' Liegt eine ganze Zahl vor?
            Dim qte As Integer
            Try
                qte = Integer.Parse(strQte)
                If (qte <= 0) Then Throw New Exception
            Catch ex As Exception
                 ' Nicht normal – die Informationsseite mit der Fehlermeldung wird gesendet
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                context.Items("msg") = "Quantité incorrecte"
                context.Items("qte") = strQte
                Server.Transfer(CType(Application.Item("urlInfos"), String))
            End Try
             ' alles in Ordnung – der Artikel wird in den Warenkorb des Kunden gelegt
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
            unPanier.ajouter(New Achat(unArticle, qte))
             ' Die Artikelliste wird angezeigt
            doListe()
        End Sub

Anmerkungen:

  • Der in die Sitzung geladene Artikel wird abgerufen – Zeile 5
  • Wenn er nicht vorhanden ist (die Sitzung ist möglicherweise abgelaufen), wird die Ansicht [LISTE] angezeigt – Zeilen 7–10
  • Die gekaufte Menge wird aus der Abfrage abgerufen – Zeile 12
  • Die Gültigkeit wird überprüft – Zeilen 13–29
  • Im Falle einer Ungültigkeit wird je nach Situation entweder die Ansicht [LISTE] – Zeile 16 oder die Ansicht [INFOS] – Zeilen 24–28 – gesendet
  • Wenn alles in Ordnung ist, wird der Kauf im Warenkorb gespeichert – Zeilen 31–32
  • Anschließend wird die Ansicht [LISTE] gesendet – Zeile 34

1.5.7.7. Verarbeitung der Aktion [panier]

Der Kunde hat verschiedene Artikel gekauft und möchte den Warenkorb einsehen:

Image

Der Code lautet wie folgt:

         ' Warenkorb anzeigen
        Public Sub doPanier()
             'Der Warenkorb wird aus der Sitzung abgerufen
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' Warenkorb leer?
            If unPanier.achats.Count = 0 Then
                 ' Anzeige des leeren Warenkorbs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlPanierVide"), String))
            End If
             ' Anzeige eines nicht leeren Warenkorbs
            context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable), CType(options("validationpanier"), Hashtable)}
            Server.Transfer(CType(Application.Item("urlPanier"), String))
        End Sub

Anmerkungen:

  • Der Warenkorb wird aus der Sitzung abgerufen – Zeile 4. Hier wird nicht überprüft, ob tatsächlich etwas abgerufen wird. Dies wäre jedoch erforderlich, da die Sitzung möglicherweise abgelaufen ist.
  • Wenn der Warenkorb leer ist, wird die Ansicht [PANIERVIDE] gesendet – Zeilen 6–10
  • Andernfalls wird die Ansicht [PANIER] gesendet – Zeilen 11–14

1.5.7.8. Verarbeitung der Aktion [retirerachat]

Der Kunde möchte einen Artikel aus seinem Warenkorb entfernen:

Image

Der Code lautet wie folgt:

         ' Löschen eines Artikels
        Public Sub doRetirerAchat()
             'Abruf des Warenkorbs aus der Sitzung
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' Artikel wird entfernt
            Try
                 ' ID des entfernten Artikels abrufen
                Dim idArticle As Integer = Integer.Parse(Request.QueryString("id"))
                 ' Entfernen aus dem Warenkorb
                unPanier.enlever(idArticle)
            Catch ex As Exception
                 ' Die Artikelliste wird angezeigt
                doListe()
            End Try
             ' Der Warenkorb wird angezeigt
            doPanier()
        End Sub

Anmerkungen:

  • Der Warenkorb wird aus der Sitzung abgerufen – Zeile 4. Hier wird nicht überprüft, ob tatsächlich etwas abgerufen wurde. Dies wäre jedoch erforderlich, da die Sitzung möglicherweise abgelaufen ist.
  • In der Abfrage wird die ID [id] des zu entfernenden Artikels abgerufen – Zeile 8.
  • Der entsprechende Kauf wird aus dem Warenkorb entfernt – Zeile 10
  • Die Gültigkeit der ID des gekauften Artikels wurde hier nicht überprüft. Wenn diese einen ungültigen Typ hat, wird eine Ausnahme ausgelöst und in den Zeilen 11–13 behandelt. Wenn sie gültig ist, aber nicht existiert, führt die Methode [panier.enlever] – Zeile 10 – keine Aktion aus.
  • Der neue Warenkorb wird angezeigt – Zeile 16

1.5.7.9. Verarbeitung der Aktion [validerpanier]

Der Kunde möchte seinen Warenkorb bestätigen:

Image

Der Code lautet wie folgt:

         ' Warenkorb kaufen
        Public Sub doValidationPanier()
             ' Zunächst keine Fehler
            Dim erreurs As ArrayList
             'Der Warenkorb wird aus der Sitzung abgerufen
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' Versuch, den Warenkorb zu bestätigen
            Try
                articlesDomain.acheter(unPanier)
                 ' Eventuelle Fehler beim Kauf werden notiert
                erreurs = articlesDomain.erreurs
            Catch ex As Exception
                 ' Der Fehler wird vermerkt
                erreurs = New ArrayList
                erreurs.Add(String.Format("Erreur lors de la validation du panier [{0}]", ex.Message))
            End Try
             ' Bei Fehlern wird die Fehlerseite angezeigt
            If erreurs.Count <> 0 Then
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable), CType(options("panier"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
                Exit Sub
            End If
             ' Alles in Ordnung – die Artikelliste wird mit einer Erfolgsmeldung angezeigt
            context.Items("message") = "Votre panier a été validé"
            doListe()
        End Sub

Anmerkungen:

  • Der Warenkorb wird aus der Sitzung abgerufen – Zeile 6. Hier wird nicht überprüft, ob tatsächlich etwas abgerufen wurde. Dies wäre jedoch erforderlich, da die Sitzung möglicherweise abgelaufen ist.
  • Falls die Sitzung abgelaufen ist, haben wir einen Zeiger [nothing] für den Warenkorb, und die Methode [acheter] – Zeile 9 – löst eine Ausnahme aus, woraufhin die Ansicht [ERREURS] gesendet wird. Allerdings ist die Fehlermeldung für den Benutzer wenig aussagekräftig.
  • In den Zeilen 8–16 wird versucht, den aus der Sitzung abgerufenen Warenkorb zu validieren. Bestimmte Käufe können möglicherweise nicht bestätigt werden, wenn die angeforderte Menge den Lagerbestand des gewünschten Artikels übersteigt. Diese Fälle werden von der Methode [acheter] in einer Fehlerliste gespeichert, die in Zeile 11 abgerufen wird.
  • Liegen Fehler vor, wird die Ansicht [ERREURS] gesendet – Zeilen 18–23
  • Andernfalls wird die Ansicht [LISTE] gesendet – Zeilen 25–26

1.6. Conclusion

Wir haben hier eine Anwendung nach dem Muster MVC entwickelt. Es scheint (Stand: April 2005) keine professionellen Entwicklungs-„Frameworks“ für MVC in ASP.NET zu geben, wie es sie in Java gibt (Struts, Spring, ...). Das Projekt [Spring.net] dürfte in Kürze ein solches anbieten. Bis dahin ermöglicht die oben beschriebene Methode eine praktikable MVC-Entwicklung für mittelgroße Anwendungen.