Skip to content

1. Teil 1

Das PDF des Dokuments ist |HIER| verfügbar.

Die Beispiele im Dokument sind |HIER| verfügbar.

1.1. Einleitung

Ziele dieses Artikels:

  • Eine dreischichtige Webanwendung schreiben [Benutzeroberfläche, Geschäftslogik, Datenzugriff]
  • Konfigurieren der Anwendung mit Spring IOC
  • Erstellen verschiedener Versionen durch Änderung der Implementierung einer oder mehrerer der drei Schichten.

Verwendete Tools:

  • Visual Studio.NET für die Entwicklung – siehe Anhang, Abschnitt 3.1;
  • Cassini-Webserver zur 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 Webanwendungsschichten – siehe Anhang, Abschnitt 3.3;

Auf einer Skala von Anfänger über Fortgeschrittene bis hin zu Experten fällt dieses Dokument in die Kategorie [Fortgeschrittene bis Experten]. Um es zu verstehen, sind verschiedene Voraussetzungen erforderlich. Einige davon finden sich in Dokumenten, die ich verfasst habe. In solchen Fällen verweise ich darauf. Es versteht sich von selbst, dass dies lediglich ein Vorschlag ist und es dem Leser freisteht, seine bevorzugten Ressourcen zu nutzen.

Dieses Dokument folgt derselben Struktur wie ein für Java verfasstes Dokument [3-Schichten-Architekturen und MVC-Architekturen mit Struts, Spring und Java]. Wir erstellen die in Java geschriebene dreischichtige MVC-Webanwendung mit VB.NET. Damit möchten wir verdeutlichen, dass die Entwicklungsplattformen Java und .NET so ähnlich sind, dass in einem dieser beiden Bereiche erworbene Kenntnisse im anderen wiederverwendet werden können.

Es scheint keine allgemein anerkannte ASP.NET-MVC-Entwicklungslösung zu geben. Die folgende Lösung greift die im Dokument [Web Development with ASP.NET 1.1] vorgestellte Methode auf. Diese Methode hat zwar den Vorteil, dass sie in der J2EE-Entwicklung gängige Konzepte nutzt, sollte aber dennoch als das betrachtet werden, was sie ist: eine von vielen MVC-Entwicklungsmethoden. Sobald sich eine MVC-Entwicklungsmethode in ASP.NET allgemein durchgesetzt hat, sollte sie übernommen werden. Die derzeit in Entwicklung befindliche .NET-Version von Spring könnte durchaus eine erste Lösung sein.

1.2. Die Webarticles-Anwendung

Hier stellen wir die Komponenten einer vereinfachten E-Commerce-Webanwendung vor. Diese Anwendung ermöglicht es Webnutzern:

  • eine Liste von Artikeln aus einer Datenbank anzeigen
  • einige davon in einen Online-Warenkorb legen
  • den Warenkorb zu bestätigen. Diese Bestätigung aktualisiert lediglich den Bestand der gekauften Artikel in der Datenbank.

Dem Benutzer werden folgende Ansichten angezeigt:

- die Ansicht „LIST“, die eine Liste der zum Verkauf stehenden Artikel anzeigt
- die Ansicht [INFO], die zusätzliche Informationen zu einem Produkt bereitstellt:
  • die Ansichten [WARENKORB] und [LEERER WARENKORB], die den Inhalt des Warenkorbs des Kunden anzeigen
  • die Ansicht [ERRORS], die etwaige Anwendungsfehler meldet

Image

1.3. Allgemeine Anwendungsarchitektur

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 wird von Spring übernommen
  • Jede Schicht hat ihren eigenen Namensraum: web (UI-Schicht), domain (Geschäftsschicht) und dao (Datenzugriffsschicht).

Die Anwendung folgt einer MVC-Architektur (Model-View-Controller). Wenn wir uns das obige Schichtdiagramm noch einmal ansehen, fügt sich die MVC-Architektur wie folgt ein:

Die Bearbeitung einer Client-Anfrage erfolgt in folgenden Schritten:

  1. Der Client sendet eine Anfrage an den Controller. In diesem Fall ist der Controller eine .aspx-Seite, die eine bestimmte Rolle spielt. Sie verarbeitet alle Client-Anfragen. Sie ist der Einstiegspunkt der Anwendung. Sie ist das C in MVC.
  1. Der Controller verarbeitet diese Anfrage. Dazu benötigt er möglicherweise Unterstützung durch die Geschäftslogik, die als „M“ in der MVC-Struktur bezeichnet wird.
  2. Der Controller erhält eine Antwort von der Geschäftsschicht. Die Anfrage des Clients wurde verarbeitet. Dies kann verschiedene mögliche Antworten auslösen. Ein klassisches Beispiel ist
    • eine Fehlerseite, wenn 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. Meistens handelt es sich dabei 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

Hier betrachten wir das M in MVC. Das Modell besteht aus den folgenden Elementen:

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

1.4.1. Die Datenbank

Die Datenbank enthält nur eine Tabelle namens ARTICLES. Diese Tabelle wurde mit den folgenden SQL-Befehlen 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
);
/* constraints */
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);
/* primary key */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
id
Primärschlüssel, der einen Artikel eindeutig identifiziert
Name
Elementname
Preis
sein Preis
Aktueller Lagerbestand
aktueller Lagerbestand
Mindestbestand
der Lagerbestand, unterhalb dessen eine Nachbestellung aufgegeben werden muss

1.4.2. Die Namespaces des Modells

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

  • istia.st.articles.dao: enthält die Datenzugriffsklassen der [dao]-Schicht
  • istia.st.articles.domain: enthält die Business-Klassen der [domain]-Schicht

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

assembly
Inhalt
role
Webartikel-DAO
- [IArticlesDao]: die Schnittstelle für den Zugriff auf die [dao]-Schicht.
Dies ist die einzige Schnittstelle, die für die [domain]-Schicht sichtbar ist. Sie sieht keine anderen.
- [Article]: Klasse, die einen Artikel definiert
- [ArticlesDaoArrayList]: Implementierungsklasse der
der Schnittstelle [IArticlesDao] mit einer [ArrayList]
Datenzugriffsebene
- befindet sich vollständig innerhalb der
[DAO]-Schicht der 3-Tier-Architektur der
der Webanwendung
webarticles-domain
- [IArticlesDomain]: die Schnittstelle für den Zugriff auf die [domain]-Schicht. Dies ist die einzige Schnittstelle, die für die Webschicht sichtbar ist. Sie sieht keine anderen.
- [AchatsArticles]: eine Klasse, die [IArticlesDomain] implementiert
- [Purchase]: eine Klasse, die den Einkauf eines Kunden repräsentiert
- [Cart]: eine Klasse, die die Gesamteinkäufe eines Kunden repräsentiert
stellt das Modell der Web-Einkäufe dar
Web – befindet sich vollständig in der
[Domain]-Schicht der
3-Schichten-Architektur der Webanwendung

1.4.3. Die [DAO]-Schicht

Die [DAO]-Schicht enthält die folgenden Elemente:

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

  • [Article]: Klasse, die einen Artikel definiert

  • [ArticlesDaoArrayList]: Implementierungsklasse für die [IArticlesDao]-Schnittstelle unter Verwendung einer [ArrayList]-Klasse

Die [Visual Studio]-Projektstruktur für die [dao]-Schicht sieht wie folgt aus:

Image

Kommentare:

  • Das [dao]-Projekt ist vom Typ [Klassenbibliothek]
  • Die Klassen wurden in einer Baumstruktur ab dem Ordner [istia] angeordnet. Sie befinden sich alle im Namespace [istia.st.articles.dao].

1.4.3.1. Die Klasse [Article]

Die Klasse, die einen Artikel definiert, sieht wie folgt aus:

Imports System

Namespace istia.st.articles.dao

    Public Class Article

        ' private fields
        Private _id As Integer
        Private _nom As String
        Private _prix As Double
        Private _stockactuel As Integer
        Private _stockminimum As Integer

        ' id article
        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

        ' item name
        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

        ' item price
        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

        ' current stock item
        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

        ' minimum stock item
        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

        ' default builder
        Public Sub New()
        End Sub

        ' builder with properties
        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

        ' article identification method
        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 zum Festlegen der 5 Informationen für einen Artikel: [id, name, price, currentStock, minimumStock]
  2. öffentliche Eigenschaften zum Lesen und Schreiben der 5 Informationen
  3. eine Validierung der für den Artikel eingegebenen Daten. Sind die Daten ungültig, wird eine Ausnahme ausgelöst.
  4. Eine toString-Methode, die den Wert eines Elements als Zeichenfolge zurückgibt. Dies ist häufig nützlich für die Fehlersuche in einer Anwendung.

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
        ' list of all items
        Function getAllArticles() As IList
        ' add an article
        Function ajouteArticle(ByVal unArticle As Article) As Integer
        ' deletes an article
        Function supprimeArticle(ByVal idArticle As Integer) As Integer
        ' modify an article
        Function modifieArticle(ByVal unArticle As Article) As Integer
        ' search for an article
        Function getArticleById(ByVal idArticle As Integer) As Article
        ' deletes all articles
        Sub clearAllArticles()
        ' changes the stock of an item
        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 Elemente aus der Datenquelle zurück
clearAllArticles
löscht die Datenquelle
getArticleById
gibt das [Article]-Objekt zurück, das durch seinen Primärschlüssel identifiziert wird
addArticle
ermöglicht es Ihnen, einen Artikel zur Datenquelle hinzuzufügen
modifyArticle
ermöglicht es Ihnen, einen Artikel in der Datenquelle zu ändern
deleteItem
ermöglicht es Ihnen, einen Eintrag aus der Datenquelle zu löschen
updateItemStock
ermöglicht es Ihnen, den Lagerbestand eines Artikels in der Datenquelle zu ändern

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

Die Auswahl einer bestimmten Implementierung erfolgt über eine Spring-Konfigurationsdatei. Um zu veranschaulichen, dass für das Testen der Webanwendung nur die Datenzugriffsschnittstelle von Bedeutung ist – und nicht deren Implementierungsklasse –, werden wir die Datenquelle zunächst mithilfe eines einfachen [ArrayList]-Objekts implementieren. Später werden wir eine datenbankbasierte Lösung 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

        ' default builder
        Public Sub New()
            ' we build a few items
            For i As Integer = 1 To nbArticles
                articles.Add(New Article(i, "article" + i.ToString, i * 10, i * 10, i * 10))
            Next
        End Sub

        ' list of all items
        Public Function getAllArticles() As IList Implements IArticlesDao.getAllArticles
            ' returns the list of items
            SyncLock Me
                Return articles
            End SyncLock
        End Function

        ' delete all items
        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
            ' empty the list of items
            SyncLock Me
                articles.Clear()
            End SyncLock
        End Sub

        ' obtain an item identified by its key
        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
            ' search for the item in the list
            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

        ' add an item to the list of items
        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
            ' add the item to the list of items
            SyncLock Me
                ' we check that it doesn't already exist
                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
                ' we add the article
                articles.Add(unArticle)
                ' we return the result
                Return 1
            End SyncLock
        End Function

        ' modify an article
        Public Function modifieArticle(ByVal articleNouveau As Article) As Integer Implements IArticlesDao.modifieArticle
            ' modify an article
            SyncLock Me
                ' we check that
                Dim ipos As Integer = posArticle(articles, articleNouveau.id)
                ' if it doesn't exist
                If ipos = -1 Then Return 0
                ' it exists - we modify it
                articles(ipos) = articleNouveau
                ' we return the result
                Return 1
            End SyncLock
        End Function

        ' delete an item identified by its key
        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
            ' article deletion
            SyncLock Me
                ' we check that
                Dim ipos As Integer = posArticle(articles, idArticle)
                ' if it doesn't exist
                If ipos = -1 Then Return 0
                ' it exists - we remove it
                articles.RemoveAt(ipos)
                ' we return the result
                Return 1
            End SyncLock
        End Function

        ' change the stock of an item identified by its key
        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
            ' change the stock of an item
            SyncLock Me
                ' we check that
                Dim ipos As Integer = posArticle(articles, idArticle)
                ' if it doesn't exist
                If ipos = -1 Then Return 0
                ' it exists - you modify your stock if you can
                Dim unArticle As Article = CType(articles(ipos), Article)
                ' only change stock if it is sufficient
                If unArticle.stockactuel + mouvement >= 0 Then
                    unArticle.stockactuel += mouvement
                    Return 1
                Else
                    Return 0
                End If
            End SyncLock
        End Function

        ' search for an item identified by its key
        Private Function posArticle(ByVal listArticles As ArrayList, ByVal idArticle As Integer) As Integer
            ' returns the position of item [idArticle] in the list or -1 if not found
            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
            ' not found
            Return -1
        End Function
    End Class

End Namespace

Anmerkungen:

  • Die Datenquelle wird durch das private Feld [articles] vom Typ [ArrayList] simuliert
  • Der Klassenkonstruktor erstellt standardmäßig 4 Elemente in der Datenquelle.
  • Alle Datenzugriffsmethoden wurden synchronisiert, um Probleme durch gleichzeitigen Zugriff auf die Datenquelle zu vermeiden. Zu jedem Zeitpunkt hat nur ein Thread Zugriff auf eine bestimmte Methode.
  • Die Methode [posArticle] gibt die Position [0..N] in der [ArrayList]-Datenquelle eines durch seine Nummer identifizierten Artikels zurück. Wenn der Artikel nicht existiert, gibt die Methode die Position -1 zurück. Diese Methode wird von den anderen Methoden wiederholt verwendet.
  • Die Methode [addArticle] fügt einen Artikel zur Liste der Artikel hinzu. Sie gibt die Anzahl der eingefügten Artikel zurück: 1. Wenn der Artikel bereits existierte, wird eine Ausnahme ausgelöst.
  • Die Methode [modifyItem] ermöglicht es Ihnen, ein vorhandenes Element zu ändern. Sie gibt die Anzahl der geänderten Elemente zurück: 1, wenn das Element vorhanden war, andernfalls 0.
  • Die Methode [deleteArticle] löscht einen vorhandenen Artikel. Sie gibt die Anzahl der gelöschten Artikel zurück: 1, wenn der Artikel vorhanden war, andernfalls 0.
  • Die Methode [getAllArticles] gibt eine Liste aller Artikel zurück
  • Die Methode [getArticleById] ruft einen Artikel anhand seiner ID ab. Sie gibt den Wert [nothing] zurück, 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. Erstellen der [dao]-Layer-Assembly

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 für die [dao]-Schicht

In Java werden Klassen mithilfe des [JUnit]-Frameworks getestet. In .NET bietet das NUnit-Framework die gleichen Unit-Test-Funktionen:

Image

Das Visual Studio-Testprojekt ist wie folgt aufgebaut:

Image

Anmerkungen:

  • Das [tests]-Projekt ist eine [Class Library]
  • [NUnit]-Tests erfordern einen Verweis auf die Assembly [nunit.framework.dll]
  • Die [NUnit]-Testklasse ruft über Spring eine Instanz des zu testenden Objekts ab. Daher
    • müssen im Ordner [bin] die Spring-Klassendateien
    • in [References] muss ein Verweis auf die [Spring-Core.dll]-Assembly aus dem [bin]-Ordner
    • in [bin] eine Konfigurationsdatei für Spring
  • Die Testklasse benötigt die Assembly [webarticles-dao.dll] aus der [dao]-Schicht. Diese wurde im Ordner [bin] abgelegt und ihr Verweis zu den Projektverweisen hinzugefügt.

Eine [NUnit]-Testklasse benötigt Zugriff auf Klassen im Namespace [NUnit.Framework]. Daher ist die folgende Import-Anweisung enthalten:

Imports NUnit.Framework

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

Die Assembly [nunit.framework.dll] sollte in der angezeigten Liste enthalten sein, wenn [NUnit] installiert wurde. Doppelklicken Sie einfach auf die Assembly, um sie dem Projekt hinzuzufügen:

Image

Die [NUnit]-Testklasse für die [DAO]-Schicht 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
        ' the test object
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
            ' retrieve an instance of the Spring object manufacturer
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
            ' request instantiation of articlesdao object
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
        End Sub

        <Test()> _
        Public Sub testGetAllArticles()
            ' visual check
            listArticles()
        End Sub

        <Test()> _
        Public Sub testClearAllArticles()
            ' delete all articles
            articlesDao.clearAllArticles()
            ' all articles are requested
            Dim articles As IList = articlesDao.getAllArticles
            ' verification: there must be 0
            Assert.AreEqual(0, articles.Count)
        End Sub

        <Test()> _
        Public Sub testAjouteArticle()
            ' delete all items
            articlesDao.clearAllArticles()
            ' check: the item table must be empty
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
            ' we add two items
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            ' check: there must be two items
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
            ' visual check
            listArticles()
        End Sub

        <Test()> _
        Public Sub testSupprimeArticle()
            ' delete all items
            articlesDao.clearAllArticles()
            ' check: the item table must be empty
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
            ' we add two items
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            ' check: there must be 2 items
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
            ' we delete article 4
            articlesDao.supprimeArticle(4)
            ' check: there must be 1 item left
            articles = articlesDao.getAllArticles
            Assert.AreEqual(1, articles.Count)
            ' visual check
            listArticles()
        End Sub

        <Test()> _
        Public Sub testModifieArticle()
            ' delete all items
            articlesDao.clearAllArticles()
            ' check
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
            ' 2 items added
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            ' check
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
            ' article 3 search
            Dim unArticle As Article = articlesDao.getArticleById(3)
            ' check
            Assert.AreEqual(unArticle.nom, "article3")
            ' research article 4
            unArticle = articlesDao.getArticleById(4)
            ' check
            Assert.AreEqual(unArticle.nom, "article4")
            ' modification article 4
            articlesDao.modifieArticle(New Article(4, "article4", 44, 44, 44))
            ' check
            unArticle = articlesDao.getArticleById(4)
            Assert.AreEqual(unArticle.prix, 44, 0.000001)
            ' visual check
            listArticles()
        End Sub

        <Test()> _
        Public Sub testGetArticleById()
            ' article deletion
            articlesDao.clearAllArticles()
            ' check
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
            ' 2 items added
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            ' check
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
            ' research article 3
            Dim unArticle As Article = articlesDao.getArticleById(3)
            ' check
            Assert.AreEqual(unArticle.nom, "article3")
            ' research article 4
            unArticle = articlesDao.getArticleById(4)
            ' check
            Assert.AreEqual(unArticle.nom, "article4")
        End Sub

        ' screen listing
        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()
            ' delete all items
            articlesDao.clearAllArticles()
            ' research article 1
            Dim article As article = articlesDao.getArticleById(1)
            ' check
            Assert.IsNull(article)
            ' modification of a non-existent item
            Dim i As Integer = articlesDao.modifieArticle(New article(1, "1", 1, 1, 1))
            ' had to modify no line
            Assert.AreEqual(i, 0)
            ' deletion of non-existent item
            i = articlesDao.supprimeArticle(1)
            ' had to delete no line
            Assert.AreEqual(0, i)
        End Sub

        <Test()> _
        Public Sub testChangerStockArticle()
            ' delete all items
            articlesDao.clearAllArticles()
            ' add an item
            Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
            Assert.AreEqual(nbArticles, 1)
            ' add an item
            nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            Assert.AreEqual(nbArticles, 1)
            ' creation of 100 threads
            Dim taches(99) As Thread
            For i As Integer = 0 To taches.Length - 1
                ' create thread i
                taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
                ' set the thread name
                taches(i).Name = "tache_" & i
                ' start execution of thread i
                taches(i).Start()
            Next
            ' wait for all threads to finish
            For i As Integer = 0 To taches.Length - 1
                taches(i).Join()
            Next
            ' checks - item 3 must have a stock of 1
            Dim unArticle As Article = articlesDao.getArticleById(3)
            Assert.AreEqual(unArticle.nom, "article3")
            Assert.AreEqual(1, unArticle.stockactuel)
            ' item 4 stock is decremented
            Dim erreur As Boolean = False
            Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
            ' check: its stock must not have changed
            Assert.AreEqual(0, nbLignes)
            ' visual check
            listArticles()
        End Sub

        Public Sub décrémente()
            ' thread launched
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
            ' thread decrements stock
            articlesDao.changerStockArticle(3, -1)
            ' thread terminated
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
        End Sub

    End Class
End Namespace

Kommentare:

  • Wir wollten ein Testprogramm für die Schnittstelle [IArticlesDao] schreiben, das unabhängig von ihrer Implementierungsklasse ist. Daher haben wir Spring verwendet, um den Namen der Implementierungsklasse vor dem Testprogramm zu verbergen.
  • Die Methode <Setup()> ruft aus Spring eine Referenz auf das zu testende [articlesdao]-Objekt ab. Dies ist in der folgenden [spring-config.xml]-Datei 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] für die Schnittstelle [IArticlesDao] an und wo diese zu finden ist [webarticles-dao.dll]. Da die Instanziierung keine Parameter erfordert, sind hier keine definiert.

  • Die meisten Tests sind leicht verständlich. Dem Leser wird empfohlen, die Kommentare zu lesen.
  • Die Methode [testChangerStockArticle] bedarf einer Erläuterung. Sie erstellt 100 Threads, die für die Verringerung des Lagerbestands eines bestimmten Artikels zuständig sind.
        <Test()> _
        Public Sub testChangerStockArticle()
            ' delete all items
            articlesDao.clearAllArticles()
            ' add an item
            Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
            Assert.AreEqual(nbArticles, 1)
            ' add an item
            nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            Assert.AreEqual(nbArticles, 1)
            ' creation of 100 threads
            Dim taches(99) As Thread
            For i As Integer = 0 To taches.Length - 1
                ' create thread i
                taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
                ' set the thread name
                taches(i).Name = "tache_" & i
                ' start execution of thread i
                taches(i).Start()
            Next
            ' wait for all threads to finish
            For i As Integer = 0 To taches.Length - 1
                taches(i).Join()
            Next
            ' checks - item 3 must have a stock of 1
            Dim unArticle As Article = articlesDao.getArticleById(3)
            Assert.AreEqual(unArticle.nom, "article3")
            Assert.AreEqual(1, unArticle.stockactuel)
            ' item 4 stock is decremented
            Dim erreur As Boolean = False
            Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
            ' check: its stock must not have changed
            Assert.AreEqual(0, nbLignes)
            ' visual check
            listArticles()
        End Sub

Dies dient dazu, den gleichzeitigen Zugriff auf die Datenquelle zu testen. Die Methode, die für die Aktualisierung des Lagerbestands zuständig ist, lautet wie folgt:

        Public Sub décrémente()
            ' thread launched
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
            ' thread decrements stock
            articlesDao.changerStockArticle(3, -1)
            ' thread terminated
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
        End Sub

Dadurch wird der Lagerbestand von Artikel Nr. 3 um eins verringert. Wenn wir uns den Code der Methode [testChangerStockArticle] ansehen, stellen wir fest, dass:

  • der Lagerbestand von Artikel Nr. 3 auf 101 initialisiert ist
  • die 100 Threads verringern diesen Bestand jeweils um eins
  • wir sollten daher am Ende der Ausführung aller Threads einen Bestand von 1 haben

Außerdem versuchen wir, ebenfalls innerhalb dieser Methode, den Bestand von Artikel Nr. 4 auf einen negativen Wert zu setzen. Dies sollte fehlschlagen.

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

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

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 bestanden (grün) oder nicht bestanden (rot) hat. Leser, die dieses Dokument am Bildschirm betrachten, werden sehen, dass alle Tests bestanden wurden. Wir betrachten die [dao]-Schicht daher als funktionsfähig.

1.4.4. Die [domain]-Schicht

Die [domain]-Schicht enthält die folgenden Elemente:

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

  • [Purchase]: Klasse, die einen Kauf definiert

  • [ShoppingCart]: Klasse, die einen Warenkorb definiert

  • [ProductPurchases]: die Klasse, die die Schnittstelle [IArticlesDomain] implementiert

Die Struktur der [Visual Studio]-Lösung für die [Domain]-Schicht ist wie folgt:

Image

Kommentare:

  • Das [Domain]-Projekt ist vom Typ [Klassenbibliothek]
  • Die Klassen wurden in einer Baumstruktur ab dem Ordner [istia] angeordnet. Sie befinden sich alle im Namespace [istia.st.articles.domain].
  • Die DLL der [dao]-Schicht wurde im Ordner [bin] des neuen Projekts abgelegt. Zusätzlich wurde diese DLL als Referenz zum Projekt hinzugefügt.

1.4.4.1. Die Schnittstelle [IArticlesDomain]

Die Schnittstelle [IArticlesDomain] entkoppelt die [Business]-Schicht von der [Web]-Schicht. Letztere greift über diese Schnittstelle auf die [Business/Domain]-Schicht 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 Business-Schicht:

Imports Article = istia.st.articles.dao.Article

Namespace istia.st.articles.domain
    Public Interface IArticlesDomain
        ' methods
        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
Funktion getAllArticles() As IList
gibt die Liste der [Article]-Objekte aus der zugehörigen Datenquelle zurück
Funktion getArticleById(ByVal idArticle As Integer) As Article
gibt das durch [idArticle] identifizierte [Article]-Objekt zurück
buy(ByVal cart As Cart)
bearbeitet den Warenkorb des Kunden, indem der Lagerbestand der gekauften Artikel um die gekaufte Menge verringert wird – kann fehlschlagen, wenn der Lagerbestand nicht ausreicht
ReadOnly Property errors() As ArrayList
gibt die Liste der aufgetretenen Fehler zurück – leer, wenn keine Fehler vorliegen

1.4.4.2. Die Klasse [Purchase]

Die Klasse [Purchase] repräsentiert einen Kundenkauf:

Imports istia.st.articles.dao

Namespace istia.st.articles.domain

    Public Class Achat

        ' private fields
        Private _article As article
        Private _qte As Integer

        ' default builder
        Public Sub New()
        End Sub

        ' builder with parameters
        Public Sub New(ByVal unArticle As article, ByVal qte As Integer)
            ' we go through the properties
            Me.article = unArticle
            Me.qte = qte
        End Sub

        ' item purchased
        Public Property article() As article
            Get
                Return _article
            End Get
            Set(ByVal Value As article)
                _article = Value
            End Set
        End Property

        ' qty purchased
        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

        ' total purchase
        Public ReadOnly Property totalAchat() As Double
            Get
                Return _qte * _article.prix
            End Get
        End Property

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

End Namespace

Kommentare:

  • Die Klasse [Purchase] verfügt über die folgenden Eigenschaften und Methoden:
Public Property item() As item
der gekaufte Artikel
Öffentliche Eigenschaft qty() As Integer
die gekaufte Menge
Öffentliche schreibgeschützte Eigenschaft purchaseTotal() As Double
der Kaufbetrag
Public Overrides Function ToString() As String
Zeichenkettendarstellung des Objekts
  • Sie verfügt über einen Konstruktor, der die Eigenschaften [item, qty] initialisiert, die einen Kauf definieren.

1.4.4.3. Die Klasse [Cart]

Die Klasse [Cart] repräsentiert die gesamten Einkäufe des Kunden:

Namespace istia.st.articles.domain

Public Class Panier

    ' private fields
    Private _achats As New ArrayList
        Private _totalPanier As Double = 0

    ' default builder
    Public Sub New()
    End Sub

    ' list of purchases
    Public ReadOnly Property achats() As ArrayList
        Get
            Return _achats
        End Get
    End Property

    ' total purchases
    Public ReadOnly Property totalPanier() As Double
        Get
            Return _totalPanier
        End Get
    End Property

    ' methods
    Public Sub ajouter(ByVal unAchat As Achat)
        ' find out if the purchase already exists
        Dim iAchat As Integer = posAchat(unAchat.article.id)
        If iAchat <> -1 Then
            ' we found
                Dim achatCourant As Achat = CType(_achats(iAchat), Achat)
            achatCourant.qte += unAchat.qte
        Else
            ' we didn't find
            _achats.Add(unAchat)
        End If
        ' increment the basket total
        _totalPanier += unAchat.totalAchat
    End Sub

    ' remove a purchase
    Public Sub enlever(ByVal idAchat As Integer)
        ' we're looking to buy
            Dim iachat As Integer = posAchat(idAchat)
        ' if found, remove
        If iachat <> -1 Then
                Dim achatCourant As Achat = CType(_achats(iachat), Achat)
            ' remove from basket
            _achats.RemoveAt(iachat)
            ' decrement the basket total
            _totalPanier -= achatCourant.totalAchat
        End If
    End Sub

    Private Function posAchat(ByVal idArticle As Integer) As Integer
        ' search for a purchase in the purchase list
        ' returns its position in the list or -1 if not found
        Dim achatCourant As Achat
        Dim trouvé As Boolean = False
        Dim i As Integer = 0
            While Not trouvé AndAlso i < _achats.Count
                ' regular purchase
                achatCourant = CType(_achats(i), Achat)
                ' comparison with article searched
                If achatCourant.article.id = idArticle Then
                    Return i
                End If
                'next purchase
                i += 1
            End While
            ' not found
            Return -1
    End Function

    ' identity function
    Public Overrides Function ToString() As String
        Return _achats.ToString
    End Function
End Class

End Namespace

Kommentare:

  • Die Klasse [ShoppingCart] verfügt über die folgenden Eigenschaften und Methoden:
ReadOnly-Eigenschaft purchases() als ArrayList
die Einkaufsliste des Kunden – eine Liste von Objekten vom Typ [Purchase]
add(ByVal aPurchase As Purchase)
fügt einen Kauf zur Liste der Käufe hinzu
remove(ByVal purchaseId As Integer)
entfernt den Kauf mit der ID idPurchase
ReadOnly Property totalBasket() As Double
Gesamtbetrag der Einkäufe im Warenkorb
Function ToString() As String
gibt die Zeichenfolge zurück, die den Warenkorb darstellt
  • Die Methode [posAchat] ist eine Hilfsmethode, die die Position eines durch die Artikelnummer identifizierten Kaufs in der Einkaufsliste zurückgibt. 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 existiert.
  • Die Methode [add] fügt einen neuen Kauf zur Einkaufsliste hinzu. Dabei wird entweder ein neuer Eintrag zur Einkaufsliste hinzugefügt, falls der gekaufte Artikel noch nicht in der Liste vorhanden war, oder die gekaufte Menge erhöht, falls er bereits vorhanden war.
  • Mit der Methode [remove] können Sie einen durch eine Nummer identifizierten Einkauf aus der Einkaufsliste entfernen. Wenn der Einkauf nicht existiert, führt die Methode keine Aktion aus.
  • Der Gesamtkaufbetrag [totalPanier] wird aktualisiert, wenn Artikel hinzugefügt oder entfernt werden.

1.4.4.4. Die Klasse [PurchaseItems]

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

Imports istia.st.articles.dao

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

        'private fields
        Private _articlesDao As IArticlesDao
        Private _erreurs As ArrayList

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

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

        ' list of items
        Public Function getAllArticles() As IList Implements IArticlesDomain.getAllArticles
            ' list of all items
            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

        ' get an item identified by its number
        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDomain.getArticleById
            ' a special item
            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


        ' buy a basket
        Public Sub acheter(ByVal panier As Panier) Implements IArticlesDomain.acheter
            ' basket purchase - stocks of purchased items must be decremented
            _erreurs = New ArrayList
            Dim achat As achat
            Dim achats As ArrayList = panier.achats
            For i As Integer = achats.Count - 1 To 0 Step -1
                ' decrement stock item i
                achat = CType(achats(i), achat)
                Try
                    If _articlesDao.changerStockArticle(achat.article.id, -achat.qte) = 0 Then
                        ' we couldn't do the operation
                        _erreurs.Add("L'achat " + achat.ToString + " n'a pu se faire - Vérifiez les stocks")
                    Else
                        ' the transaction has been completed - the purchase is removed from the basket
                        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

Kommentare:

  • Diese Klasse implementiert die vier Methoden der Schnittstelle [IArticlesDomain]. Sie verfügt über zwei private Felder:
_articlesDao As IArticlesDao
das Datenzugriffsobjekt
_errors As ArrayList
die Liste möglicher Fehler. Der Zugriff erfolgt über die öffentliche Eigenschaft [errors]
  • Um eine Instanz der Klasse zu erstellen, müssen Sie das Objekt bereitstellen, das den Zugriff auf die Daten ermöglicht:
Sub New(ByVal articlesDao As IArticlesDao)
  • Die Methoden [getAllArticles] und [getArticleById] stützen sich auf die gleichnamigen Methoden in der [dao]-Schicht
  • Die Methode [buy] validiert den Kauf eines Warenkorbs. Diese Validierung besteht lediglich darin, den Bestand der gekauften Artikel zu verringern. Ein Artikel kann nur gekauft werden, wenn ausreichend Lagerbestand vorhanden ist. Ist dies nicht der Fall, wird der Kauf abgelehnt: Der Artikel verbleibt im Warenkorb und ein Fehler wird in der Liste [errors] gemeldet. Ein validierter Kauf wird aus dem Warenkorb entfernt, und der Bestand des entsprechenden Artikels wird um die gekaufte Menge verringert.

1.4.4.5. Erstellen der [domain]-Layer-Assembly

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. NUnit-Tests für die [domain]-Schicht

Das Visual Studio-Testprojekt ist wie folgt aufgebaut:

Image

Anmerkungen:

  • Das [tests]-Projekt ist vom Typ [Class Library]
  • Die [NUnit]-Tests erfordern einen Verweis auf die Assembly [nunit.framework.dll]
  • Die [NUnit]-Testklasse ruft über Spring eine Instanz des zu testenden Objekts ab. Daher
  • müssen 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 [dao]-Schicht und die Assembly [webarticles-domain.dll] aus der [domain]-Schicht. Diese wurden im Ordner [bin] abgelegt und ihre Verweise zu den Projektverweisen hinzugefügt.

Eine NUnit-Testklasse für die [domain]-Schicht 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

        ' the test object
        Private articlesDomain As IArticlesDomain
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
            ' retrieve an instance of the Spring object manufacturer
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
            ' request instantiation of the articles dao object
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
            ' then the articlesdomain aobject
            articlesDomain = CType(factory.GetObject("articlesdomain"), IArticlesDomain)
        End Sub

        <Test()> _
        Public Sub getAllArticles()
            ' visual check
            listArticles()
        End Sub

        <Test()> _
        Public Sub getArticleById()
            ' article deletion
            articlesDao.clearAllArticles()
            ' check
            Dim articles As IList = articlesDomain.getAllArticles
            Assert.AreEqual(0, articles.Count)
            ' 2 items added
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            ' check
            articles = articlesDomain.getAllArticles
            Assert.AreEqual(2, articles.Count)
            ' research article 3
            Dim unArticle As Article = articlesDomain.getArticleById(3)
            ' check
            Assert.AreEqual(unArticle.nom, "article3")
            ' research article 4
            unArticle = articlesDao.getArticleById(4)
            ' check
            Assert.AreEqual(unArticle.nom, "article4")
        End Sub

        <Test()> _
        Public Sub acheterPanier()
            ' article deletion
            articlesDao.clearAllArticles()
            ' check
            Dim articles As IList = articlesDomain.getAllArticles
            Assert.AreEqual(0, articles.Count)
            ' 2 items added
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            ' check
            articles = articlesDomain.getAllArticles
            Assert.AreEqual(2, articles.Count)
            ' create a basket with two purchases
            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))
            ' checks
            Assert.AreEqual(700, panier.totalPanier, 0.000001)
            Assert.AreEqual(2, panier.achats.Count)
            ' shopping cart validation
            articlesDomain.acheter(panier)
            ' checks
            Assert.AreEqual(0, articlesDomain.erreurs.Count)
            Assert.AreEqual(0, panier.achats.Count)
            ' research article 3
            Dim unArticle As Article = articlesDomain.getArticleById(3)
            ' check
            Assert.AreEqual(unArticle.stockactuel, 20)
            ' research article 4
            unArticle = articlesDao.getArticleById(4)
            ' check
            Assert.AreEqual(unArticle.stockactuel, 30)
            ' new basket
            panier.ajouter(New Achat(New Article(3, "article3", 30, 30, 3), 100))
            ' shopping cart validation
            articlesDomain.acheter(panier)
            ' checks
            Assert.AreEqual(1, articlesDomain.erreurs.Count)
            ' research article 3
            unArticle = articlesDomain.getArticleById(3)
            ' check
            Assert.AreEqual(unArticle.stockactuel, 20)
        End Sub

        <Test()> _
        Public Sub testRetirerAchats()
            ' delete contents of ARTICLES
            articlesDao.clearAllArticles()
            ' reads the ARTICLES table
            Dim articles As IList = articlesDao.getAllArticles()
            Assert.AreEqual(0, articles.Count)
            ' insertion
            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)
            ' reads the ARTICLES table
            articles = articlesDomain.getAllArticles()
            Assert.AreEqual(2, articles.Count)
            ' create a basket with two purchases
            Dim monPanier As New Panier
            monPanier.ajouter(New Achat(article3, 10))
            monPanier.ajouter(New Achat(article4, 10))
            ' checks
            Assert.AreEqual(700.0, monPanier.totalPanier, 0.000001)
            Assert.AreEqual(2, monPanier.achats.Count)
            ' add a previously purchased item
            monPanier.ajouter(New Achat(article3, 10))
            ' checks
            ' the total must be increased to 1000
            Assert.AreEqual(1000.0, monPanier.totalPanier, 0.000001)
            ' always 2 items in the basket
            Assert.AreEqual(2, monPanier.achats.Count)
            ' qty item 3 increased to 20
            Dim unAchat As Achat = CType(monPanier.achats(0), Achat)
            Assert.AreEqual(20, unAchat.qte)
            ' article 3 is removed from the basket
            monPanier.enlever(3)
            ' checks
            ' the total must be increased to 400
            Assert.AreEqual(400.0, monPanier.totalPanier, 0.000001)
            ' 1 item only in basket
            Assert.AreEqual(1, monPanier.achats.Count)
            ' this must be article no. 4
            Assert.AreEqual(4, CType(monPanier.achats(0), Achat).article.id)
        End Sub

        ' screen listing
        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

Kommentare:

  • Wir wollten ein Testprogramm für die Schnittstelle [IArticlesDomain] schreiben, das unabhängig von ihrer Implementierungsklasse ist. Daher haben wir Spring verwendet, um den Namen der Implementierungsklasse vor dem Testprogramm zu verbergen.
  • Die Methode mit dem Attribut <Setup()> ruft aus 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 gibt an:

  • (Fortsetzung)
    • für das Singleton [articlesdao] den Namen der Implementierungsklasse [istia.st.articles.dao.ArticlesDaoArrayList] und den Speicherort [webarticles-dao.dll]. Da die Instanziierung keine Parameter erfordert, sind hier keine definiert.
    • für das Singleton [articlesdomain] den Namen der Implementierungsklasse [istia.st.articles.domain.AchatsArticles] und den Speicherort [webarticles-domain.dll]. Die Klasse [AchatsArticles] verfügt über einen Konstruktor mit einem Parameter: das Singleton, das den Zugriff auf die [dao]-Schicht verwaltet. Hier ist dies 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. Die Testklasse sollte theoretisch keinen Zugriff auf die [dao]-Schicht benötigen, von der sie nicht einmal wissen sollte. Hier haben wir diese „Ethik“ außer Acht gelassen, die, wenn wir sie befolgt hätten, uns dazu gezwungen hätte, neue Methoden in unserer [IArticlesDomain]-Schnittstelle zu erstellen.

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

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

Image

Leser, die dieses Dokument auf dem Bildschirm betrachten, werden sehen, dass alle Tests erfolgreich waren. Wir betrachten die [Domain]-Schicht nun als betriebsbereit.

1.4.5. Fazit

Denken Sie daran, dass wir die folgende dreischichtige Webanwendung erstellen wollen:

Das M-Modell unserer MVC-Anwendung ist nun geschrieben und getestet. Es wird uns in zwei DLLs [webarticles-dao.dll, webarticles-domain.dll] zur Verfügung gestellt. Wir können nun zur letzten Schicht, der [Web]-Schicht, übergehen, die den C-Controller und die V-Ansichten enthält. Wir werden zunächst eine Methode betrachten, die im Dokument [Web Development with ASP.NET 1.1] vorgestellt wird

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

1.5. Die [Web]-Schicht

Die MVC-Architektur der Webanwendung sieht wie folgt aus:

M = Modell
Die Business-Klassen [Domain], die Datenzugriffsklassen [DAO] und die Datenquelle
V = Ansichten
die ASPX-Seiten
C = Controller
Alle HTTP-Client-Anfragen durchlaufen die folgenden beiden Controller:
global.asax: verarbeitet Ereignisse im Zusammenhang mit dem ersten Start der Anwendung
main.aspx: verarbeitet jede Client-Anfrage einzeln

1.5.1. Die Ansichten

Die Ansichten entsprechen denen, die am Anfang dieses Dokuments vorgestellt wurden:

LISTE
list.aspx
Die Ansichten befinden sich im Ordner [views] der Anwendung
INFO
info.aspx
WARENKORB
Warenkorb.aspx
Warenkorb leeren
empty-cart.aspx
FEHLER
errors.aspx

1.5.2. Die Controller

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

  1. [global.asax, global.asax.vb]: dient in erster Linie dazu, die Anwendung zu initialisieren und den Kontext für alle Daten einzurichten, die zwischen verschiedenen Clients ausgetauscht werden sollen
  2. [main.aspx, main.aspx.vb]: der eigentliche Controller, der HTTP-Anfragen von Clients verarbeitet.

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

request
Bedeutung
Controller-Aktion
Mögliche Antworten
action=list
Der Client möchte die Liste der
Elemente
- fordert die Liste der Elemente von der Ebene an
Beruf
- [LIST]
- [FEHLER]
action=info
Der Client fordert
Informationen zu einem der
in der Ansicht angezeigten Elemente
[LIST]
- fordert das Element aus der Geschäftsschicht an
- [INFO]
- [FEHLER]
action=purchase
Der Kunde kauft einen Artikel
- fordert den Artikel von der Geschäftsschicht an
und legt ihn in den Warenkorb des Kunden
- [INFO] bei Mengenfehler
- [LIST] bei keinem Fehler
action=Kauf entfernen
Der Kunde möchte einen
Artikel aus seinem Warenkorb entfernen
- den Warenkorb aus der Sitzung abrufen
und modifiziere ihn
- [WARENKORB]
- [WARENKORB LEEREN]
- [FEHLER]
action=cart
Der Kunde möchte seinen
Warenkorb
- ruft den Warenkorb aus der Sitzung ab
- [WARENKORB]
- [WARENKORB LEER]
- [FEHLER]
action=validate-cart
Der Kunde hat seinen Einkauf abgeschlossen
und geht zur Kasse
- Aktualisiert die Datenbank mit den Lagerbeständen
der gekauften Artikel
- Leert den Warenkorb des Kunden von Artikeln
, deren Kauf bestätigt wurde
- [LISTE]
- [FEHLER]

1.5.3. Anwendungskonfiguration

Wir werden die Anwendung so konfigurieren, dass sie hinsichtlich Änderungen wie den folgenden so flexibel wie möglich ist:

  1. Änderungen an den URLs der verschiedenen Ansichten
  2. Änderungen an den Klassen, die die Schnittstellen [IArticlesDao] und [IArticlesDomain] implementieren
  3. Änderungen am DBMS, an der Datenbank oder an der Artikeltabelle

1.5.3.1. Änderungen an den URLs

Die Namen der Ansichts-URLs werden zusammen mit einigen anderen Parametern in die Konfigurationsdatei [web.config] der Anwendung 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. Ändern der Klassen, die die Schnittstellen implementieren

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

  • Schichten kommunizieren über Schnittstellen miteinander, nicht über konkrete Klassen
  • Der Code einer Schicht instanziiert niemals selbst die Klasse einer anderen Schicht, um sie zu verwenden. Er fordert lediglich eine Instanz der Schnittstellenimplementierung von einem externen Tool – in diesem Fall Spring – für die Schicht an, die er verwenden möchte. Dazu muss er nicht den Namen der Implementierungsklasse kennen, sondern lediglich den Namen des Spring-Singletons, für das er eine Referenz wünscht.

In unserer Anwendung wird Spring in der [web.config]-Datei 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 auf die [business]-Schicht zuzugreifen, kann eine Klasse in der [web]-Schicht 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 [Web]-Schicht, die das [articlesDomain]-Singleton angefordert hat, über die gesamte Kette, die sie mit der Datenquelle verbindet:

1.5.3.3. Änderungen in Bezug auf das DBMS oder die Datenbank

Dieser Punkt wird hier ignoriert, da wir in einer Testanwendung ohne DBMS arbeiten. Die Implementierung einer [dao]-Schicht auf Basis eines DBMS werden wir in einem späteren Abschnitt behandeln.

1.5.4. Die <asp:>-Tag-Bibliothek

Betrachten Sie die Ansicht [ERRORS], die eine Liste von Fehlern anzeigt:

Image

Die [ERRORS]-Ansicht ist dafür zuständig, eine Liste von Fehlern anzuzeigen, die der [main.aspx]-Controller unter dem Namen [context.Items("errors")] im Request-Kontext abgelegt hat. Es gibt mehrere Möglichkeiten, eine solche Seite zu schreiben. Hier interessiert uns nur der Teil, der die Fehleranzeige betrifft.

Denken Sie daran, dass eine ASPX-Seite eine HTML-Präsentationsschicht und eine .NET-Code-Schicht hat, die die Daten vorbereitet, die die Präsentationsschicht anzeigen muss. Diese beiden Teile können sich in derselben [aspx]-Datei (WebMatrix-Lösung) oder in zwei Dateien befinden: [aspx] für die Präsentation, [aspx.vb] für den Code. Die letztere Lösung wird von Visual Studio verwendet. Erschwerend kommt hinzu, dass die HTML-Präsentationsschicht auch .NET-Code enthalten kann, was die Trennung zwischen der [Controller]- und der [Präsentations]-Schicht der Ansicht verwischt. Von diesem Ansatz wird generell dringend abgeraten. Das Entfernen des gesamten Codes aus dem [Präsentations]-Bereich erforderte die Erstellung von Tag-Bibliotheken. Diese „verbergen“ den Code unter dem Deckmantel von Tags, die HTML-Tags ähneln. Wir stellen zwei mögliche Lösungen für die [ERRORS]-Seite vor.

Unsere erste Lösung verwendet .NET-Code im [Präsentations]-Abschnitt der Seite. Die ASPX-Seite ruft die Liste der in der Anfrage vorhandenen Fehler in ihrem Controller-Abschnitt [errors.aspx.vb] ab:

        Protected erreurs As ArrayList

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

und zeige sie dann im Abschnitt [presentation, errors.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 verwendet das <asp:repeater>-Tag aus der ASP.NET-Tag-Bibliothek <asp:>. Wenn Sie eine ASPX-Seite grafisch erstellen, steht dieses Tag als Serversteuerelement zur Verfügung, das Sie auf das Entwurfsformular ziehen können. Wenn Sie den ASPX-Code manuell schreiben, können Sie auf die Tag-Bibliothek zurückgreifen.

Mit der <asp:>-Tag-Bibliothek sieht der ASPX-Code für die vorherige [ERRORS]-Ansicht 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>

Die

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

Das tag-Element dient dazu, eine HTML-Vorlage für die verschiedenen Elemente einer Datenquelle zu wiederholen. Es besteht aus folgenden Elementen:

HeaderTemplate
die HTML-Vorlage, die vor der Anzeige der Datenquellenelemente angezeigt werden soll
ItemTemplate
die HTML-Vorlage, die für jedes Element in der Datenquelle wiederholt wird. Der Ausdruck [<%# Container.DataItem %>] wird verwendet, um den Wert des aktuellen Elements in der Datenquelle anzuzeigen
FooterTemplate
die HTML-Vorlage, die angezeigt werden soll, nachdem die Elemente der Datenquelle angezeigt wurden

Die Datenquelle ist an das Tag gebunden, typischerweise im [controller]-Bereich der Seite:

        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
..
            ' link errors to rptErreurs
            With rptErreurs
                .DataSource = context.Items("erreurs")
                .DataBind()
            End With
        End Sub

Diese Bindung kann auch während der Seitenerstellung vorgenommen werden, wenn die Datenquelle bereits bekannt ist, beispielsweise eine vorhandene Datenbank.

In unseren Ansichten verwenden wir ein weiteres Tag: <asp:datagrid>, mit dem wir eine Datenquelle als Tabelle anzeigen können.

1.5.5. Struktur der Visual Studio-Lösung für die Anwendung [webarticles]

Eine Webanwendung ist ein Puzzle mit vielen Teilen. Durch die Verwendung einer MVC-Architektur erhöht sich die Anzahl dieser Teile in der Regel. Die Struktur der [webarticles]-Anwendung in [Visual Studio] sieht wie folgt aus:

  

Kommentare:

  • Das [Web]-Projekt ist vom Typ [Class Library] und nicht vom Typ [ASP.NET Web Application], wie man logischerweise erwarten würde. Der Typ [ASP.NET Web Application] setzt das Vorhandensein des IIS-Webservers auf dem Entwicklungsrechner oder auf einem Remote-Rechner voraus. Der IIS-Server ist auf Rechnern mit Windows XP Home Edition standardmäßig nicht enthalten. Viele PCs werden jedoch mit dieser Version verkauft. Damit Leser, die Windows XP verwenden, die behandelte Anwendung implementieren können, werden wir den Cassini-Webserver (siehe Anhang) verwenden, der kostenlos von Microsoft erhältlich ist, und das [ASP.NET-Webanwendung]-Projekt durch ein [Klassenbibliothek]-Projekt ersetzen. Dies bringt einige Nachteile mit sich, die in den Anhängen erläutert werden.
  • Die von der Anwendung verwendeten DLLs sind folgende:
webarticles-dao.dll
enthält die Klassen der Datenzugriffsebene
webarticles-domain.dll
enthält die Klassen der Geschäftsschicht
Spring.Core.dll
enthält die Spring-Klassen, die es uns ermöglichen, die Web-, Domänen- und DAO-Schichten zu integrieren
log4net.dll
Logging-Klassen – von Spring verwendet

Diese DLLs werden im Ordner [bin] abgelegt und den Projektreferenzen hinzugefügt.

1.5.6. Die ASPX-Ansichten

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

1.5.6.1. Die Benutzerkomponente [entete.ascx]

Um die Konsistenz zwischen den verschiedenen Ansichten zu gewährleisten, verwenden diese denselben Header, der den Anwendungsnamen zusammen mit dem Menü anzeigt:

Das Menü ist dynamisch und wird vom Controller festgelegt. Der Controller fügt der an die ASPX-Seite gesendeten Anfrage ein Schlüsselattribut „actions“ hinzu, dessen Wert ein Array aus Hashtable()-Elementen ist. Jedes Element in diesem Array ist ein Dictionary, das dazu dient, eine Option im Kopfmenü 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 in ein Benutzersteuerelement umwandeln. Ein Benutzersteuerelement kapselt einen Abschnitt einer Seite (Layout und zugehöriger Code) in eine Komponente, die dann auf anderen Seiten wiederverwendet werden kann. Hier möchten wir die Komponente [entete] in anderen Ansichten der Anwendung wiederverwenden. Der Präsentationscode befindet sich in [entete.ascx] und der zugehörige Steuerelementcode in [entete.ascx.vb]. Der Präsentationscode verwendet eine <asp:repeater>-Komponente, um die Tabelle mit den Menüoptionen anzuzeigen:

Nr.
Typ
Name
Rolle
1
Repeater
rptMenu
Datenquelle: ein Array von Wörterbüchern
mit zwei Schlüsseln: href, link
Menüoptionen anzeigen

Der Code für die Darstellung der Seite sieht wie folgt aus:

<%@ 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("lien") %>
                            </a>
                        </td>
                    </ItemTemplate>
                </asp:Repeater>
            </tr>
        </table>
        <hr>

Kommentare:

  • Die Komponente [repeater] ist in den Zeilen 6–14 definiert
  • Jedes Element in der mit dem Repeater verknüpften Datenquelle ist ein Dictionary 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())
                ' associate the action table with its component
                With rptMenu
                    .DataSource = Value
                    .DataBind()
                End With
            End Set
        End Property
    End Class
End Namespace

Kommentare:

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

Die anderen Ansichten in der Anwendung verwenden die durch [entete.ascx] definierte Kopfzeile. Die Seite [erreurs.aspx] beispielsweise bindet die Kopfzeile mit dem folgenden Code 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>    

Kommentare:

  • Zeile 1 legt fest, dass das <WA:entete>-Tag mit der durch die Datei [entete.ascx] definierten Komponente verknüpft werden muss. Die Attribute [TagPrefix] und [TagName] sind optional.
  • Sobald dies erledigt ist, wird die Komponente mithilfe von Zeile 9 in den Präsentationscode der Seite eingefügt. Zur Laufzeit bindet dieses Tag den Code aus der Seite [entete.ascx] in die ASPX-Seite ein, die es enthält. Der Steuerungscode [erreurs.aspx.vb] übernimmt die Initialisierung dieser Komponente. Dies kann wie folgt erfolgen:
    Public Class ErreursWebarticles
        Inherits System.Web.UI.Page

        ' page components
...
        Protected WithEvents entete As New EnteteWebArticles

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
            ' link menu options to rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
...
        End Sub
    End Class

Kommentare:

  • Zeile 6 erstellt ein Objekt vom Typ [EnteteWebArticles], dem Typ der zu erstellenden Komponente
  • Zeile 11 initialisiert die Eigenschaft [actions] dieses Objekts

1.5.6.2. Die Ansicht [liste.aspx]

1.5.6.2.1. Einleitung

Diese Ansicht zeigt die Liste der zum Verkauf stehenden Artikel an:

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

Aktionen
Hashtable()-Objekt – das Array der Menüoptionen
listarticles
ArrayList mit Objekten vom Typ [Item]
message
String-Objekt – Meldung, die am unteren Rand der Seite angezeigt werden soll

Jeder [Info]-Link in der HTML-Tabelle der Artikel hat eine URL in der Form [?action=info&id=ID], wobei ID das id-Feld des angezeigten Artikels ist.

1.5.6.2.2. Seitenkomponenten
Nr.
Typ
Name
Rolle
1
Benutzerkomponente
Kopfzeile
Anzeige-Kopfzeile
2
DataGrid
DataGridArticles
3 – zugehörige Spalte: Überschrift: Name, Feld: name
4 – zugehörige Spalte: Überschrift: Preis, Feld: Preis
5 – Hypertext-Spalte: Text: Info, URL-Feld: id,
URL-Format: /webarticles/main.aspx?action=info&id={0}
Verkaufsartikel anzeigen
6
Bezeichnung
lblMessage
eine Meldung anzeigen

Schauen wir uns an, wie man diese Eigenschaften festlegt:

  • Wählen Sie in Visual Studio das [DataGrid] aus, um dessen Eigenschaftenfenster aufzurufen:

Image

  • Verwenden Sie den Link [AutoFormat] oben, um das Layout des angezeigten Rasters zu verwalten
  • und den Link [Property Generator], um dessen Inhalt zu verwalten
1.5.6.2.3. Präsentationscode [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>

Kommentare:

  • Zeile 9 definiert die Seitenüberschrift
  • Die Zeilen 12–24 definieren die Eigenschaften des [DataGrid]
  • Zeile 26 definiert das Label [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

    ' manages the item list display page
    Public Class ListeWebarticles
        Inherits System.Web.UI.Page

        ' page components
        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
            ' prepare the [list] view using context information
            ' link menu options to rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
            ' retrieve items from a datatable
            Dim articles As ArrayList = CType(context.Items("articles"), ArrayList)
            ' we link them to the [DataGrid] component of the page
            With DataGridArticles
                .DataSource = articles
                .DataBind()
            End With
            ' the message
            lblMessage.Text = context.Items("message").ToString
        End Sub
    End Class
End Namespace

Kommentare:

  • Die Seitenkomponenten erscheinen in den Zeilen 13–15. Beachten Sie, dass wir ein [EnteteWebArticles]-Objekt mit dem [new]-Operator erstellen mussten, während dies bei den anderen Komponenten nicht erforderlich war. Ohne diese explizite Erstellung trat ein Laufzeitfehler auf, der darauf hinwies, dass das [entete]-Objekt auf nichts verwies. Dieser Punkt verdient eine weitere Untersuchung. Er wurde noch nicht untersucht.
  • Die Tabelle der Kopfmenüoptionen wird aus dem Kontext abgerufen, um die [entete]-Komponente der Seite zu initialisieren – Zeile 20
  • Die Liste der Artikel wird aus dem Kontext abgerufen – Zeile 22
  • um die [DataGridArticles]-Komponente zu initialisieren – Zeilen 24–27
  • Die [lblMessage]-Komponente wird mit einer im Kontext platzierten Meldung initialisiert – Zeile 29

1.5.6.3. Die Ansicht [infos.aspx]

1.5.6.3.1. Einführung

Diese Ansicht zeigt Informationen zu einem Artikel an und ermöglicht 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 Parameter der Controller-Anfrage lauten wie folgt:

actions
Hashtable()-Objekt – das Array der Menüoptionen
item
Objekt vom Typ [Artikel] – anzuzeigender Eintrag
msg
String-Objekt – Meldung, die im Falle eines Fehlers bei der Menge angezeigt werden soll
qty
String-Objekt – Wert, der im Eingabefeld [Qty] angezeigt werden soll

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

Image

Diese Seite enthält ein Formular, das über die Schaltfläche [Kaufen] übermittelt wird. Die Ziel-URL für die POST-Anfrage lautet [?action=purchase&id=ID], wobei ID die ID des gekauften Artikels ist.

1.5.6.3.2. Seitenkomponenten
Nr.
Typ
Name
Rolle
1
Benutzerkomponente
Kopfzeile
Anzeige-Kopfzeile
2
Literal
litID
Anzeige der Artikelnummer
3 bis 6
DataGrid
DataGridArtikel
3 – zugehörige Spalte: Überschrift: Name, Feld: name
4 – zugehörige Spalte: Überschrift: Preis, Feld: Preis
5 – zugehörige Spalte: Überschrift: Aktueller Bestand, Feld: currentStock
6 – zugehörige Spalte: Überschrift: Mindestbestand, Feld: stockMinimum
Ein Element anzeigen
7
HTML-Absenden
 
Formular absenden
8
HTML-Eingabe
runat=server
txtQty
Geben Sie die gekaufte Menge ein
9
label
lblMsgQte
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>

Kommentare:

  • Der Header ist in der Seite enthalten – Zeile 9
  • Das Literal [litId] ist in Zeile 10 definiert
  • Das DataGrid [DataGridArticles] ist in den Zeilen 12–34 definiert
  • Das Formular ist in den Zeilen 36–46 definiert. Es ist vom Typ POST.
  • Das POST-Ziel wird durch eine 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 serverseitige HTML-Komponente (runat=server). Auf der Codeseite wird über ein Objekt darauf zugegriffen.
  • Zeile 42 definiert das Label [lblMsgQte], das etwaige Fehlermeldungen bezüglich der 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

    ' manages an article information page
    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

        ' the URL where the form will be posted
        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

        ' page display
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            ' retrieve query info
            Dim unArticle As Article = CType(Session.Item("article"), Article)
            ' link menu options to rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
            ' we link the article to [DataGrid]
            Dim articles As New ArrayList
            articles.Add(unArticle)
            With DataGridArticle
                .DataSource = articles
                .DataBind()
            End With
            ' the id label
            litId.Text = unArticle.id.ToString
            ' the error message
            lblMsgQte.Text = context.Items("msg").ToString
            ' the previous qty
            txtQte.Value = context.Items("qte").ToString
            ' the URL action
            strAction = "?action=achat&id=" + unArticle.id.ToString
        End Sub
End Namespace

Kommentare:

  • Die Seitenkomponenten sind in den Zeilen 10–14 definiert
  • Die Klasse definiert eine öffentliche Eigenschaft [strAction], die zur Festlegung des POST-Ziels des Formulars dient – Zeilen 17–25
  • Der anzuzeigende Artikel wird aus dem Anwendungskontext abgerufen – Zeile 30
  • Das Array der Kopfmenüoptionen wird aus dem Kontext abgerufen, um die [entete]-Komponente der Seite zu initialisieren – Zeile 32
  • Zeilen 33–39: Die Komponente [DataGridArticle] wird an eine Datenquelle vom Typ [ArrayList] gebunden, die nur 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 Informationen aus dem Kontext initialisiert – Zeile 47. Diese Variable wird verwendet, um das [action]-Attribut des auf der Seite vorhandenen HTML-Formulars zu generieren:
        <form method="post" action="<%=strAction%>">
....
        </form>

1.5.6.4. Die Ansicht [panier.aspx]

1.5.6.4.1. Einführung

Diese Ansicht zeigt den Inhalt des Warenkorbs an:

Image

Sie wird als Antwort auf eine Anfrage wie /main?action=cart oder /main?action=cancelpurchase&id=ID angezeigt. Die Anfrageparameter des Controllers lauten wie folgt:

actions
object Hashtable() – das Array der Menüoptionen
cart
Objekt vom Typ [Cart] – der anzuzeigende Warenkorb

Jeder [Entfernen]-Link in der HTML-Tabelle der Warenkorbartikel hat eine URL in der Form [?action=removeitem&id=ID], wobei ID das Feld [id] des Artikels ist, der aus dem Warenkorb entfernt werden soll.

1.5.6.4.2. Seitenkomponenten
Nr.
Typ
Name
Rolle
1
Benutzerkomponente
Kopfzeile
Anzeige-Kopfzeile
2
DataGrid
DataGridPurchases
3 – zugehörige Spalte – Kopfzeile: Artikel, Feld: Name
4 – zugehörige Spalte – Überschrift: Menge, Feld: Menge
5 – zugehörige Spalte – Überschrift: Preis, Feld: Preis
6 – zugehörige Spalte – Überschrift: Gesamt, Feld: total, 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
den fälligen Betrag anzeigen
1.5.6.4.3. Der Präsentationscode [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>

Kommentare

  • Zeile 9 enthält die Kopfzeile
  • Die Zeilen 12–27 definieren die Komponente [DataGridAchats]
  • Zeile 29: Die Komponente [lblTotal] wird definiert
1.5.6.4.4. Der Steuerungscode [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
    ' manages the basket display page
    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
            ' link menu options to rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
            ' we pick up the basket
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
            ' transfer purchases to a table of purchase lines
            Dim achats(unPanier.achats.Count - 1) As LigneAchat
            ' change the type of ArrayList elements
            For i As Integer = 0 To achats.Length - 1
                achats(i) = New LigneAchat(CType(unPanier.achats(i), Achat))
            Next
            ' link the data to the [DataGrid] components of the page
            With DataGridAchats
                .DataSource = achats
                .DataBind()
            End With
            ' total payable is displayed
            lblTotal.Text = unPanier.totalPanier.ToString
        End Sub

        ' purchase line built from a Purchase object
        Private Class LigneAchat
            Inherits Achat

            ' builder receives a purchase
            Public Sub New(ByVal unAchat As Achat)
                Me.article = unAchat.article
                Me.qte = unAchat.qte
            End Sub

            ' id: returns the id of the item purchased
            Public ReadOnly Property id() As Integer
                Get
                    Return article.id
                End Get
            End Property

            ' name: name of item purchased
            Public ReadOnly Property nom() As String
                Get
                    Return article.nom
                End Get
            End Property

            ' price of item purchased
            Public ReadOnly Property prix() As Double
                Get
                    Return article.prix
                End Get
            End Property

        End Class
    End Class
End Namespace

Kommentare:

  • Die Seitenkomponenten werden in den Zeilen 11–13 deklariert
  • Die Initialisierung der [header]-Komponente entspricht der in 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. Sehen wir uns ihre Spalten an:
    • Die Spalte [Article] ist mit dem Feld [name] in der Datenquelle verknüpft
    • Spalte [Qty], die dem Feld [qty] in der Datenquelle zugeordnet ist
    • Spalte [Price], die dem Feld [price] in der Datenquelle zugeordnet ist
    • Spalte [Total], die dem Feld [total] in der Datenquelle zugeordnet ist

Unsere Datenquelle ist der Warenkorb und die dazugehörige Einkaufsliste. Letztere dient als Datenquelle für das [DataGrid]. Die [Purchase]-Objekte, die die Zeilen des [DataGrid] füllen sollen, verfügen jedoch nicht über die vom [DataGrid] erwarteten Eigenschaften [name, qty, price, total]. Daher erstellen wir hier speziell für das [DataGrid] eine Datenquelle, deren Elemente die vom [DataGrid] erwarteten Eigenschaften aufweisen. Diese Elemente werden vom Typ [PurchaseLine] sein, einer zu diesem Zweck erstellten Klasse, die von der Klasse [Purchase] abgeleitet ist – Zeilen 36–66

  • Sobald die Klasse [PurchaseLine] definiert ist, wird die Datenquelle für [DataGridPurchases] aus dem in der Sitzung befindlichen Warenkorb erstellt – Zeilen 20–30
  • Der Gesamtkaufbetrag wird mithilfe der Eigenschaft [totalPanier] der Klasse [Panier] angezeigt – Zeile 32

1.5.6.5. Die Ansicht [emptyCart.aspx]

1.5.6.5.1. Einführung

Diese Ansicht zeigt Informationen an, die darauf hinweisen, dass der Warenkorb leer ist:

Image

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

Aktionen
Hashtable()-Objekt – das Array der Menüoptionen
1.5.6.5.2. Seitenkomponenten
Nr.
Typ
Name
Rolle
1
Benutzerkomponente
Kopfzeile
Anzeige-Kopfzeile
1.5.6.5.3. Der Präsentationscode [emptycart.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>

Kommentare:

  • Der Header ist in Zeile 9 enthalten
1.5.6.5.4. Der Steuerungscode [paniervide.aspx.vb]
Namespace istia.st.articles.web
    ' manages the empty basket display page
    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
            ' prepare the [emptybasket] view using context information
            ' link menu options to rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
        End Sub
    End Class
End Namespace

Kommentare:

  • Wir initialisieren einfach die einzige dynamische Komponente der Seite – Zeile 10

1.5.6.6. Die Ansicht [errors.aspx]

1.5.6.6.1. Einführung

Diese Ansicht wird im Falle von Fehlern angezeigt:

Image

Sie wird nach jeder Anfrage angezeigt, die zu einem Fehler führt, mit Ausnahme der Kaufaktion mit einer falschen Menge, die von der Ansicht [INFOS] behandelt wird. Die Elemente der Controller-Anfrage lauten wie folgt:

Aktionen
Hashtable()-Objekt – das Array der Menüoptionen
Fehler
ArrayList mit [String]-Objekten, die die anzuzeigenden Fehlermeldungen darstellen
1.5.6.6.2. Seitenelemente
Nr.
Typ
Name
Rolle
1
Benutzerkomponente
Kopfzeile
Anzeige-Kopfzeile
2
Wiederholer
rptErrors
Fehlerliste anzeigen
1.5.6.6.3. Der Präsentationscode [errors.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>

Kommentare:

  • Der Header ist in Zeile 9 definiert
  • Die Komponente [rptErrors] ist in den Zeilen 13–19 definiert. Ihr Inhalt stammt aus einer Datenquelle vom Typ [ArrayList], die [String]-Objekte enthält.
1.5.6.6.4. Der Steuerungscode [errors.aspx.vb]
Namespace istia.st.articles.web

    ' manages the error page
    Public Class ErreursWebarticles
        Inherits System.Web.UI.Page

        ' page components
        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
            ' prepare the [errors] view using context information
            ' link menu options to rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
            ' link errors to rptErreurs
            With rptErreurs
                .DataSource = context.Items("erreurs")
                .DataBind()
            End With
        End Sub
    End Class

End Namespace

Kommentare:

  • Die Komponente [header] wird wie gewohnt initialisiert, Zeilen 9 und 14
  • Die [rptErrors]-Komponente wird mit der im Kontext gefundenen [ArrayList]-Fehlerliste initialisiert – Zeilen 16–19

1.5.7. Die Controller global.asax und main.aspx

Wir müssen noch den Kern unserer Webanwendung schreiben: den Controller. Seine Aufgabe ist es:

  • die Anfrage des Clients abrufen,
  • die vom Client angeforderte Aktion mithilfe der Business-Klassen zu verarbeiten,
  • die entsprechende Ansicht als Antwort senden.

1.5.7.1. Der Controller [global.asax.vb]

Wenn die Anwendung ihre allererste Anfrage erhält, wird die Prozedur [Application_Start] in der Datei [global.asax.vb] ausgeführt. Dies geschieht nur einmal. Der Zweck der Prozedur [Application_Start] besteht darin, die von der Webanwendung benötigten Objekte zu initialisieren, die von allen Client-Threads im schreibgeschützten Modus gemeinsam genutzt werden. Diese gemeinsam genutzten Objekte können an zwei Stellen platziert werden:

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

Die Methode [Application_Start] in der Datei [global.asax.vb] führt die folgenden Aktionen aus:

  • Sie überprüft die Datei [web.config] auf die Parameter, die für die ordnungsgemäße Funktion der Anwendung erforderlich sind. Diese wurden in Abschnitt 1.5.3 beschrieben.
  • Sie fügt eine Liste aller Fehler im Anwendungskontext in Form eines [ArrayList errors]-Objekts ein. Diese Liste ist leer, wenn keine Fehler vorliegen, existiert aber dennoch.
  • Wenn Fehler aufgetreten sind, bricht die Methode [Application_Start] an dieser Stelle ab. Andernfalls fordert sie eine Referenz auf ein Singleton 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. Dieser Instanziierungsvorgang kann zu verschiedenen Fehlern führen. In diesem Fall werden diese Fehler wiederum im [errors]-Objekt des Anwendungskontexts gespeichert.

Der Controller [global.asax.vb] verfügt über eine [Session_Start]-Prozedur, die bei jedem Eintreffen eines neuen Clients ausgeführt wird. In dieser Prozedur erstellen wir einen leeren Warenkorb für den Client. Dieser Warenkorb wird während der gesamten Dauer der Anfragen dieses bestimmten Clients 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

        ' init application
        Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)

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

            ' retrieve application initialization parameters
            Dim param As String
            For i As Integer = 0 To parameters.Length - 1
                ' read from conf file
                param = ConfigurationSettings.AppSettings(parameters(i))
                If param Is Nothing Then
                    ' we note the error
                    erreurs.Add("Paramètre [" + parameters(i) + "] absent dans le fichier [web.config]")
                Else
                    ' the parameter is stored in the application
                    Application.Item(parameters(i)) = param
                End If
            Next
            ' mistakes?
            If erreurs.Count = 0 Then
                ' create a IArticlesDomain business layer access object
                Dim contexte As IApplicationContext = CType(ConfigurationSettings.GetConfig("spring/context"), IApplicationContext)
                Dim articlesDomain As IArticlesDomain
                Try
                    articlesDomain = CType(contexte.GetObject("articlesDomain"), IArticlesDomain)
                    ' the object is stored in the application
                    Application.Item("articlesDomain") = articlesDomain
                Catch ex As Exception
                    ' we memorize the error
                    erreurs.Add("Erreur lors de la construction de l'objet d'accès à la couche métier [" + ex.ToString + "]")
                End Try
            End If
            ' errors are placed in the application
            Application.Item("erreurs") = erreurs
            ' it's over if there were mistakes
            If erreurs.Count <> 0 Then Return
            ' build an array of menu options
            Dim options As New Hashtable
            ' retrieve the URL from the controller
            Dim urlMain As String = CType(Application.Item("urlMain"), String)
            Dim uneOption As Hashtable
            ' list of items
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=liste")
            uneOption.Add("lien", "Liste des articles")
            options.Add("liste", uneOption)
            ' basket
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=panier")
            uneOption.Add("lien", "Voir le panier")
            options.Add("panier", uneOption)
            ' shopping cart validation
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=validationpanier")
            uneOption.Add("lien", "Valider le panier")
            options.Add("validationpanier", uneOption)
            ' set menu options in the application
            Application.Item("options") = options
            Return
        End Sub

        ' init session
        Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
            ' create a basket for the customer
            Session.Item("panier") = New Panier
        End Sub
    End Class
End Namespace

Kommentare:

  • Die erwarteten Parameter in [web.config] 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 [errors] protokolliert – Zeilen 21–33
  • Wenn keine Fehler vorliegen, wird Spring um eine Referenz auf das Singleton [articlesDomain] gebeten, das den Zugriff auf die [domain]-Schicht der Anwendung verwaltet – Zeilen 35–47. Etwaige Fehler werden in [errors] protokolliert.
  • Fehler werden im Anwendungskontext protokolliert – Zeile 49
  • Die Prozedur wird beendet, wenn Fehler aufgetreten sind – Zeile 51
  • Wir erstellen ein Array aus drei Dictionaries. Jedes 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
  • Für jeden neuen Kunden wird die Prozedur [Session_Start] ausgeführt. In der Sitzung des Kunden wird ein leerer Warenkorb erstellt – Zeilen 78–81

1.5.7.2. Der Controller [main.aspx.vb]

Der Controller [main.aspx.vb] verarbeitet alle Client-Anfragen. Diese Anfragen folgen alle dem Format [/webarticles/main.aspx?action=XX]. Eine Anfrage wird wie folgt verarbeitet:

  • Das [errors]-Objekt im Anwendungskontext wird überprüft. Ist es nicht leer, bedeutet dies, dass während der Initialisierung der Anwendung Fehler aufgetreten sind und die Anwendung nicht ausgeführt werden kann. Als Reaktion darauf wird die Ansicht [ERRORS] gesendet.
  • Der Parameter [action] der Anfrage wird abgerufen und überprüft. Wenn er keiner bekannten Aktion entspricht, wird die Ansicht [ERRORS] mit einer entsprechenden Fehlermeldung gesendet.
  • Ist der Parameter [action] gültig, wird die Anfrage des Clients zur Verarbeitung an eine für die Aktion spezifische Prozedur weitergeleitet:
Methode
Anfrage
Verarbeitung
mögliche Antworten
doList
GET /main?action=list
- Liste der Elemente
aus der Business-Klasse
- anzeigen
[LIST] oder [ERRORS]
doInfo
GET /main?action=info&id=ID
- Ruft das Element mit der ID=ID aus
der Business-Klasse
- es anzeigen
[INFO] oder [ERRORS]
doPurchase
POST /main?action=purchase&id=ID
- Die gekaufte Menge ist in den übermittelten Parametern enthalten
- Fordere den Artikel mit id=ID von
der Business-Klasse
- füge ihn im
der Kundensitzung
[LIST] oder [INFO] oder [ERRORS]
doRetirePurchase
GET /main?action=removePurchase&id=ID
- Entferne den Artikel mit der ID=ID aus der
Einkaufsliste in
der Kundensitzung
[CART]
doCart
GET /main?action=cart
- Zeige die
Client-Sitzung
[CART] oder [EMPTY_CART]
doCartValidation
GET /main?action=cartvalidation
- Verringere die
Lagerbestände aller Artikel
im
[LIST] oder [ERRORS]
[LIST] oder [ERRORS]

Die Controller-Vorlage [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

    ' web application controller class
    Public Class MainWebArticles
        Inherits System.Web.UI.Page

        ' private fields
        Private articlesDomain As IArticlesDomain
        Private options As Hashtable

        ' page loading
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
....
        End Sub

        ' stock processing methods

        ' list of items
        Public Sub doListe()
...
        End Sub

        ' article info
        Public Sub doInfos()
...
        End Sub

        ' purchase an item
        Public Sub doAchat()
...
        End Sub

        ' delete a purchase
        Public Sub doRetirerAchat()
...
        End Sub

        ' view basket
        Public Sub doPanier()
...
        End Sub

        ' bUY BASKET
        Public Sub doValidationPanier()
...
        End Sub

    End Class

End Namespace

Kommentare:

  • 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 [domain]-Ebene
    • options: das Array von Dictionaries, das Menüoptionen enthält
  • Die [Page_Load]-Prozedur:
    • initialisiert die beiden privaten Felder der Klasse
    • ruft den [action]-Parameter aus der Anfrage ab und führt die Methode aus, die diese Aktion verarbeitet.

1.5.7.3. Die [Page_Load]-Methode

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

        ' page loading
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            ' check that the application has started correctly
            Dim erreurs As ArrayList = CType(Application.Item("erreurs"), ArrayList)
            ' if there are errors, we send the view [errors]
            If erreurs.Count <> 0 Then
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {}
                Server.Transfer(CType(Application("urlErreurs"), String))
            End If
            ' retrieve the business class access object
            articlesDomain = CType(Application.Item("articlesDomain"), IArticlesDomain)
            ' and menu options
            options = CType(Application.Item("options"), Hashtable)
            ' retrieve the action to be performed
            Dim action As String = Request.QueryString("action")
            If action Is Nothing Then
                action = "liste"
            End If
            ' execute the action
            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

Kommentare:

  • Bei jedem Laden der Seite stellen wir sicher, dass die von [global.asax] durchgeführte Anwendungsinitialisierung erfolgreich war.
  • Dazu rufen wir die Liste der von [global.asax] dort abgesetzten Fehler aus dem Anwendungskontext ab – Zeile 4
  • Ist diese Liste nicht leer, zeigen wir die Ansicht [ERRORS] an – Zeilen 6–10
  • Wir rufen das von [global.asax] im Anwendungskontext abgelegte Singleton [articlesDomain] ab und speichern es im privaten Feld [articlesDomain], damit es den verschiedenen Methoden der Klasse zur Verfügung steht – Zeile 12
  • Wir führen eine ähnliche Operation mit dem Array der Menüoptionen durch – Zeile 14
  • Wir rufen den Parameter [action] aus der Anfrage ab – Zeile 16
  • Wir führen die Methode aus, die der angeforderten Aktion entspricht. Eine nicht angegebene Aktion wird als [list]-Aktion behandelt – Zeilen 16–36

1.5.7.4. Behandlung der [list]-Aktion

Dies beinhaltet die Anzeige der Liste der Elemente:

Image

Der Code lautet wie folgt:

        ' list of items
        Public Sub doListe()
            ' error management
            Dim erreurs As New ArrayList
            ' the list of items is requested
            Dim articles As IList
            Try
                articles = articlesDomain.getAllArticles
            Catch ex As Exception
                ' we note the error
                erreurs.Add(ex.ToString)
            End Try
            ' mistakes?
            If erreurs.Count <> 0 Then
                ' display view [errors]
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
            End If
            ' display view [list]
            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

Kommentare:

  • Alle Fehler werden in einer [ArrayList] gespeichert – Zeile 4
  • Die Liste der Elemente wird vom Singleton [articlesDomain] angefordert – Zeilen 5–12
  • Wenn Fehler aufgetreten sind, wird die Ansicht [ERRORS] gerendert – Zeilen 13–19
  • Andernfalls wird die Ansicht [LIST] zurückgegeben – Zeilen 20–24

1.5.7.5. Behandlung der [info]-Aktion

Der Client hat Informationen zu einem bestimmten Element angefordert:

Image

Der Code lautet wie folgt:

        ' article info
        Public Sub doInfos()
            ' error management
            Dim erreurs As New ArrayList
            ' retrieve the id of the requested item
            Dim strId As String = Request.QueryString("id")
            ' do we have anything?
            If strId Is Nothing Then
                ' not normal - sends error page
                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
            ' do we have an integer?
            Dim id As Integer
            Try
                id = Integer.Parse(strId)
            Catch ex As Exception
                ' not normal - sends error page
                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
            ' the key item id is requested
            Dim unArticle As Article
            Try
                unArticle = articlesDomain.getArticleById(id)
            Catch ex As Exception
                ' data access issues
                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
            ' has an item been recovered?
            If unArticle Is Nothing Then
                ' article does not exist
                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
            ' we have the article - we put it in the current session
            Session.Item("article") = unArticle
            ' prepare your display
            context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
            context.Items("msg") = ""
            context.Items("qte") = ""
            Server.Transfer(CType(Application.Item("urlInfos"), String))
        End Sub

Kommentare:

  • Etwaige Fehler werden in einem [ArrayList] gespeichert – Zeile 4
  • Die ID des angeforderten Elements wird aus der Anfrage abgerufen – Zeile 6
  • Diese ID wird validiert. Sie muss existieren und eine Ganzzahl sein. Ist dies nicht der Fall, wird die Ansicht [ERRORS] mit der entsprechenden Fehlermeldung angezeigt – Zeilen 7–27
  • Sobald die ID überprüft wurde, wird das Element vom Singleton [articlesDomain] angefordert. Tritt eine Ausnahme auf, wird die Ansicht [ERRORS] gesendet – Zeilen 29–39
  • Wird der Artikel nicht gefunden, wird die Ansicht [ERRORS] gesendet – Zeilen 41–48
  • Wird der Artikel gefunden, wird er in die Sitzung des Benutzers aufgenommen und anschließend in der Ansicht [INFO] angezeigt – Zeilen 50–56

1.5.7.6. Verarbeitung der [purchase]-Aktion

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

Image

Der Code lautet wie folgt:

        ' purchase an item
        Public Sub doAchat()
            ' purchase an item
            ' we retrieve the article that was in the session
            Dim unArticle As Article = CType(Session.Item("article"), Article)
            ' do we have anything?
            If unArticle Is Nothing Then
                ' not normal - item list is displayed
                doListe()
            End If
            ' we retrieve the posted qty
            Dim strQte As String = Request.Form("txtQte")
            ' do we have anything?
            If strQte Is Nothing Then
                ' not normal - we send the list of items
                doListe()
            End If
            ' do we have an integer?
            Dim qte As Integer
            Try
                qte = Integer.Parse(strQte)
                If (qte <= 0) Then Throw New Exception
            Catch ex As Exception
                ' not normal - send info page with error message
                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
            ' all's well - we put the purchase in the customer's shopping cart
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
            unPanier.ajouter(New Achat(unArticle, qte))
            ' displays the list of items
            doListe()
        End Sub

Kommentare:

  • Das in der Sitzung abgelegte Element wird abgerufen – Zeile 5
  • Wenn er nicht vorhanden ist (die Sitzung ist möglicherweise abgelaufen), wird die Ansicht [LIST] angezeigt – Zeilen 7–10
  • Die gekaufte Menge wird aus der Abfrage abgerufen – Zeile 12
  • Ihre Gültigkeit wird überprüft – Zeilen 13–29
  • Ist sie ungültig, wird je nach Fall die Ansicht [LIST] angezeigt – Zeile 16 oder die Ansicht [INFO] – Zeilen 24–28
  • Wenn alles normal ist, wird der Kauf in den Warenkorb gelegt – Zeilen 31–32
  • Anschließend wird die Ansicht [LIST] gesendet – Zeile 34

1.5.7.7. Verarbeitung der [cart]-Aktion

Der Kunde hat mehrere Einkäufe getätigt und möchte den Warenkorb anzeigen:

Image

Der Code lautet wie folgt:

        ' view basket
        Public Sub doPanier()
            'the basket is retrieved from the session
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
            ' empty basket?
            If unPanier.achats.Count = 0 Then
                ' empty basket display
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlPanierVide"), String))
            End If
            ' display basket not empty
            context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable), CType(options("validationpanier"), Hashtable)}
            Server.Transfer(CType(Application.Item("urlPanier"), String))
        End Sub

Kommentare:

  • Wir rufen den Warenkorb aus der Sitzung ab – Zeile 4. Wir überprüfen hier nicht, ob tatsächlich etwas abgerufen wird. Das sollten wir tun, da die Sitzung möglicherweise abgelaufen ist.
  • Wenn der Warenkorb leer ist, senden wir die Ansicht [EMPTY CART] – Zeilen 6–10
  • Andernfalls senden wir die Ansicht [SHOPPINGCART] – Zeilen 11–14

1.5.7.8. Verarbeitung der Aktion [removepurchase]

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

Image

Der Code lautet wie folgt:

        ' delete a purchase
        Public Sub doRetirerAchat()
            'the basket is retrieved from the session
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
            ' we remove the purchase
            Try
                ' retrieve the id of the removed item
                Dim idArticle As Integer = Integer.Parse(Request.QueryString("id"))
                ' take it out of the basket
                unPanier.enlever(idArticle)
            Catch ex As Exception
                ' displays the list of items
                doListe()
            End Try
            ' the basket is displayed
            doPanier()
        End Sub

Kommentare:

  • Den Warenkorb aus der Sitzung abrufen – Zeile 4. Wir überprüfen hier nicht, ob tatsächlich etwas abgerufen wurde. Dies sollte jedoch geschehen, da die Sitzung möglicherweise abgelaufen ist.
  • Die ID [id] des zu entfernenden Artikels aus der Abfrage abrufen – 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 sie einen ungültigen Typ hat, tritt eine Ausnahme auf, die in den Zeilen 11–13 behandelt wird. Wenn sie gültig ist, aber nicht existiert, führt die Methode [cart.remove] – Zeile 10 – nichts aus.
  • Der neue Warenkorb wird angezeigt – Zeile 16

1.5.7.9. Verarbeitung der Aktion [validateCart]

Der Kunde möchte seinen Warenkorb bestätigen:

Image

Der Code lautet wie folgt:

        ' bUY BASKET
        Public Sub doValidationPanier()
            ' at the start, no mistakes
            Dim erreurs As ArrayList
            'the basket is retrieved from the session
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
            ' we try to validate the basket
            Try
                articlesDomain.acheter(unPanier)
                ' note any purchasing errors
                erreurs = articlesDomain.erreurs
            Catch ex As Exception
                ' we note the error
                erreurs = New ArrayList
                erreurs.Add(String.Format("Erreur lors de la validation du panier [{0}]", ex.Message))
            End Try
            ' if errors then error page
            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
            ' all is well - the list of items is displayed with a success message
            context.Items("message") = "Votre panier a été validé"
            doListe()
        End Sub

Kommentare:

  • Wir rufen den Warenkorb aus der Sitzung ab – Zeile 6. Wir überprüfen hier nicht, ob tatsächlich etwas abgerufen wird. Dies sollten wir tun, da die Sitzung möglicherweise abgelaufen ist.
  • Wenn die Sitzung abgelaufen ist, haben wir einen [nothing]-Zeiger für den Warenkorb, und die [buy]-Methode – Zeile 9 – löst eine Ausnahme aus, woraufhin die [ERRORS]-Ansicht angezeigt wird. Die Fehlermeldung ist für den Benutzer jedoch unklar.
  • Zeilen 8–16: Wir versuchen, den aus der Sitzung abgerufenen Warenkorb zu validieren. Einige Käufe können fehlschlagen, wenn die angeforderte Menge den Lagerbestand des gewünschten Artikels übersteigt. Diese Fälle werden von der [buy]-Methode in einer Fehlerliste gespeichert, die in Zeile 11 abgerufen wird.
  • Wenn Fehler vorliegen, wird die Ansicht [ERRORS] gesendet – Zeilen 18–23
  • ansonsten wird die Ansicht [LIST] gesendet – Zeilen 25–26

1.6. Fazit

Hier haben wir eine Anwendung unter Verwendung des MVC-Musters entwickelt. Stand April 2005 scheint es keine professionellen MVC-Entwicklungsframeworks für ASP.NET zu geben, die mit denen für Java (Struts, Spring usw.) vergleichbar sind. Es wird erwartet, dass das [Spring.net]-Projekt bald eines veröffentlicht. Bis dahin bietet die oben beschriebene Methode einen praktikablen MVC-Entwicklungsansatz für mittelgroße Anwendungen.