Skip to content

1. Część 1

Plik PDF z tego dokumentu jest dostępny jako |TUTAJ|.

Przykłady z dokumentu są dostępne pod numerem |TUTAJ|.

1.1. Introduction

Cele artykułu:

  • napisanie trójwarstwowej aplikacji internetowej [interface utilisateur, métier, accès aux données]
  • skonfigurowanie aplikacji przy użyciu Springa IOC
  • napisać różne wersje poprzez zmianę implementacji jednej lub kilku z trzech warstw.

Wykorzystane narzędzia:

  • Visual Studio.net do tworzenia aplikacji – patrz załącznik, punkt 3.1;
  • Serwer WWW Cassini do uruchamiania – patrz załącznik, punkt 3.2;
  • Nunit do testów jednostkowych – patrz załącznik, punkt 3.4;
  • Spring do integracji i konfiguracji warstw aplikacji internetowej – patrz załącznik, punkt 3.3;

W skali poziomów początkujący–średniozaawansowany–zaawansowany niniejszy dokument plasuje się w części [intermédiaire-avancé]. Jego zrozumienie wymaga spełnienia różnych warunków wstępnych. Niektóre z nich można poznać w dokumentach, które sam napisałem. W takim przypadku je cytuję. Oczywiste jest, że jest to jedynie sugestia i że czytelnik może korzystać ze swoich ulubionych dokumentów.

Niniejszy dokument nawiązuje do dokumentu poświęconego Javie [Architectures à 3 couches et architectures MVC avec Struts, Spring et Java ]. W VB.NET tworzymy trójwarstwową aplikację internetową MVC napisaną w Javie. Chcemy tutaj podkreślić, że platformy programistyczne Java i .NET są na tyle do siebie zbliżone, że umiejętności zdobyte w jednej z tych dziedzin można wykorzystać w drugiej.

Wydaje się, że nie istnieje powszechnie uznane rozwiązanie programistyczne MVC ASP.NET. Poniższe rozwiązanie opiera się na metodzie przedstawionej w dokumencie [Tworzenie aplikacji internetowych z wykorzystaniem ASP.NET 1.1 (2004)]. Chociaż ma ona tę zaletę, że wykorzystuje koncepcje powszechnie stosowane w programowaniu J2EE, należy ją jednak traktować wyłącznie jako to, czym jest – c.a.d – jedną z wielu metod programowania MVC. Gdy tylko metoda tworzenia oprogramowania MVC w ASP.NET zyska powszechną akceptację, należy ją wówczas przyjąć. Wersja .NET platformy Spring, obecnie w fazie rozwoju, może okazać się pierwszym takim rozwiązaniem.

1.2. Aplikacja webarticles

Przedstawiamy tutaj elementy uproszczonej aplikacji internetowej do handlu elektronicznego. Umożliwi ona użytkownikom sieci:

  • przeglądanie listy produktów pochodzących z bazy danych
  • dodawanie niektórych z nich do koszyka
  • zatwierdzenie koszyka. Jedynym skutkiem tego zatwierdzenia będzie aktualizacja stanów magazynowych zakupionych artykułów w bazie danych.

Użytkownikowi będą prezentowane następujące widoki:

- widok „LISTE”, zawierający listę produktów w sprzedaży
- widok „[INFOS]”, który zawiera dodatkowe informacje o produkcie:
  • widoki [PANIER] i [PANIERVIDE], które przedstawiają zawartość koszyka klienta
  • widok [ERREURS], który sygnalizuje wszelkie błędy aplikacji

Image

1.3. Ogólna architektura aplikacji

Chcemy stworzyć aplikację o następującej strukturze trójwarstwowej:

  • trzy warstwy są od siebie niezależne dzięki zastosowaniu interfejsów
  • integracja poszczególnych warstw odbywa się za pomocą biblioteki Spring
  • każda warstwa posiada oddzielną przestrzeń nazw: web (warstwa UI), domain (warstwa biznesowa) oraz dao (warstwa dostępu do danych).

Aplikacja będzie zgodna z architekturą MVC (Model – Widok – Kontroler). Jeśli powrócimy do powyższego schematu warstwowego, architektura MVC wpisuje się w niego w następujący sposób:

Przetwarzanie żądania klienta przebiega zgodnie z następującymi etapami:

  1. klient wysyła żądanie do kontrolera. Kontrolerem tym będzie w tym przypadku strona .aspx, której przypisano szczególną rolę. Przechodzą przez nią wszystkie żądania klientów. Stanowi ona bramę wejściową do aplikacji. Jest to litera C w skrócie MVC.
  1. kontroler przetwarza to żądanie. W tym celu może potrzebować pomocy warstwy biznesowej, zwanej modelem M w strukturze MVC.
  2. Kontroler otrzymuje odpowiedź od warstwy biznesowej. Żądanie klienta zostało przetworzone. Może to skutkować kilkoma możliwymi odpowiedziami. Klasycznym przykładem jest
    • strona błędów, jeśli żądania nie udało się poprawnie przetworzyć
    • w przeciwnym razie strona potwierdzenia
  3. kontroler wybiera odpowiedź (= widok), którą ma wysłać do klienta. Najczęściej jest to strona zawierająca elementy dynamiczne. Kontroler dostarcza je do widoku.
  4. Widok jest wysyłany do klienta. Jest to „V” w skrócie MVC.

1.4. Model

W tym miejscu analizujemy literę M z kodu MVC. Model składa się tutaj z następujących elementów:

  1. klasy biznesowe
  2. klasy dostępu do danych
  3. baza danych

1.4.1. Baza danych

Baza danych zawiera tylko jedną tabelę o nazwie ARTICLES. Została ona wygenerowana za pomocą następujących poleceń SQL:

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
);
/* ograniczenia */
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);
/* klucz główny */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
id
klucz podstawowy jednoznacznie identyfikujący artykuł
nom
nazwa artykułu
prix
jego cena
stockactuel
aktualny stan magazynowy
stockminimum
poziom zapasów, poniżej którego należy złożyć zamówienie na uzupełnienie zapasów

1.4.2. Przestrzenie nazw modelu

Szablon M jest tutaj przedstawiony w postaci dwóch przestrzeni nazw:

  • istia.st.articles.dao: zawiera klasy dostępu do danych warstwy [dao]
  • istia.st.articles.domain: zawiera klasy biznesowe warstwy [domain]

Każda z tych przestrzeni nazw zostanie wygenerowana w ramach własnego pliku „assembly”:

assembly
contenu
rôle
webarticles-dao
- [IArticlesDao]: interfejs dostępu do warstwy [dao].
Jest to jedyny interfejs widoczny dla warstwy [domain]. Nie widzi ona żadnego innego.
- [Article]: klasa definiująca artykuł
- [ArticlesDaoArrayList]: klasa implementująca
interfejsu [IArticlesDao] z klasą [ArrayList]
warstwy dostępu do danych
- znajduje się w całości w warstwie
[dao] w architekturze trójwarstwowej
aplikacji internetowej
webarticles-domain
- [IArticlesDomain]: interfejs dostępu do warstwy [domain]. Jest to jedyny interfejs widoczny dla warstwy internetowej. Nie widzi ona żadnego innego.
- [AchatsArticles]: klasa implementująca [IArticlesDomain]
- [Achat]: klasa reprezentująca zakup klienta
- [Panier]: klasa reprezentująca zbiór zakupów klienta
przedstawia model zakupów w
– znajduje się w całości w
warstwie [domain] architektury
trójwarstwowej architektury aplikacji internetowej

1.4.3. Warstwa [dao]

Warstwa [dao] zawiera następujące elementy:

  • [IArticlesDao]: interfejs dostępu do warstwy [dao]

  • [Article]: klasa definiująca artykuł

  • [ArticlesDaoArrayList]: klasa implementująca interfejs [IArticlesDao] z wykorzystaniem klasy [ArrayList]

Struktura projektu [Visual Studio] warstwy [dao] jest następująca:

Image

Uwagi:

  • projekt [dao] jest typu [bibliothèque de classes]
  • klasy zostały umieszczone w drzewie katalogów, którego katalogiem głównym jest folder [istia]. Wszystkie znajdują się w przestrzeni nazw [istia.st.articles.dao].

1.4.3.1. Klasa [Article]

Klasa definiująca artykuł ma następujący wygląd:

Imports System

Namespace istia.st.articles.dao

    Public Class Article

         ' pola prywatne
        Private _id As Integer
        Private _nom As String
        Private _prix As Double
        Private _stockactuel As Integer
        Private _stockminimum As Integer

         ' identyfikator artykułu
        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

         ' nazwa artykułu
        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

         ' cena artykułu
        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

         ' aktualny stan magazynowy artykułu
        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

         ' minimalny stan magazynowy artykułu
        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

         ' domyślny producent
        Public Sub New()
        End Sub

         ' producent z właściwościami
        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

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

Ta klasa oferuje:

  1. konstruktor umożliwiający ustawienie 5 informacji dotyczących artykułu: [id, nom, prix, stockactuel, stockminimum]
  2. publiczne właściwości umożliwiające odczyt i zapis tych 5 informacji.
  3. weryfikację danych wprowadzonych do artykułu. W przypadku błędnych danych generowany jest wyjątek.
  4. metodę toString, która pozwala uzyskać wartość artykułu w postaci ciągu znaków. Jest to często przydatne podczas debugowania aplikacji.

1.4.3.2. Interfejs [IArticlesDao]

Interfejs [IArticlesDao] jest zdefiniowany w następujący sposób:

Imports System
Imports System.Collections

Namespace istia.st.articles.dao

    Public Interface IArticlesDao
         ' lista wszystkich artykułów
        Function getAllArticles() As IList
         ' dodaje artykuł
        Function ajouteArticle(ByVal unArticle As Article) As Integer
         ' usuwa artykuł
        Function supprimeArticle(ByVal idArticle As Integer) As Integer
         ' modyfikuje artykuł
        Function modifieArticle(ByVal unArticle As Article) As Integer
         ' wyszukuje artykuł
        Function getArticleById(ByVal idArticle As Integer) As Article
         ' usuwa wszystkie artykuły
        Sub clearAllArticles()
         ' zmienia stan magazynowy artykułu
        Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
    End Interface
End Namespace

Rola poszczególnych metod interfejsu jest następująca:

getAllArticles
zwraca wszystkie artykuły z źródła danych
clearAllArticles
opróżnia źródło danych
getArticleById
zwraca obiekt [Article] identyfikowany przez jego klucz główny
ajouteArticle
umożliwia dodanie pozycji do źródła danych
modifieArticle
umożliwia modyfikację pozycji w źródle danych
supprimerArticle
umożliwia usunięcie pozycji ze źródła danych
changerStockArticle
umożliwia zmianę stanu magazynowego pozycji w źródle danych

Interfejs udostępnia programom klienckim pewną liczbę metod zdefiniowanych wyłącznie poprzez ich sygnatury. Nie zajmuje się tym, w jaki sposób metody te zostaną faktycznie zaimplementowane. Zapewnia to elastyczność aplikacji. Program kliencki wywołuje interfejs, a nie konkretną implementację tego interfejsu.

Wybór konkretnej implementacji nastąpi za pomocą pliku konfiguracyjnego Spring. Aby pokazać, że do testowania aplikacji internetowej liczy się wyłącznie interfejs dostępu do danych, a nie jego klasa implementacyjna, najpierw zaimplementujemy źródło danych za pomocą prostego obiektu [ArrayList]. W dalszej części przedstawimy rozwiązanie oparte na SGBD.

1.4.3.3. Klasa implementacyjna [ArticlesDaoArrayList]

Klasa implementacyjna [ArticlesDaoArrayList] jest zdefiniowana w następujący sposób:

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

         ' domyślny producent
        Public Sub New()
             ' produkujemy kilka artykułów
            For i As Integer = 1 To nbArticles
                articles.Add(New Article(i, "article" + i.ToString, i * 10, i * 10, i * 10))
            Next
        End Sub

         ' lista wszystkich artykułów
        Public Function getAllArticles() As IList Implements IArticlesDao.getAllArticles
             ' zwraca listę artykułów
            SyncLock Me
                Return articles
            End SyncLock
        End Function

         ' usunięcie wszystkich artykułów
        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
             ' czyści się listę artykułów
            SyncLock Me
                articles.Clear()
            End SyncLock
        End Sub

         ' pobieranie artykułu o określonym kluczu
        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
             ' wyszukiwanie pozycji na liście
            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

         ' dodaj pozycję do listy pozycji
        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
             ' dodajemy pozycję do listy pozycji
            SyncLock Me
                 ' sprawdzamy, czy artykuł nie istnieje już na liście
                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
                 ' dodaje się pozycję
                articles.Add(unArticle)
                 ' zwracamy wynik
                Return 1
            End SyncLock
        End Function

         ' modyfikacja artykułu
        Public Function modifieArticle(ByVal articleNouveau As Article) As Integer Implements IArticlesDao.modifieArticle
             ' modyfikuje się artykuł
            SyncLock Me
                 ' sprawdza się, czy istnieje
                Dim ipos As Integer = posArticle(articles, articleNouveau.id)
                 ' jeśli nie istnieje
                If ipos = -1 Then Return 0
                 ' istnieje – modyfikujemy go
                articles(ipos) = articleNouveau
                 ' zwracamy wynik
                Return 1
            End SyncLock
        End Function

         ' usunięcie artykułu zidentyfikowanego na podstawie klucza
        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
             ' usunięcie artykułu
            SyncLock Me
                 ' sprawdzamy, czy istnieje
                Dim ipos As Integer = posArticle(articles, idArticle)
                 ' w przypadku, gdy nie istnieje
                If ipos = -1 Then Return 0
                 ' artykuł istnieje – usuwamy go
                articles.RemoveAt(ipos)
                 ' zwracamy wynik
                Return 1
            End SyncLock
        End Function

         ' zmieniamy stan magazynowy artykułu zidentyfikowanego za pomocą klucza
        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
             ' zmiana stanu magazynowego artykułu
            SyncLock Me
                 ' sprawdzamy, czy istnieje
                Dim ipos As Integer = posArticle(articles, idArticle)
                 ' w przypadku, gdy nie istnieje
                If ipos = -1 Then Return 0
                 ' artykuł istnieje – modyfikuje się jego stan magazynowy, jeśli to możliwe
                Dim unArticle As Article = CType(articles(ipos), Article)
                 ' stan magazynowy zmieniamy tylko wtedy, gdy jest wystarczający
                If unArticle.stockactuel + mouvement >= 0 Then
                    unArticle.stockactuel += mouvement
                    Return 1
                Else
                    Return 0
                End If
            End SyncLock
        End Function

         ' wyszukaj artykuł identyfikowany za pomocą klucza
        Private Function posArticle(ByVal listArticles As ArrayList, ByVal idArticle As Integer) As Integer
             ' zwraca pozycję artykułu [idArticle] na liście lub -1, jeśli nie został znaleziony
            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
             ' nie znaleziono
            Return -1
        End Function
    End Class

End Namespace

Uwagi:

  • źródło danych jest symulowane przez pole prywatne [articles] typu [ArrayList]
  • konstruktor klasy domyślnie tworzy 4 pozycje w źródle danych.
  • wszystkie metody dostępu do danych zostały zsynchronizowane w celu uniknięcia problemów związanych z równoczesnym dostępem do źródła danych. W danym momencie tylko jeden wątek ma dostęp do danej metody.
  • Metoda [posArticle] pozwala ustalić pozycję [0..N] w źródle [ArrayList] dla pozycji zidentyfikowanej na podstawie jej numeru. Jeśli artykuł nie istnieje, metoda zwraca pozycję -1. Metoda ta jest wielokrotnie wykorzystywana przez inne metody.
  • Metoda [ajouteArticle] pozwala dodać artykuł do listy artykułów. Zwraca liczbę dodanych artykułów: 1. Jeśli artykuł już istniał, generowany jest wyjątek.
  • Metoda [modifieArticle] umożliwia modyfikację istniejącego artykułu. Zwraca liczbę zmodyfikowanych artykułów: 1, jeśli artykuł istniał, w przeciwnym razie 0.
  • Metoda [supprimeArticle] umożliwia usunięcie istniejącego artykułu. Zwraca liczbę usuniętych artykułów: 1, jeśli artykuł istniał, w przeciwnym razie 0.
  • Metoda [getAllArticles] zwraca listę wszystkich artykułów
  • Metoda [getArticleById] pozwala uzyskać artykuł identyfikowany na podstawie jego numeru. Jeśli artykuł nie istnieje, zwracana jest wartość [nothing].
  • Kod nie stanowi większej trudności. Pozostawiamy czytelnikowi jego przejrzenie i zrozumienie.

1.4.3.4. Generowanie zestawu (assembly) warstwy [dao]

Projekt Visual Studio jest skonfigurowany do generowania zestawu [webarticles-dao.dll]. Zestaw ten jest generowany w folderze [bin] projektu:

1.4.3.5. Testy NUnit warstwy [dao]

W Javie klasy są testowane przy użyciu frameworka [Junit]. W .NET framework Nunit oferuje te same możliwości testów jednostkowych:

Image

Struktura projektu testowego w Visual Studio wygląda następująco:

Image

Uwagi:

  • projekt [tests] jest typu [bibliothèque de classes]
  • testy [NUnit] wymagają odwołania do zestawu [nunit.framework.dll]
  • klasa testowa [NUnit] pobiera instancję testowanego obiektu za pośrednictwem Springa. Znajduje się więc
    • w folderze [bin] pliki klas Springa
    • w [References] – odwołanie do zestawu [Spring-Core.dll] z folderu [bin]
    • w [bin] – plik konfiguracyjny dla Springa
  • klasa testowa wymaga zestawu [webarticles-dao.dll] z warstwy [dao]. Został on umieszczony w folderze [bin], a jego odniesienie dodano do odniesień projektu.

Klasa testowa [NUnit] wymaga dostępu do klas z przestrzeni nazw [NUnit.Framework]. Znajduje się w niej zatem następująca instrukcja importu:

Imports NUnit.Framework

Przestrzeń nazw [NUnit.Framework] znajduje się w „assembly” [nunit.framework.dll], które należy dodać do odwołań projektu:

Zestaw [nunit.framework.dll] powinien znajdować się na wyświetlonej liście, jeśli zainstalowano już zestaw [Nunit]. Wystarczy dwukrotnie kliknąć na zestaw, aby dodać go do projektu:

Image

Klasa testowa [NUnit] z warstwy [dao] mogłaby wyglądać następująco:

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
         ' obiekt do testowania
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
             ' pobieramy instancję generatora obiektów Spring
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
             ' żądamy instancjonowania obiektu articlesdao
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
        End Sub

        <Test()> _
        Public Sub testGetAllArticles()
             ' weryfikacja wizualna
            listArticles()
        End Sub

        <Test()> _
        Public Sub testClearAllArticles()
             ' usuwamy wszystkie artykuły
            articlesDao.clearAllArticles()
             ' pobieramy wszystkie artykuły
            Dim articles As IList = articlesDao.getAllArticles
             ' sprawdzenie: powinno być ich 0
            Assert.AreEqual(0, articles.Count)
        End Sub

        <Test()> _
        Public Sub testAjouteArticle()
             ' usunięcie wszystkich pozycji
            articlesDao.clearAllArticles()
             ' weryfikacja: tabela artykułów musi być pusta
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' dodajemy dwa artykuły
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' sprawdzenie: musi być dwa artykuły
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' kontrola wizualna
            listArticles()
        End Sub

        <Test()> _
        Public Sub testSupprimeArticle()
             ' usunięcie wszystkich artykułów
            articlesDao.clearAllArticles()
             ' weryfikacja: tabela artykułów musi być pusta
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' dodajemy dwa artykuły
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' sprawdzenie: muszą być 2 pozycje
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' usuwamy pozycję nr 4
            articlesDao.supprimeArticle(4)
             ' sprawdzenie: musi pozostać 1 artykuł
            articles = articlesDao.getAllArticles
            Assert.AreEqual(1, articles.Count)
             ' kontrola wzrokowa
            listArticles()
        End Sub

        <Test()> _
        Public Sub testModifieArticle()
             ' usunięcie wszystkich pozycji
            articlesDao.clearAllArticles()
             ' weryfikacja
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' dodanie 2 artykułów
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' weryfikacja
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' wyszukiwanie pozycji 3
            Dim unArticle As Article = articlesDao.getArticleById(3)
             ' weryfikacja
            Assert.AreEqual(unArticle.nom, "article3")
             ' wyszukiwanie pozycji 4
            unArticle = articlesDao.getArticleById(4)
             ' weryfikacja
            Assert.AreEqual(unArticle.nom, "article4")
             ' modyfikacja artykułu 4
            articlesDao.modifieArticle(New Article(4, "article4", 44, 44, 44))
             ' weryfikacja
            unArticle = articlesDao.getArticleById(4)
            Assert.AreEqual(unArticle.prix, 44, 0.000001)
             ' kontrola wzrokowa
            listArticles()
        End Sub

        <Test()> _
        Public Sub testGetArticleById()
             ' usunięcie artykułów
            articlesDao.clearAllArticles()
             ' weryfikacja
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' dodanie 2 artykułów
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' weryfikacja
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' wyszukiwanie artykułu 3
            Dim unArticle As Article = articlesDao.getArticleById(3)
             ' weryfikacja
            Assert.AreEqual(unArticle.nom, "article3")
             ' wyszukiwanie artykułu 4
            unArticle = articlesDao.getArticleById(4)
             ' weryfikacja
            Assert.AreEqual(unArticle.nom, "article4")
        End Sub

         ' wyświetlenie listy
        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()
             ' usunięcie wszystkich artykułów
            articlesDao.clearAllArticles()
             ' wyszukiwanie pozycji 1
            Dim article As article = articlesDao.getArticleById(1)
             ' weryfikacja
            Assert.IsNull(article)
             ' modyfikacja nieistniejącej pozycji
            Dim i As Integer = articlesDao.modifieArticle(New article(1, "1", 1, 1, 1))
             ' nie zmodyfikowano żadnego wiersza
            Assert.AreEqual(i, 0)
             ' usunięcie nieistniejącego artykułu
            i = articlesDao.supprimeArticle(1)
             ' nie usunięto żadnego wiersza
            Assert.AreEqual(0, i)
        End Sub

        <Test()> _
        Public Sub testChangerStockArticle()
             ' usunięcie wszystkich artykułów
            articlesDao.clearAllArticles()
             ' dodanie artykułu
            Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
            Assert.AreEqual(nbArticles, 1)
             ' dodano artykuł
            nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            Assert.AreEqual(nbArticles, 1)
             ' utworzenie 100 wątków
            Dim taches(99) As Thread
            For i As Integer = 0 To taches.Length - 1
                 ' tworzymy wątek i
                taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
                 ' ustalanie nazwy wątku
                taches(i).Name = "tache_" & i
                 ' uruchamia się wątek i
                taches(i).Start()
            Next
             ' oczekiwanie na zakończenie wszystkich wątków
            For i As Integer = 0 To taches.Length - 1
                taches(i).Join()
            Next
             ' sprawdzenie – stan magazynowy artykułu 3 musi wynosić 1
            Dim unArticle As Article = articlesDao.getArticleById(3)
            Assert.AreEqual(unArticle.nom, "article3")
            Assert.AreEqual(1, unArticle.stockactuel)
             ' zmniejszamy stan magazynowy artykułu 4
            Dim erreur As Boolean = False
            Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
             ' sprawdzenie: jego stan magazynowy nie powinien ulec zmianie
            Assert.AreEqual(0, nbLignes)
             ' kontrola wizualna
            listArticles()
        End Sub

        Public Sub décrémente()
             ' uruchomiono wątek
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
             ' wątek zmniejsza stan magazynowy
            articlesDao.changerStockArticle(3, -1)
             ' wątek zakończony
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
        End Sub

    End Class
End Namespace

Uwagi:

  • chcieliśmy napisać program testujący interfejs [IArticlesDao], który byłby niezależny od klasy implementującej ten interfejs. W tym celu wykorzystaliśmy bibliotekę Spring, aby ukryć przed programem testującym nazwę klasy implementującej.
  • Metoda atrybutu <Setup()> pobiera z biblioteki Spring odwołanie do obiektu [articlesdao], który ma zostać przetestowany. Obiekt ten jest zdefiniowany w następującym pliku [spring-config.xml]:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">

<objects>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoArrayList, webarticles-dao"/>
</objects>

Ten plik podaje nazwę klasy implementacyjnej [istia.st.articles.dao.ArticlesDaoArrayList] dla interfejsu [IArticlesDao] oraz informację, gdzie można ją znaleźć: [ webarticles-dao.dll]. Ponieważ instancjonowanie nie wymaga żadnych parametrów, nie zdefiniowano ich tutaj.

  • Większość testów jest łatwa do zrozumienia. Zachęcamy czytelnika do zapoznania się z komentarzami.
  • Metoda [testChangerStockArticle] wymaga kilku wyjaśnień. Tworzy ona 100 wątków odpowiedzialnych za zmniejszanie stanu magazynowego danego artykułu.
        <Test()> _
        Public Sub testChangerStockArticle()
             ' usunięcie wszystkich artykułów
            articlesDao.clearAllArticles()
             ' dodanie pozycji
            Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
            Assert.AreEqual(nbArticles, 1)
             ' dodanie artykułu
            nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            Assert.AreEqual(nbArticles, 1)
             ' utworzenie 100 wątków
            Dim taches(99) As Thread
            For i As Integer = 0 To taches.Length - 1
                 ' tworzymy wątek i
                taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
                 ' ustalanie nazwy wątku
                taches(i).Name = "tache_" & i
                 ' uruchamia się wątek i
                taches(i).Start()
            Next
             ' oczekiwanie na zakończenie wszystkich wątków
            For i As Integer = 0 To taches.Length - 1
                taches(i).Join()
            Next
             ' sprawdzenie – artykuł 3 musi mieć stan magazynowy równy 1
            Dim unArticle As Article = articlesDao.getArticleById(3)
            Assert.AreEqual(unArticle.nom, "article3")
            Assert.AreEqual(1, unArticle.stockactuel)
             ' zmniejsza się stan magazynowy artykułu nr 4
            Dim erreur As Boolean = False
            Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
             ' weryfikacja: stan magazynowy tego artykułu nie powinien ulec zmianie
            Assert.AreEqual(0, nbLignes)
             ' kontrola wzrokowa
            listArticles()
        End Sub

Celem tego testu jest sprawdzenie współbieżnego dostępu do źródła danych. Metoda odpowiedzialna za aktualizację stanu zapasów wygląda następująco:

        Public Sub décrémente()
             ' uruchomiono wątek
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
             ' wątek zmniejsza stan magazynowy
            articlesDao.changerStockArticle(3, -1)
             ' wątek zakończony
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
        End Sub

Zmniejsza stan magazynowy artykułu nr 3 o jedną jednostkę. Jeśli spojrzymy na kod metody [testChangerStockArticle], widzimy, że:

  • stan magazynowy artykułu nr 3 jest inicjowany na 101
  • 100 wątków zmniejszy ten stan o jedną jednostkę każdy
  • w związku z tym po zakończeniu działania wszystkich wątków stan zapasów powinien wynosić 1

Ponadto, nadal w tej metodzie, próbujemy ustawić stan magazynowy artykułu nr 4 na wartość ujemną. Próba ta musi zakończyć się niepowodzeniem.

Aby przetestować warstwę [dao], generujemy pliki DLL i [tests-webarticles-dao.dll] w folderze [bin] projektu [tests]:

Następnie, korzystając z aplikacji [Nunit-Gui], wczytujemy ten plik DLL i przeprowadzamy testy:

Image

W lewym oknie widoczna jest lista testowanych metod. Kolor kropki przed nazwą każdej metody wskazuje, czy metoda zakończyła się powodzeniem (zielony) czy niepowodzeniem (czerwony). Czytelnik przeglądający ten dokument na ekranie będzie mógł zauważyć, że wszystkie testy zakończyły się powodzeniem. W dalszej części będziemy zakładać, że dysponujemy działającą warstwą [dao].

1.4.4. Warstwa [domain]

Warstwa [domain] zawiera następujące elementy:

  • [IArticlesDomain]: interfejs dostępu do warstwy [domain]

  • [Achat]: klasa definiująca zakup

  • [Panier]: klasa definiująca koszyk zakupów

  • [AchatsArticles]: klasa implementująca interfejs [IArticlesDomain]

Struktura rozwiązania [Visual Studio] warstwy [domain] jest następująca:

Image

Uwagi:

  • projekt [domain] jest typu [bibliothèque de classes]
  • klasy zostały umieszczone w drzewie katalogów, którego katalogiem głównym jest folder [istia]. Wszystkie znajdują się w przestrzeni nazw [istia.st.articles.domain].
  • Klasa DLL z warstwy [dao] została umieszczona w folderze [bin] nowego projektu. Ponadto klasa DLL została dodana do projektu jako odniesienie.

1.4.4.1. Interfejs [IArticlesDomain]

Interfejs [IArticlesDomain] oddziela warstwę [métier] od warstwy [web]. Ta ostatnia uzyskuje dostęp do warstwy [métier/domain] za pośrednictwem tego interfejsu, nie zwracając uwagi na klasę, która faktycznie go implementuje. Interfejs definiuje następujące akcje dotyczące dostępu do warstwy biznesowej:

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

Namespace istia.st.articles.domain
    Public Interface IArticlesDomain
         ' metody
        Sub acheter(ByVal panier As Panier)
        Function getAllArticles() As IList
        Function getArticleById(ByVal idArticle As Integer) As Article
        ReadOnly Property erreurs() As ArrayList
    End Interface
End Namespace
Function getAllArticles() As IList
zwraca listę obiektów [Article] z powiązanego źródła danych
Function getArticleById(ByVal idArticle As Integer) As Article
zwraca obiekt [Article] identyfikowany przez [idArticle]
acheter(ByVal panier As Panier)
zatwierdza koszyk klienta, zmniejszając stany magazynowe zakupionych artykułów o zakupioną ilość – może zakończyć się niepowodzeniem, jeśli stan magazynowy jest niewystarczający
ReadOnly Property erreurs() As ArrayList
zwraca listę wystąpionych błędów – pusta, jeśli nie ma błędów

1.4.4.2. Klasa [Achat]

Klasa [Achat] reprezentuje zakup dokonywany przez klienta:

Imports istia.st.articles.dao

Namespace istia.st.articles.domain

    Public Class Achat

         ' pola prywatne
        Private _article As article
        Private _qte As Integer

         ' konstruktor domyślny
        Public Sub New()
        End Sub

         ' konstruktor z parametrami
        Public Sub New(ByVal unArticle As article, ByVal qte As Integer)
             ' przechodzimy przez właściwości
            Me.article = unArticle
            Me.qte = qte
        End Sub

         ' zakupiony artykuł
        Public Property article() As article
            Get
                Return _article
            End Get
            Set(ByVal Value As article)
                _article = Value
            End Set
        End Property

         ' ilość zakupiona
        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

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

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

End Namespace

Uwagi:

  • klasa [Achat] posiada następujące właściwości i metody:
Public Property article() As article
zakupiony artykuł
Public Property qte() As Integer
zakupiona ilość
Public ReadOnly Property totalAchat() As Double
kwota zakupu
Public Overrides Function ToString() As String
ciąg identyfikacyjny obiektu
  • posiada konstruktor umożliwiający zainicjowanie właściwości [article, qte], które definiują zakup.

1.4.4.3. Klasa [Panier]

Klasa [Panier] reprezentuje zbiór zakupów klienta:

Namespace istia.st.articles.domain

Public Class Panier

     ' pola prywatne
    Private _achats As New ArrayList
        Private _totalPanier As Double = 0

     ' domyślny producent
    Public Sub New()
    End Sub

     ' lista zakupów
    Public ReadOnly Property achats() As ArrayList
        Get
            Return _achats
        End Get
    End Property

     ' suma zakupów
    Public ReadOnly Property totalPanier() As Double
        Get
            Return _totalPanier
        End Get
    End Property

     ' metody
    Public Sub ajouter(ByVal unAchat As Achat)
         ' sprawdzamy, czy zakup już istnieje
        Dim iAchat As Integer = posAchat(unAchat.article.id)
        If iAchat <> -1 Then
             ' znaleziono
                Dim achatCourant As Achat = CType(_achats(iAchat), Achat)
            achatCourant.qte += unAchat.qte
        Else
             ' nie znaleziono
            _achats.Add(unAchat)
        End If
         ' zwiększamy sumę w koszyku
        _totalPanier += unAchat.totalAchat
    End Sub

     ' usuwanie pozycji z koszyka
    Public Sub enlever(ByVal idAchat As Integer)
         ' szukamy produktu
            Dim iachat As Integer = posAchat(idAchat)
         ' jeśli znaleziono, usuwamy
        If iachat <> -1 Then
                Dim achatCourant As Achat = CType(_achats(iachat), Achat)
             ' usuwamy z koszyka
            _achats.RemoveAt(iachat)
             ' zmniejszamy sumę koszyka
            _totalPanier -= achatCourant.totalAchat
        End If
    End Sub

    Private Function posAchat(ByVal idArticle As Integer) As Integer
         ' wyszukuje zakup na liście zakupów
         ' zwraca pozycję na liście lub -1, jeśli nie znaleziono
        Dim achatCourant As Achat
        Dim trouvé As Boolean = False
        Dim i As Integer = 0
            While Not trouvé AndAlso i < _achats.Count
                 ' bieżący zakup
                achatCourant = CType(_achats(i), Achat)
                 ' porównanie z poszukiwanym produktem
                If achatCourant.article.id = idArticle Then
                    Return i
                End If
                 'następny zakup
                i += 1
            End While
             ' nie znaleziono
            Return -1
    End Function

     ' funkcja tożsamości
    Public Overrides Function ToString() As String
        Return _achats.ToString
    End Function
End Class

End Namespace

Uwagi:

  • klasa [Panier] posiada następujące właściwości i metody:
ReadOnly Property achats() As ArrayList
lista zakupów klienta – lista obiektów typu [Achat]
ajouter(ByVal unAchat As Achat)
dodaje zakup do listy zakupów
enlever(ByVal idAchat As Integer)
usuwa zakup artykułu idAchat
ReadOnly Property totalPanier() As Double
łączna kwota zakupów w koszyku
Function ToString() As String
zwraca ciąg identyfikacyjny koszyka
  • metoda [posAchat] jest metodą pomocniczą, która pozwala uzyskać pozycję na liście zakupów dla zakupu zidentyfikowanego na podstawie numeru zakupionego artykułu. Lista zakupów jest zarządzana w taki sposób, że artykuł zakupiony wielokrotnie zajmuje tylko jedną pozycję na liście. Dzięki temu zakup można zidentyfikować na podstawie numeru zakupionego artykułu. Metoda [posAchat] zwraca -1, jeśli szukany zakup nie istnieje.
  • Metoda [ajouter] dodaje nowy zakup do listy zakupów. Oznacza to albo dodanie nowego wpisu do listy zakupów, jeśli zakupiony artykuł nie występował jeszcze na liście, albo zwiększenie ilości zakupionego towaru, jeśli już tam występował.
  • Metoda [enlever] pozwala usunąć z listy zakupów pozycję oznaczoną numerem. Jeśli pozycja nie istnieje, metoda nie generuje żadnego komunikatu i nie wykonuje żadnej czynności.
  • Kwota zakupów [totalPanier] jest aktualizowana na bieżąco w miarę dodawania i usuwania zakupów.

1.4.4.4. Klasa [AchatsArticles]

Interfejs [IArticlesDomain] zostanie zaimplementowany przez następującą klasę [AchatsArticles]:

Imports istia.st.articles.dao

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

         'pola prywatne
        Private _articlesDao As IArticlesDao
        Private _erreurs As ArrayList

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

         ' lista błędów
        Public ReadOnly Property erreurs() As ArrayList Implements IArticlesDomain.erreurs
            Get
                Return _erreurs
            End Get
        End Property

         ' lista artykułów
        Public Function getAllArticles() As IList Implements IArticlesDomain.getAllArticles
             ' lista wszystkich artykułów
            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

         ' pobierz artykuł o określonym numerze
        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDomain.getArticleById
             ' konkretny artykuł
            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


         ' zakup koszyka
        Public Sub acheter(ByVal panier As Panier) Implements IArticlesDomain.acheter
             ' zakup koszyka – stany magazynowe zakupionych artykułów muszą zostać pomniejszone
            _erreurs = New ArrayList
            Dim achat As achat
            Dim achats As ArrayList = panier.achats
            For i As Integer = achats.Count - 1 To 0 Step -1
                 ' zmniejszyć stan magazynowy artykułu i
                achat = CType(achats(i), achat)
                Try
                    If _articlesDao.changerStockArticle(achat.article.id, -achat.qte) = 0 Then
                         ' nie udało się wykonać operacji
                        _erreurs.Add("L'achat " + achat.ToString + " n'a pu se faire - Vérifiez les stocks")
                    Else
                         ' operacja została wykonana – usunięto zakup z koszyka
                        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

Uwagi:

  • ta klasa implementuje cztery metody interfejsu [IArticlesDomain]. Posiada dwa pola prywatne:
_articlesDao As IArticlesDao
obiekt dostępu do danych
_erreurs As ArrayList
lista ewentualnych błędów. Dostęp do niej zapewnia publiczna właściwość [erreurs]
  • Aby utworzyć instancję tej klasy, należy podać obiekt umożliwiający dostęp do danych:
Sub New(ByVal articlesDao As IArticlesDao)
  • Metody [getAllArticles] i [getArticleById] opierają się na metodach o tej samej nazwie z warstwy [dao]
  • Metoda [acheter] zatwierdza zakup zawartości koszyka. Zatwierdzenie to polega po prostu na zmniejszeniu stanów magazynowych zakupionych artykułów. Zakup artykułu jest możliwy tylko wtedy, gdy pozwala na to stan magazynowy. Jeśli tak nie jest, zakup zostaje odrzucony: artykuł pozostaje w koszyku, a na liście [erreurs] zgłaszany jest błąd. Zatwierdzony zakup jest usuwany z koszyka, a stan magazynowy danego artykułu zmniejszany o zakupioną ilość.

1.4.4.5. Generowanie zestawu warstwy [domain]

Projekt Visual Studio jest skonfigurowany do generowania zestawu [webarticles-domain.dll]. Zestaw ten jest generowany w folderze [bin] projektu:

1.4.4.6. Testy NUnit warstwy [domain]

Struktura projektu testowego w Visual Studio wygląda następująco:

Image

Uwagi:

  • projekt [tests] jest typu [bibliothèque de classes]
  • testy [NUnit] wymagają odwołania do zestawu [nunit.framework.dll]
  • klasa testowa [NUnit] pobiera instancję testowanego obiektu za pośrednictwem Springa. Znajduje się więc
  • w folderze [bin] pliki klas Springa
  • w [References] – odwołanie do zestawu [Spring-Core.dll] z folderu [bin]
  • w [bin] – plik konfiguracyjny dla Springa
  • klasa testowa wymaga zestawu [webarticles-dao.dll] z warstwy [dao] oraz zestawu [webarticles-domain.dll] z warstwy [domain]. Zostały one umieszczone w folderze [bin], a ich odwołania dodano do odwołań projektu.

Klasa testowa NUnit z warstwy [domain] mogłaby wyglądać następująco:

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

         ' obiekt do testowania
        Private articlesDomain As IArticlesDomain
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
             ' pobieramy instancję generatora obiektów Spring
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
             ' żądamy instancji obiektu DAO typu „articles”
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
             ' a następnie obiektu „articlesdomain”
            articlesDomain = CType(factory.GetObject("articlesdomain"), IArticlesDomain)
        End Sub

        <Test()> _
        Public Sub getAllArticles()
             ' wizualna weryfikacja
            listArticles()
        End Sub

        <Test()> _
        Public Sub getArticleById()
             ' usuwanie artykułów
            articlesDao.clearAllArticles()
             ' weryfikacja
            Dim articles As IList = articlesDomain.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' dodanie 2 artykułów
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' weryfikacja
            articles = articlesDomain.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' wyszukiwanie artykułu 3
            Dim unArticle As Article = articlesDomain.getArticleById(3)
             ' weryfikacja
            Assert.AreEqual(unArticle.nom, "article3")
             ' wyszukiwanie artykułu 4
            unArticle = articlesDao.getArticleById(4)
             ' weryfikacja
            Assert.AreEqual(unArticle.nom, "article4")
        End Sub

        <Test()> _
        Public Sub acheterPanier()
             ' usunięcie artykułów
            articlesDao.clearAllArticles()
             ' weryfikacja
            Dim articles As IList = articlesDomain.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' dodanie 2 artykułów
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' weryfikacja
            articles = articlesDomain.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' utworzenie koszyka z dwoma produktami
            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))
             ' weryfikacje
            Assert.AreEqual(700, panier.totalPanier, 0.000001)
            Assert.AreEqual(2, panier.achats.Count)
             ' zatwierdzenie koszyka
            articlesDomain.acheter(panier)
             ' weryfikacje
            Assert.AreEqual(0, articlesDomain.erreurs.Count)
            Assert.AreEqual(0, panier.achats.Count)
             ' wyszukiwanie produktu 3
            Dim unArticle As Article = articlesDomain.getArticleById(3)
             ' weryfikacja
            Assert.AreEqual(unArticle.stockactuel, 20)
             ' wyszukiwanie produktu 4
            unArticle = articlesDao.getArticleById(4)
             ' weryfikacja
            Assert.AreEqual(unArticle.stockactuel, 30)
             ' nowy koszyk
            panier.ajouter(New Achat(New Article(3, "article3", 30, 30, 3), 100))
             ' potwierdzenie koszyka
            articlesDomain.acheter(panier)
             ' weryfikacje
            Assert.AreEqual(1, articlesDomain.erreurs.Count)
             ' wyszukiwanie produktu 3
            unArticle = articlesDomain.getArticleById(3)
             ' weryfikacja
            Assert.AreEqual(unArticle.stockactuel, 20)
        End Sub

        <Test()> _
        Public Sub testRetirerAchats()
             ' usunięcie zawartości z ARTICLES
            articlesDao.clearAllArticles()
             ' odczytuje tabelę ARTICLES
            Dim articles As IList = articlesDao.getAllArticles()
            Assert.AreEqual(0, articles.Count)
             ' wstawianie
            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)
             ' odczytuje tabelę ARTICLES
            articles = articlesDomain.getAllArticles()
            Assert.AreEqual(2, articles.Count)
             ' utworzenie koszyka z dwoma produktami
            Dim monPanier As New Panier
            monPanier.ajouter(New Achat(article3, 10))
            monPanier.ajouter(New Achat(article4, 10))
             ' weryfikacje
            Assert.AreEqual(700.0, monPanier.totalPanier, 0.000001)
            Assert.AreEqual(2, monPanier.achats.Count)
             ' dodanie już zakupionego produktu
            monPanier.ajouter(New Achat(article3, 10))
             ' weryfikacje
             ' suma musi zostać zmieniona na 1000
            Assert.AreEqual(1000.0, monPanier.totalPanier, 0.000001)
             ' w koszyku nadal znajdują się 2 produkty
            Assert.AreEqual(2, monPanier.achats.Count)
             ' ilość produktu 3 musi zostać zmieniona na 20
            Dim unAchat As Achat = CType(monPanier.achats(0), Achat)
            Assert.AreEqual(20, unAchat.qte)
             ' usuwamy produkt nr 3 z koszyka
            monPanier.enlever(3)
             ' sprawdzenie
             ' suma musi wynosić 400
            Assert.AreEqual(400.0, monPanier.totalPanier, 0.000001)
             ' W koszyku znajduje się tylko 1 pozycja
            Assert.AreEqual(1, monPanier.achats.Count)
             ' musi to być produkt nr 4
            Assert.AreEqual(4, CType(monPanier.achats(0), Achat).article.id)
        End Sub

         ' wyświetlanie na ekranie
        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

Komentarze:

  • chcieliśmy napisać program testujący interfejs [IArticlesDomain], który byłby niezależny od klasy implementującej ten interfejs. W tym celu wykorzystaliśmy bibliotekę Spring, aby ukryć przed programem testującym nazwę klasy implementującej.
  • Metoda atrybutu <Setup()> pobiera ze Springa odwołania do obiektów [articlesdomain] i [articlesdao], które mają zostać przetestowane. Obiekty te są zdefiniowane w poniższym pliku [spring-config.xml]:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoArrayList, webarticles-dao" />
    <object id="articlesdomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
        <constructor-arg index="0">
            <ref object="articlesdao" />
        </constructor-arg>
    </object>
</objects>

Ten plik zawiera:

  • (ciąg dalszy)
    • dla singletonu [articlesdao] – nazwę klasy implementacyjnej [istia.st.articles.dao.ArticlesDaoArrayList] oraz lokalizację, w której można ją znaleźć: [ webarticles-dao.dll]. Ponieważ instancjonowanie nie wymaga parametrów, nie zdefiniowano tu żadnych.
    • dla singletonu [articlesdomain] podano nazwę klasy implementacyjnej [istia.st.articles.domain.AchatsArticles] oraz lokalizację klasy [ webarticles-domain.dll]. Klasa [AchatsArticles] posiada konstruktor z jednym parametrem: singletonem zarządzającym dostępem do warstwy [dao]. W tym przypadku jest to singleton [articlesdao] zdefiniowany wcześniej.
  • Klasa testowa uzyskuje instancję klasy testowanej [articlesdomain], a także instancję klasy dostępu do danych [articlesdao]. Ta ostatnia kwestia budzi wątpliwości. Teoretycznie klasa testowa nie powinna potrzebować dostępu do warstwy [dao], o której nawet nie powinna wiedzieć. W tym przypadku zignorowaliśmy tę „etykę”, której przestrzeganie zmusiłoby nas do utworzenia nowych metod w naszym interfejsie [IArticlesDomain].

Aby przetestować warstwę [domain], generujemy pliki DLL i [tests-webarticles-domain.dll] w folderze [bin] projektu [tests]:

Następnie, korzystając z aplikacji [Nunit-Gui], wczytujemy ten plik DLL i przeprowadzamy testy:

Image

Użytkownik przeglądający ten dokument na ekranie zobaczy, że wszystkie testy zakończyły się powodzeniem. Następnie uznamy, że mamy gotową do użycia warstwę [domain].

1.4.5. Wniosek

Przypomnijmy, że chcemy zbudować następującą trójwarstwową aplikację internetową:

Model M naszej aplikacji MVC został już napisany i przetestowany. Jest on dostarczony w dwóch plikach: DLL i [webarticles-dao.dll, webarticles-domain.dll]. Możemy przejść do ostatniej warstwy, warstwy [web], która zawiera kontroler C oraz widoki V. Najpierw przyjrzymy się metodzie przedstawionej w dokumencie [Développement WEB avec ASP.NET 1.1 ]

  • kontroler C jest realizowany przez dwa pliki [global.asax, main.aspx]
  • widoki V są realizowane przez strony aspx

1.5. Warstwa [web]

Architektura aplikacji internetowej MVC będzie wyglądać następująco:

M=modèle
klasy biznesowe [domain], klasy dostępu do danych [dao] oraz źródło danych
V=vues
strony ASPX
C=contrôleur
wszystkie żądania klientów HTTP przechodzą przez następujące dwa kontrolery:
global.asax: obsługuje zdarzenia związane z pierwszym uruchomieniem aplikacji
main.aspx: indywidualnie przetwarza żądania poszczególnych klientów

1.5.1. Widoki

Widoki są takie same jak te przedstawione na początku dokumentu:

LISTE
liste.aspx
Widoki są zgromadzone w folderze [vues] aplikacji
INFOS
infos.aspx
PANIER
panier.aspx
PANIERVIDE
paniervide.aspx
ERREURS
erreurs.aspx

1.5.2. Kontrolery

Jak już wspomniano, kontroler będzie składał się z dwóch elementów:

  1. [global.asax,global.asax.vb]: służy głównie do zainicjowania aplikacji i umieszczenia w jej kontekście wszystkich danych, które mają być współdzielone między różnymi klientami
  2. [main.aspx, main.aspx.vb]: właściwy kontroler, który przetwarza żądania HTTP od klientów.

Różne żądania klientów będą kierowane do kontrolera [main.aspx] i będą zawierały parametr o nazwie [action], określający akcję żądaną przez klienta:

znaczenie
znaczenie
działanie kontrolera
możliwe odpowiedzi
action=liste
klient chce otrzymać listę
artykułów
– prosi o listę artykułów na poziomie
biznesowej
– [LISTE]
- [ERREURS]
action=infos
klient prosi o
informacji na temat jednego z
artykułów wyświetlanych w widoku
[LISTE]
– wysyła zapytanie o artykuł do warstwy biznesowej
- [INFOS]
- [ERREURS]
action=achat
klient kupuje artykuł
- wysyła zapytanie o produkt do warstwy biznesowej
i umieszcza go w koszyku klienta
- [INFOS] w przypadku błędu ilości
- [LISTE], jeśli nie ma błędu
action=retirerachat
klient chce usunąć
zakup z koszyka
- pobiera koszyk z sesji
i modyfikuje go
- [PANIER]
- [PANIERVIDE]
- [ERREURS]
action=panier
klient chce wyświetlić swój
koszyk
- pobiera koszyk z sesji
- [PANIER]
- [PANIERVIDE]
- [ERREURS]
action=validationpanier
klient zakończył zakupy
i przechodzi do etapu płatności
- aktualizuje stan magazynowy
zakupionych produktów
- usuwa z koszyka klienta produkty,
, których zakup został zatwierdzony
- [LISTE]
- [ERREURS]

1.5.3. Konfiguracja aplikacji

Postaramy się skonfigurować aplikację tak, aby była jak najbardziej elastyczna w obliczu zmian, takich jak:

  1. zmiana klas URL w poszczególnych widokach
  2. zmiana klas implementujących interfejsy [IArticlesDao] i [IArticlesDomain]
  3. zmiana SGBD, bazy danych oraz tabeli artykułów

1.5.3.1. Zmiany w URL

Nazwy widoków URL zostaną umieszczone w pliku konfiguracyjnym aplikacji [web.config] wraz z kilkoma innymi parametrami:

<?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. Zmiana klas implementujących interfejsy

Zgodnie z zasadami architektury trójwarstwowej warstwy muszą być od siebie odizolowane. Izolację tę osiąga się w następujący sposób:

  • warstwy komunikują się ze sobą za pośrednictwem interfejsów, a nie konkretnych klas
  • kod danej warstwy nigdy sam nie instancjonuje klasy z innej warstwy w celu jej wykorzystania. Po prostu zwraca się do narzędzia zewnętrznego, w tym przypadku Springa, o instancję implementacji interfejsu warstwy, z której chce skorzystać. W tym celu wiemy, że nie musi znać nazwy klasy implementującej, a jedynie nazwę singletonu Springa, do którego chce uzyskać odwołanie.

W naszej aplikacji Spring zostanie skonfigurowany w pliku [web.config] aplikacji internetowej w następujący sposób:

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

Aby uzyskać dostęp do warstwy [métier], klasa z warstwy [web] może zażądać singletonu [articlesDomain]. Spring utworzy wówczas instancję obiektu typu [istia.st.articles.domain.AchatsArticles]. Do tej instancji potrzebny jest obiekt typu [articlesDao], czyli obiekt typu [istia.st.articles.dao.ArticlesDaoArrayList]. Spring utworzy wówczas taki obiekt. Po zakończeniu operacji warstwa [web], która zażądała singletonu [articlesDomain], posiada cały łańcuch łączący ją ze źródłem danych:

1.5.3.3. Zmiany związane z SGBD lub bazą danych

Ten punkt zostanie tutaj pominięty, ponieważ mamy do czynienia z aplikacją testową bez warstwy SGBD. W kolejnym etapie omówimy implementację warstwy [dao] opartej na warstwie SGBD.

1.5.4. Biblioteka tagów <asp:>

Rozważmy widok [ERREURS], który wyświetla listę błędów:

Image

Widok [ERREURS] ma za zadanie wyświetlić listę błędów, którą kontroler [main.aspx] umieścił w kontekście żądania pod nazwą [context.Items("erreurs")]. Istnieje kilka sposobów napisania takiej strony. W tym miejscu skupiamy się wyłącznie na części odpowiedzialnej za wyświetlanie błędów.

Przypomnijmy, że strona ASPX składa się z części prezentacyjnej HTML oraz części kodowej .NET, która przygotowuje dane, które ma wyświetlić część prezentacyjna. Te dwie części mogą znajdować się w tym samym pliku [aspx] (rozwiązanie WebMatrix) lub w dwóch plikach: [aspx] dla części prezentacyjnej, [aspx.vb] dla kodu. To ostatnie rozwiązanie jest stosowane w programie Visual Studio. Aby jeszcze bardziej skomplikować sprawę, część prezentacyjna HTML może również zawierać kod .NET, co zniekształca widoczny podział na [contrôleur] i [présentation]. Zazwyczaj zdecydowanie odradza się stosowanie tego rozwiązania. Usunięcie całego kodu z części [présentation] wymagało utworzenia bibliotek znaczników. Biblioteki te „ukrywają” kod pod postacią znaczników analogicznych do znaczników HTML. Przedstawiamy dwa możliwe rozwiązania dla strony [ERREURS].

Nasze pierwsze rozwiązanie wykorzystuje kod .NET w części [présentation] strony. Strona ASPX pobiera listę błędów zawartych w żądaniu w swojej części kontrolera [erreurs.aspx.vb]:

        Protected erreurs As ArrayList

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
             ' pobieramy błędy
            erreurs = CType(context.Items("erreurs"), ArrayList)
        End Sub

a następnie wyświetla to w sekcji [présentation, erreurs.aspx]:

                <h2>Les erreurs suivantes se sont produites :</h2>
                <ul>
                <%
                    for i as integer=0 to erreurs.count-1
                        response.write("<li>" & erreurs(i).ToString & "</li>")
                    next
                %>
                </ul>

Drugie rozwiązanie wykorzystuje tag <asp:repeater> z biblioteki tagów <asp:> w ASP.NET. Jeśli tworzymy stronę ASPX graficznie, tag ten jest dostępny w postaci komponentu serwerowego, który umieszczamy na formularzu projektowym. Jeśli kod ASPX tworzymy ręcznie, możemy mówić o bibliotece tagów.

Dzięki bibliotece tagów <asp:> kod ASPX z poprzedniego widoku [ERREURS] przyjmuje następującą postać:

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

Tag

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

służy do powtórzenia wzorca HTML w różnych elementach źródła danych. Jego poszczególne elementy są następujące:

HeaderTemplate
motyw HTML, który ma być wyświetlany przed wyświetleniem elementów źródła danych
ItemTemplate
motyw HTML, który ma być powtarzany dla każdego elementu źródła danych. Wyrażenie [<%# Container.DataItem %>] służy do wyświetlenia wartości bieżącego elementu źródła danych
FooterTemplate
wzorzec HTML, który ma zostać wyświetlony po wyświetleniu elementów źródła danych

Źródło danych jest zazwyczaj powiązane z tagiem w części strony o nazwie [contrôleur]:

        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
..
             ' powiązanie błędów z rptErreurs
            With rptErreurs
                .DataSource = context.Items("erreurs")
                .DataBind()
            End With
        End Sub

Połączenie to można również nawiązać na etapie projektowania strony, jeśli źródło danych jest już znane, na przykład istniejąca baza danych.

W naszych widokach użyjemy innego tagu: <asp:datagrid>, który umożliwia wyświetlenie źródła danych w formie tabeli.

1.5.5. Struktura rozwiązania Visual Studio dla aplikacji [webarticles]

Aplikacja internetowa to układanka składająca się z wielu elementów. Nadanie jej architektury MVC zazwyczaj zwiększa ich liczbę. Struktura aplikacji [webarticles] w ramach [Visual Studio] wygląda następująco:

  

Uwagi:

  • projekt [web] jest typu [bibliothèque de classes], a nie typu [Application web ASP.NET], jak można by logicznie oczekiwać. Typ [Application web ASP.NET] wymaga obecności serwera WWW IIS na komputerze deweloperskim lub na komputerze zdalnym. Serwer IIS nie występuje standardowo na komputerach z systemem Windows XP Familial. Jednak wiele komputerów z systemem PC jest sprzedawanych właśnie z tą wersją. Aby umożliwić czytelnikom korzystającym z systemu Windows XP wdrożenie omawianej aplikacji, wykorzystamy serwer WWW Cassini (patrz załącznik), dostępny bezpłatnie w firmie Microsoft, i zastąpimy projekt [Application web ASP.NET] projektem [bibliothèque de classes]. Wiąże się to z pewnymi niedogodnościami, które wyjaśniono w załącznikach.
  • Pliki DLL wykorzystywane przez aplikację to:
webarticles-dao.dll
zawiera klasy warstwy dostępu do danych
webarticles-domain.dll
obejmuje klasy warstwy biznesowej
Spring.Core.dll
zawiera klasy Spring, które umożliwiają nam integrację warstw web, domain i DAO
log4net.dll
klasy logów – wykorzystywane przez Spring

Te pliki DLL umieszcza się w folderze [bin] i dodaje do referencji projektu.

1.5.6. Widoki ASPX

Zgodnie z wcześniejszymi zaleceniami w naszych widokach ASPX będziemy korzystać z biblioteki tagów <asp:>.

1.5.6.1. Komponent użytkownika [entete.ascx]

Aby zapewnić spójność poszczególnych widoków, będą one miały wspólny nagłówek, w którym wyświetlana jest nazwa aplikacji wraz z menu:

Menu jest dynamiczne i ustalane przez kontroler. Kontroler umieszcza w żądaniu przesyłanym do strony ASPX atrybut klucza „actions”, którego wartością jest tablica elementów typu Hashtable(). Każdy element tej tablicy jest słownikiem służącym do generowania opcji w menu nagłówka. Każdy słownik posiada dwa klucze:

  • href: adres URL powiązany z opcją menu
  • link: tekst menu

Nagłówek zostanie przekształcony w kontrolkę użytkownika. Kontrolka użytkownika hermetyzuje fragment strony (układ i powiązany kod) w komponent, który można następnie ponownie wykorzystać na innych stronach. W tym przypadku chcemy ponownie wykorzystać komponent [entete] w innych widokach aplikacji. Kod prezentacji będzie znajdował się w komponencie [entete.ascx], a powiązany kod sterujący w komponencie [entete.ascx.vb]. Kod prezentacji będzie wykorzystywał komponent <asp:repeater> do wyświetlenia tabeli opcji menu:

type
nom
rôle
1
repeater
rptMenu
źródło danych: tablica słowników
z dwoma kluczami: href, link
wyświetl opcje menu

Kod wyświetlający stronę będzie wyglądał następująco:

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

Komentarze:

  • Komponent [repeater] jest zdefiniowany w wierszach 6–14
  • każdy element źródła danych powiązanego z powtarzaczem jest słownikiem zawierającym dwa klucze: href – wiersz 9 oraz link – wiersz 10

Odpowiedni kod kontrolny będzie wyglądał następująco:

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())
                ' tabela akcji jest powiązana z odpowiednim komponentem
                With rptMenu
                    .DataSource = Value
                    .DataBind()
                End With
            End Set
        End Property
    End Class
End Namespace

Uwagi:

  • komponent typu [EnteteWebArticles] posiada publiczną właściwość [actions] z uprawnieniami tylko do zapisu – wiersz 7
  • właściwość ta pozwala powiązać z komponentem <asp:repeater> o nazwie [rptMenu] – wiersz 10 – tablicę opcji obliczoną przez kontroler aplikacji – wiersze 11–12.

Pozostałe widoki aplikacji będą korzystać z nagłówka zdefiniowanego przez [entete.ascx]. Na przykład strona [erreurs.aspx] będzie zawierać ten nagłówek za pomocą następującego kodu:

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

Komentarze:

  • Wiersz 1 określa, że tag <WA:entete> powinien być powiązany z komponentem zdefiniowanym w pliku [entete.ascx]. Atrybuty [TagPrefix] i [TagName] mogą być dowolne.
  • Po wykonaniu tej czynności wstawienie komponentu do kodu prezentacyjnego strony odbywa się za pomocą wiersza 9. Podczas wykonywania ten tag spowoduje włączenie do kodu strony ASPX, która go zawiera, kodu strony [entete.ascx]. Kod sterujący [erreurs.aspx.vb] zajmie się zainicjowaniem tego komponentu. Może to zrobić w następujący sposób:
    Public Class ErreursWebarticles
        Inherits System.Web.UI.Page

         ' elementy strony
...
        Protected WithEvents entete As New EnteteWebArticles

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

Uwagi:

  • w wierszu 6 tworzony jest obiekt typu [EnteteWebArticles], który jest typem tworzonego komponentu
  • w wierszu 11 inicjowana jest właściwość [actions] tego obiektu

1.5.6.2. Widok [liste.aspx]

1.5.6.2.1. Introduction

Ten widok wyświetla listę artykułów dostępnych w sprzedaży:

Jest ona wyświetlana po wysłaniu żądania /main?action=liste lub /main?action=validationpanier. Elementy żądania kontrolera są następujące:

actions
obiekt Hashtable() – tablica opcji menu
listarticles
ArrayList obiektów typu [Article]
message
obiekt String – komunikat do wyświetlenia na dole strony

Każdy link [Infos] z tabeli HTML artykułów ma URL w postaci [?action=infos&id=ID], gdzie ID jest polem idwyświetlanego artykułu.

1.5.6.2.2. Elementy strony
type
nom
rôle
1
element użytkownika
nagłówek
wyświetl nagłówek
2
DataGrid
DataGridArticles
3 – kolumna powiązana: nagłówek: Nazwa, pole: nazwa
4 – kolumna powiązana: nagłówek: Cena, pole: cena
5 – kolumna hipertekstowa: tekst: Informacje, pole URL: id,
format URL: /webarticles/main.aspx?action=infos&id={0}
wyświetl artykuły na sprzedaż
6
etykieta
lblMessage
wyświetl wiadomość

Przypomnijmy, jak należy postępować, aby zdefiniować te właściwości:

  • w programie Visual Studio należy zaznaczyć element [DataGrid], aby uzyskać dostęp do jego arkusza właściwości:

Image

  • używamy powyższego linku [Mise en forme automatique], aby zarządzać wyglądem wyświetlanej tabeli
  • oraz link [Générateur de propriétés], aby zarządzać jego zawartością
1.5.6.2.3. Kod prezentacji [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>

Uwagi:

  • wiersz 9 definiuje nagłówek strony
  • wiersze 12–24 definiują cechy [DataGrid]
  • wiersz 26 definiuje etykietę [lblMessage]
1.5.6.2.4. Kod kontrolera [liste.aspx.vb]
Imports System
Imports System.Collections
Imports System.Data
Imports istia.st.articles.dao

Namespace istia.st.articles.web

     ' zarządza stroną wyświetlającą listę artykułów
    Public Class ListeWebarticles
        Inherits System.Web.UI.Page

         ' komponenty strony
        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
             ' przygotowuje widok [liste] na podstawie informacji z kontekstu
             ' powiązanie opcji menu z rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
             ' pobieramy artykuły do tabeli danych
            Dim articles As ArrayList = CType(context.Items("articles"), ArrayList)
             ' powiązanie ich z komponentem [DataGrid] na stronie
            With DataGridArticles
                .DataSource = articles
                .DataBind()
            End With
             ' wyświetla się komunikat
            lblMessage.Text = context.Items("message").ToString
        End Sub
    End Class
End Namespace

Uwagi:

  • elementy strony pojawiają się w wierszach 13–15. Należy zauważyć, że konieczne było utworzenie obiektu [EnteteWebArticles] z operatorem [new], podczas gdy w przypadku pozostałych elementów nie było to konieczne. Bez tego jawnego utworzenia pojawiał się błąd wykonania wskazujący, że obiekt [entete] nie odnosił się do niczego. Kwestia ta zasługiwałaby na dokładniejsze zbadanie. Nie została jednak zbadana.
  • tabela opcji menu nagłówka jest pobierana z kontekstu w celu zainicjowania komponentu [entete] strony – wiersz 20
  • lista artykułów jest pobierana z kontekstu – wiersz 22
  • w celu zainicjowania komponentu [DataGridArticles] – wiersze 24–27
  • komponent [lblMessage] jest inicjowany za pomocą komunikatu umieszczonego w kontekście – wiersz 29

1.5.6.3. Widok [infos.aspx]

1.5.6.3.1. Introduction

Ten widok wyświetla informacje o produkcie i umożliwia jego zakup:

Image

Jest wyświetlany po wysłaniu żądania /main?action=infos&id=ID lub żądania /main?action=achat&id=ID, gdy ilość zakupiona jest nieprawidłowa. Elementy żądania kontrolera są następujące:

actions
obiekt Hashtable() – tablica opcji menu
article
obiekt typu [Article] – artykuł do wyświetlenia
msg
obiekt typu String – komunikat do wyświetlenia w przypadku błędu dotyczącego ilości
qte
obiekt typu String – wartość do wyświetlenia w polu wprowadzania danych [Qte]

Pola [msg] i [qte] są używane w przypadku błędu wprowadzenia ilości:

Image

Ta strona zawiera formularz, który jest wysyłany za pomocą przycisku [Acheter]. Docelowym polem URL dla pola POST jest [?action=achat&id=ID], gdzie ID to identyfikator zakupionego artykułu.

1.5.6.3.2. Elementy strony
type
nom
rôle
1
element użytkownika
nagłówek
wyświetl nagłówek
2
literał
litID
wyświetl numer artykułu
3 à 6
DataGrid
DataGridArticle
3 – kolumna powiązana: nagłówek: Nazwa, pole: nazwa
4 – kolumna powiązana: nagłówek: Cena, pole: cena
5 – kolumna powiązana: nagłówek: Aktualny stan magazynowy, pole: stockActuel
6 – kolumna powiązana: nagłówek: Minimalny stan magazynowy, pole: stockMinimum
wyświetl pozycję
7
HTML Prześlij
 
wyślij formularz
8
HTML Input
runat=server
txtQte
wprowadź ilość zakupioną
9
etykieta
lblMsgQte
ewentualny komunikat o błędzie
1.5.6.3.3. Kod prezentacji [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>

Uwagi:

  • nagłówek jest zawarty na stronie – wiersz 9
  • literał [litId] jest zdefiniowany w wierszu 10
  • DataGrid i [DataGridArticles] są zdefiniowane w wierszach 12–34
  • formularz jest zdefiniowany w wierszach 36–46. Ma on typ POST.
  • Cel dla POST jest podawany przez zmienną [strAction] – wiersz 36. Zmienna ta musi zostać zdefiniowana przez kontrolera.
  • Pole wprowadzania zakupionej ilości jest zdefiniowane w wierszu 41. Jest to komponent serwerowy HTML (runat=server). W kodzie dostęp do niego uzyskuje się za pośrednictwem obiektu.
  • W wierszu 42 zdefiniowano etykietę [lblMsgQte], która będzie zawierać ewentualny komunikat o błędzie dotyczący wprowadzonej ilości
1.5.6.3.4. Kod kontrolny [infos.aspx.vb]
Imports istia.st.articles.dao
Imports System
Imports System.Collections

Namespace istia.st.articles.web

     ' obsługuje stronę z informacjami o artykule
    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

         ' URL, gdzie zostanie przesłany formularz
        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

         ' wyświetlanie strony
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' pobierane są informacje z zapytania
            Dim unArticle As Article = CType(Session.Item("article"), Article)
             ' powiązanie opcji menu z rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
             ' powiązanie artykułu z [DataGrid]
            Dim articles As New ArrayList
            articles.Add(unArticle)
            With DataGridArticle
                .DataSource = articles
                .DataBind()
            End With
             ' identyfikator etykiety
            litId.Text = unArticle.id.ToString
             ' komunikat o błędzie
            lblMsgQte.Text = context.Items("msg").ToString
             ' poprzednia ilość
            txtQte.Value = context.Items("qte").ToString
             ' działanie URL
            strAction = "?action=achat&id=" + unArticle.id.ToString
        End Sub
End Namespace

Komentarze:

  • elementy strony są zdefiniowane w wierszach 10–14
  • klasa definiuje publiczną właściwość [strAction], która służy do określenia miejsca docelowego pola POST w formularzu – wiersze 17–25
  • wyświetlany artykuł jest pobierany z kontekstu aplikacji – wiersz 30
  • tabela opcji menu nagłówkowego jest pobierana z kontekstu w celu zainicjowania komponentu [entete] strony – wiersz 32
  • w wierszach 33–39 komponent [DataGridArticle] jest powiązany ze źródłem danych typu [ArrayList], zawierającym wyłącznie artykuł pobrany w wierszu 30
  • komponenty [lblMsgQte, txtQte] są inicjowane na podstawie informacji pobranych z kontekstu – wiersze 42–45
  • właściwość [straction] jest również inicjowana na podstawie informacji pobranych z kontekstu – wiersz 47. Zmienna ta służy do wygenerowania atrybutu [action] formularza HTML znajdującego się na stronie:
        <form method="post" action="<%=strAction%>">
....
        </form>

1.5.6.4. Widok [panier.aspx]

1.5.6.4.1. Introduction

Ten widok wyświetla zawartość koszyka:

Image

Jest on wyświetlany po wysłaniu żądania /main?action=koszyk lub /main?action=usunąćzakup&id=ID. Elementy żądania kontrolera są następujące:

actions
Obiekt Hashtable() – tabela opcji menu
panier
obiekt typu [Panier] – koszyk do wyświetlenia

Każdy link [Retirer] z tabeli HTML zawierającej zakupy w koszyku posiada URL o postaci [?action=retirerachat&id=ID], gdzie ID to pole [id] artykułu, który chcemy usunąć z koszyka.

1.5.6.4.2. Elementy strony
type
nom
rôle
1
element użytkownika
nagłówek
wyświetl nagłówek
2
DataGrid
DataGridAchats
3 – kolumna powiązana – nagłówek: Artykuł, pole: nazwa
4 – kolumna powiązana – nagłówek: Ilość, pole: ilość
5 – kolumna powiązana – nagłówek: Cena, pole: cena
6 – kolumna powiązana – nagłówek: Suma, pole: suma, formatowanie {0:C}
7 – kolumna hipertekstowa – Tekst: Usuń, URL: id, format URL: /webarticles/main.aspx?action=retirerachat&id={0}
wyświetl listę zakupionych artykułów
8
etykieta
lblTotal
wyświetl kwotę do zapłaty
1.5.6.4.3. Kod prezentacji [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>

Uwagi

  • wiersz 9 zawiera nagłówek
  • w wierszach 12–27 zdefiniowano komponent [DataGridAchats]
  • w wierszu 29 zdefiniowano komponent [lblTotal]
1.5.6.4.4. Kod kontrolny [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
     ' zarządza stroną wyświetlania koszyka
    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
             ' powiązanie opcji menu z rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
             ' pobieranie koszyka
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' przenosimy zakupy do tabeli wierszy zakupów
            Dim achats(unPanier.achats.Count - 1) As LigneAchat
             ' zmiana typu elementów z ArrayList
            For i As Integer = 0 To achats.Length - 1
                achats(i) = New LigneAchat(CType(unPanier.achats(i), Achat))
            Next
             ' powiązanie danych z komponentami strony [DataGrid]
            With DataGridAchats
                .DataSource = achats
                .DataBind()
            End With
             ' wyświetla się suma do zapłaty
            lblTotal.Text = unPanier.totalPanier.ToString
        End Sub

         ' pozycja zakupu utworzona na podstawie obiektu „Zakup”
        Private Class LigneAchat
            Inherits Achat

             ' konstruktor otrzymuje zakup
            Public Sub New(ByVal unAchat As Achat)
                Me.article = unAchat.article
                Me.qte = unAchat.qte
            End Sub

             ' id: zwraca identyfikator zakupionego artykułu
            Public ReadOnly Property id() As Integer
                Get
                    Return article.id
                End Get
            End Property

             ' nazwa: nazwa zakupionego artykułu
            Public ReadOnly Property nom() As String
                Get
                    Return article.nom
                End Get
            End Property

             ' cena zakupionego artykułu
            Public ReadOnly Property prix() As Double
                Get
                    Return article.prix
                End Get
            End Property

        End Class
    End Class
End Namespace

Uwagi:

  • komponenty strony są zadeklarowane w wierszach 11–13
  • inicjalizacja komponentu [entete] jest identyczna jak w stronach omówionych wcześniej – wiersz 17
  • koszyk, który ma zostać wyświetlony, jest pobierany z sesji – wiersz 19
  • wyświetlenie tego koszyka za pomocą komponentu [DataGridAchats] sprawia problemy. Trudność wynika z inicjalizacji komponentu. Przypomnijmy jego kolumny:
    • kolumna [Article] powiązana z polem [nom] w źródle danych
    • kolumna [Qté] powiązana z polem [qte] w źródle danych
    • kolumna [Prix] powiązana z polem [prix] w źródle danych
    • kolumna [Total] powiązana z polem [total] w źródle danych

Źródłem danych, którym dysponujemy, jest koszyk wraz z listą zakupów. Ta ostatnia będzie źródłem danych dla [DataGrid]. Jedynie obiekty [Achat], które będą zasilać wiersze obiektu [DataGrid], nie posiadają właściwości [nom, qte, prix, total] oczekiwanych przez obiekt [DataGrid]. Dlatego też tworzymy tutaj, specjalnie dla obiektu [DataGrid], źródło danych, którego elementy posiadają cechy oczekiwane przez obiekt [DataGrid]. Elementy te będą typu [LigneAchat] – klasy utworzonej specjalnie na tę okazję i wywodzącej się z klasy [Achat] – wiersze 36–66

  • po zdefiniowaniu klasy [LigneAchat] źródło danych klasy [DataGridAchats] jest tworzone na podstawie koszyka znalezionego w sesji – wiersze 20–30
  • kwota zakupów jest wyświetlana dzięki właściwości [totalPanier] klasy [Panier] – wiersz 32

1.5.6.5. Widok [paniervide.aspx]

1.5.6.5.1. Introduction

Ten widok wyświetla informację, że koszyk jest pusty:

Image

Jest on wyświetlany po wysłaniu żądania /main?action=koszyk lub /main?action=usunąćzakup&id=ID. Elementy żądania kontrolera są następujące:

actions
obiekt Hashtable() – tablica opcji menu
1.5.6.5.2. Elementy strony
type
nom
rôle
1
element użytkownika
nagłówek
wyświetl nagłówek
1.5.6.5.3. Kod prezentacji [paniervide.aspx]
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx"%>
<%@ Page codebehind="paniervide.aspx.vb" inherits="istia.st.articles.web.PaniervideWebarticles" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h2>Contenu de votre panier</h2>
        <P>Votre panier est vide</P>
    </body>
</HTML>

Uwagi:

  • nagłówek znajduje się w wierszu 9
1.5.6.5.4. Kod kontrolny [paniervide.aspx.vb]
Namespace istia.st.articles.web
     ' obsługuje stronę wyświetlającą pusty koszyk
    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
             ' przygotowuje widok [paniervide] na podstawie informacji z kontekstu
             ' powiązanie opcji menu z rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
        End Sub
    End Class
End Namespace

Uwagi:

  • wystarczy zainicjować jedyny dynamiczny komponent strony – wiersz 10

1.5.6.6. Widok [erreurs.aspx]

1.5.6.6.1. Introduction

Ten widok jest wyświetlany w przypadku wystąpienia błędów:

Image

Jest ona wyświetlana po każdym zapytaniu zakończonym błędem, z wyjątkiem operacji zakupu z błędną ilością, która jest obsługiwana przez widok [INFOS]. Elementy zapytania kontrolera są następujące:

actions
obiekt Hashtable() – tablica opcji menu
erreurs
ArrayList obiektów [String] reprezentujących komunikaty o błędach do wyświetlenia
1.5.6.6.2. Elementy składowe strony
type
nom
rôle
1
element użytkownika
nagłówek
wyświetl nagłówek
2
powtarzacz
rptErreurs
wyświetl listę błędów
1.5.6.6.3. Kod prezentacji [erreurs.aspx]
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx" %>
<%@ Page codebehind="erreurs.aspx.vb" inherits="istia.st.articles.web.ErreursWebarticles" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h3>Les erreurs suivantes se sont produites :
        </h3>
        <ul>
            <asp:Repeater id="rptErreurs" runat="server">
                <ItemTemplate>
                    <li>
                        <%# Container.DataItem %>
                    </li>
                </ItemTemplate>
            </asp:Repeater></ul>
    </body>
</HTML>

Uwagi:

  • nagłówek jest zdefiniowany w wierszu 9
  • komponent [rptErreurs] jest zdefiniowany w wierszach 13–19. Jego zawartość pochodzi ze źródła danych typu [ArrayList], zawierającego obiekty typu [String].
1.5.6.6.4. Kod kontrolny [erreurs.aspx.vb]
Namespace istia.st.articles.web

     ' obsługuje stronę błędów
    Public Class ErreursWebarticles
        Inherits System.Web.UI.Page

         ' komponenty strony
        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
             ' przygotowuje się widok [erreurs] na podstawie informacji z kontekstu
             ' powiązanie opcji menu z rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
             ' powiązanie błędów z rptErreurs
            With rptErreurs
                .DataSource = context.Items("erreurs")
                .DataBind()
            End With
        End Sub
    End Class

End Namespace

Uwagi:

  • komponent [entete] jest inicjowany w zwykły sposób, wiersze 9 i 14
  • komponent [rptErreurs] jest inicjowany z listą błędów typu [ArrayList] znalezioną w kontekście – wiersze 16–19

1.5.7. Kontrolery global.asax, main.aspx

Pozostaje jeszcze napisanie rdzenia naszej aplikacji internetowej, czyli kontrolera. Jego rolą jest:

  • odbioru żądania od klienta,
  • przetworzenie żądanej przez niego akcji przy pomocy klas biznesowych,
  • wysłanie w odpowiedzi odpowiedniego widoku.

1.5.7.1. Kontroler [global.asax.vb]

Gdy aplikacja odbiera swoje pierwsze żądanie, uruchamiana jest procedura [Application_Start] z pliku [global.asax.vb]. Stanie się to tylko raz. Celem procedury [Application_Start] jest zainicjowanie obiektów niezbędnych dla aplikacji internetowej, które będą współdzielone w trybie tylko do odczytu przez wszystkie wątki klienckie. Te współdzielone obiekty mogą być umieszczone w dwóch miejscach:

  • w polach prywatnych kontrolera
  • kontekście wykonania aplikacji (Application)

Metoda [Application_Start] aplikacji [global.asax.vb] wykona następujące czynności:

  • sprawdzi, czy w pliku [web.config] znajdują się parametry niezbędne do prawidłowego działania aplikacji. Zostały one opisane w punkcie 1.5.3.
  • umieści w kontekście aplikacji listę ewentualnych błędów w postaci obiektu [ArrayList erreurs]. Lista ta będzie pusta, jeśli nie ma błędów, ale mimo to będzie istnieć.
  • jeśli wystąpiły błędy, metoda [Application_Start] kończy się w tym miejscu. W przeciwnym razie żąda ona odwołania do singletonu typu [IArticlesDomain], który będzie obiektem biznesowym wykorzystywanym przez kontroler do swoich potrzeb. Jak wyjaśniono w punkcie 1.5.3.2, kontroler zażąda tego singletonu od frameworka Spring. Ta operacja instancjonowania może spowodować różne błędy. Jeśli tak się stanie, zostaną one ponownie zapisane w obiekcie [erreurs] w kontekście aplikacji.

Kontroler [global.asax.vb] posiada procedurę [Session_Start], która jest wykonywana za każdym razem, gdy pojawia się nowy klient. W tej procedurze tworzony jest pusty koszyk dla klienta. Koszyk ten będzie utrzymywany na bieżąco w trakcie kolejnych żądań tego konkretnego klienta. Kod mógłby wyglądać następująco:

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

         ' uruchamiamy aplikację
        Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)

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

             ' pobieranie parametrów inicjalizacji aplikacji
            Dim param As String
            For i As Integer = 0 To parameters.Length - 1
                 ' odczyt z pliku konfiguracyjnego
                param = ConfigurationSettings.AppSettings(parameters(i))
                If param Is Nothing Then
                     ' odnotowuje się błąd
                    erreurs.Add("Paramètre [" + parameters(i) + "] absent dans le fichier [web.config]")
                Else
                     ' zapisywanie parametru w aplikacji
                    Application.Item(parameters(i)) = param
                End If
            Next
             ' czy wystąpiły błędy?
            If erreurs.Count = 0 Then
                 ' tworzy się obiekt IArticlesDomain umożliwiający dostęp do warstwy biznesowej
                Dim contexte As IApplicationContext = CType(ConfigurationSettings.GetConfig("spring/context"), IApplicationContext)
                Dim articlesDomain As IArticlesDomain
                Try
                    articlesDomain = CType(contexte.GetObject("articlesDomain"), IArticlesDomain)
                     ' obiekt jest zapisywany w aplikacji
                    Application.Item("articlesDomain") = articlesDomain
                Catch ex As Exception
                     ' błąd jest zapisywany
                    erreurs.Add("Erreur lors de la construction de l'objet d'accès à la couche métier [" + ex.ToString + "]")
                End Try
            End If
             ' błędy są umieszczane w aplikacji
            Application.Item("erreurs") = erreurs
             ' proces kończy się, jeśli wystąpiły błędy
            If erreurs.Count <> 0 Then Return
             ' tworzy się tablicę opcji menu
            Dim options As New Hashtable
             ' pobieramy URL z kontrolera
            Dim urlMain As String = CType(Application.Item("urlMain"), String)
            Dim uneOption As Hashtable
             ' lista produktów
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=liste")
            uneOption.Add("lien", "Liste des articles")
            options.Add("liste", uneOption)
             ' koszyk
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=panier")
            uneOption.Add("lien", "Voir le panier")
            options.Add("panier", uneOption)
             ' potwierdzenie zawartości koszyka
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=validationpanier")
            uneOption.Add("lien", "Valider le panier")
            options.Add("validationpanier", uneOption)
             ' wprowadzamy opcje menu do aplikacji
            Application.Item("options") = options
            Return
        End Sub

         ' inicjowanie sesji
        Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
             ' tworzymy koszyk dla klienta
            Session.Item("panier") = New Panier
        End Sub
    End Class
End Namespace

Uwagi:

  • parametry oczekiwane w procedurze [web.config] są zdefiniowane w tablicy – wiersz 18
  • są one wyszukiwane w [web.config]. Jeśli występują, są zapisywane w kontekście aplikacji, w przeciwnym razie błąd jest rejestrowany na liście błędów [erreurs] – wiersze 21–33
  • jeśli nie ma błędów, zwraca się do Springa o odwołanie do singletonu [articlesDomain], który zarządza dostępem do warstwy [domain] aplikacji – wiersze 35–47. Ewentualne błędy są rejestrowane w [erreurs].
  • Błędy są rejestrowane w kontekście aplikacji – wiersz 49
  • w przypadku wystąpienia błędów procedura zostaje zakończona – wiersz 51
  • tworzy się tablicę składającą się z trzech słowników. Każdy z nich ma dwa klucze: href i link. Tablica ta reprezentuje trzy możliwe opcje menu – wiersze 52–71
  • tablica ta jest zapisywana w kontekście aplikacji – wiersz 73
  • dla każdego nowego klienta uruchamiana jest procedura [Session_Start]. Tworzy się w niej pusty koszyk w sesji klienta – wiersze 78–81

1.5.7.2. Kontroler [main.aspx.vb]

Kontroler [main.aspx.vb] obsługuje wszystkie żądania klientów. Wszystkie mają one bowiem postać [/webarticles/main.aspx?action=XX]. Żądanie jest przetwarzane w następujący sposób:

  • sprawdzany jest obiekt [erreurs] z kontekstu aplikacji. Jeśli nie jest pusty, oznacza to, że podczas inicjalizacji aplikacji wystąpiły błędy i aplikacja nie może działać. W odpowiedzi wysyłany jest wówczas widok [ERREURS].
  • parametr [action] z żądania zostanie pobrany i sprawdzony. Jeśli nie odpowiada on żadnej znanej akcji, wysyłany jest widok [ERREURS] wraz z odpowiednim komunikatem o błędzie.
  • Jeśli parametr [action] jest prawidłowy, żądanie klienta jest przekazywane do procedury specyficznej dla danej akcji w celu przetworzenia:
metoda
żądanie
przetwarzanie
możliwe odpowiedzi
doListe
GET /main?action=liste
- wyświetlić listę artykułów
od klasy biznesowej
- wyświetlić ją
[LISTE] lub [ERREURS]
doInfos
GET /main?action=infos&id=ID
- poproś o artykuł o identyfikatorze id=ID w
klasy biznesowej
- wyświetlić go
[INFOS] lub [ERREURS]
doAchat
POST /main?action=zakup&id=ID
- ilość zakupiona jest częścią przesłanych parametrów
- należy zwrócić się o artykuł o identyfikatorze id=ID do
klasy biznesowej
- dodaj go do koszyka w
sesji klienta
[LISTE] lub [INFOS] lub [ERREURS]
doRetirerAchat
GET /main?action=retirerachat&id=ID
- usunąć produkt o identyfikatorze id=ID z
listy zakupów w koszyku
sesji klienta
[PANIER]
doPanier
GET /main?action=koszyk
- wyświetl koszyk z
sesji klienta
[PANIER] lub [PANIERVIDE]
doValidationPanier
GET /main?action=validationpanier
- zmniejszyć w bazie danych
stanów magazynowych wszystkich artykułów
znajdujących się w koszyku
sesji klienta
[LISTE] lub [ERREURS]

Szkielet kontrolera [main.aspx.vb] mógłby wyglądać następująco:

Imports System.Collections
Imports System
Imports System.Data

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

Namespace istia.st.articles.web

     ' klasa kontrolera aplikacji internetowej
    Public Class MainWebArticles
        Inherits System.Web.UI.Page

         ' pola prywatne
        Private articlesDomain As IArticlesDomain
        Private options As Hashtable

         ' ładowanie strony
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
....
        End Sub

         ' metody obsługi akcji

         ' lista artykułów
        Public Sub doListe()
...
        End Sub

         ' informacje o artykule
        Public Sub doInfos()
...
        End Sub

         ' zakup produktu
        Public Sub doAchat()
...
        End Sub

         ' usunięcie zakupu
        Public Sub doRetirerAchat()
...
        End Sub

         ' wyświetlenie koszyka
        Public Sub doPanier()
...
        End Sub

         ' zakup zawartości koszyka
        Public Sub doValidationPanier()
...
        End Sub

    End Class

End Namespace

Uwagi:

  • klasa posiada dwa pola prywatne, które będą współdzielone przez metody – wiersze 15–16:
    • articlesDomain: singleton zapewniający dostęp do warstwy [domain]
    • opcje: tablica słowników opcji menu
  • procedura [Page_Load]:
    • zainicjuje dwa pola prywatne klasy
    • pobierze parametr [action] z zapytania i uruchomi metodę przetwarzającą tę akcję.

1.5.7.3. Metoda [Page_Load]

To zdarzenie jest pierwszym, które ma miejsce na stronie. Kod wygląda następująco:

         ' ładowanie strony
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' sprawdzamy, czy aplikacja uruchomiła się poprawnie
            Dim erreurs As ArrayList = CType(Application.Item("erreurs"), ArrayList)
             ' jeśli wystąpią błędy, wyświetla się widok [erreurs]
            If erreurs.Count <> 0 Then
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {}
                Server.Transfer(CType(Application("urlErreurs"), String))
            End If
             ' pobierany jest obiekt dostępu do klasy biznesowej
            articlesDomain = CType(Application.Item("articlesDomain"), IArticlesDomain)
             ' oraz opcje menu
            options = CType(Application.Item("options"), Hashtable)
             ' pobieramy akcję do wykonania
            Dim action As String = Request.QueryString("action")
            If action Is Nothing Then
                action = "liste"
            End If
             ' wykonuje się działanie
            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

Uwagi:

  • przy każdym ładowaniu strony sprawdzamy, czy inicjalizacja aplikacji przeprowadzona przez metodę [global.asax] przebiegła pomyślnie.
  • W tym celu pobieramy z kontekstu aplikacji listę błędów umieszczoną tam przez [global.asax] – wiersz 4
  • jeśli lista ta nie jest pusta, wyświetla się widok [ERREURS] – wiersze 6–10
  • pobieramy singleton [articlesDomain] umieszczony przez [global.asax] w kontekście aplikacji i zapisujemy go w polu prywatnym [articlesDomain], aby był dostępny dla różnych metod klasy – wiersz 12
  • wykonujemy podobną operację z tablicą opcji menu – wiersz 14
  • pobieramy parametr [action] z żądania – wiersz 16
  • wywołujemy metodę odpowiadającą żądanej akcji. Akcja nieprzewidziana jest traktowana jako akcja [liste] – wiersze 16–36

1.5.7.4. Obsługa akcji [liste]

Chodzi o wyświetlenie listy artykułów:

Image

Kod jest następujący:

         ' lista artykułów
        Public Sub doListe()
             ' obsługa błędów
            Dim erreurs As New ArrayList
             ' żądanie listy artykułów
            Dim articles As IList
            Try
                articles = articlesDomain.getAllArticles
            Catch ex As Exception
                 ' odnotowuje się błąd
                erreurs.Add(ex.ToString)
            End Try
             ' czy występują błędy?
            If erreurs.Count <> 0 Then
                 ' wyświetlanie widoku [erreurs]
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
            End If
             ' wyświetlanie widoku [liste]
            context.Items("articles") = articles
            context.Items("options") = New Hashtable() {CType(options("panier"), Hashtable)}
            If context.Items("message") Is Nothing Then context.Items("message") = ""
            Server.Transfer(CType(Application.Item("urlListe"), String))
        End Sub

Uwagi:

  • ewentualne błędy umieszcza się w pliku [ArrayList] – wiersz 4
  • lista artykułów jest pobierana z singletonu [articlesDomain] – wiersze 5–12
  • jeśli wystąpiły błędy, wysyłany jest widok [ERREURS] – wiersze 13–19
  • w przeciwnym razie wysyłany jest widok [LISTE] – wiersze 20–24

1.5.7.5. Przetwarzanie akcji [infos]

Klient poprosił o informacje dotyczące danego artykułu:

Image

Kod jest następujący:

         ' informacje o produkcie
        Public Sub doInfos()
             ' obsługa błędów
            Dim erreurs As New ArrayList
             ' pobieranie identyfikatora żądanego artykułu
            Dim strId As String = Request.QueryString("id")
             ' czy coś się znalazło?
            If strId Is Nothing Then
                 ' nieprawidłowy wynik – wyświetla się strona błędów
                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
             ' czy mamy liczbę całkowitą?
            Dim id As Integer
            Try
                id = Integer.Parse(strId)
            Catch ex As Exception
                 ' nieprawidłowe – wysyłamy stronę błędów
                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
             ' żądany jest wpis o identyfikatorze id
            Dim unArticle As Article
            Try
                unArticle = articlesDomain.getArticleById(id)
            Catch ex As Exception
                 ' problem z dostępem do danych
                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
             ' czy pobrano pozycję?
            If unArticle Is Nothing Then
                 ' artykuł nie istnieje
                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
             ' artykuł został znaleziony – dodajemy go do bieżącej sesji
            Session.Item("article") = unArticle
             ' przygotowuje się jego wyświetlenie
            context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
            context.Items("msg") = ""
            context.Items("qte") = ""
            Server.Transfer(CType(Application.Item("urlInfos"), String))
        End Sub

Uwagi:

  • ewentualne błędy umieszczamy w [ArrayList] – wiersz 4
  • identyfikator żądanego artykułu jest pobierany z zapytania – wiersz 6
  • identyfikator ten jest sprawdzany. Musi być obecny i musi to być liczba całkowita. Jeśli tak nie jest, wysyłany jest widok [ERREURS] wraz z odpowiednim komunikatem o błędzie – wiersze 7–27
  • po zweryfikowaniu identyfikatora artykuł jest pobierany z singletonu [articlesDomain]. Jeśli wystąpi wyjątek, wysyłany jest widok [ERREURS] – wiersze 29–39
  • jeśli artykuł nie został znaleziony, wysyłany jest widok [ERREURS] – wiersze 41–48
  • jeśli artykuł został znaleziony, jest on umieszczany w sesji użytkownika, a następnie wyświetlany w widoku [INFOS] – wiersze 50–56

1.5.7.6. Przetwarzanie akcji [achat]

Klient kupił artykuł wyświetlony w widoku [INFOS].

Image

Kod wygląda następująco:

         ' zakup artykułu
        Public Sub doAchat()
             ' zakup artykułu
             ' pobieramy artykuł, który znajdował się w sesji
            Dim unArticle As Article = CType(Session.Item("article"), Article)
             ' czy mamy coś?
            If unArticle Is Nothing Then
                 ' nie jest to normalne – wyświetla się lista produktów
                doListe()
            End If
             ' pobieramy ilość zadaną
            Dim strQte As String = Request.Form("txtQte")
             ' czy jest coś?
            If strQte Is Nothing Then
                 ' nieprawidłowo – wysyłamy listę artykułów
                doListe()
            End If
             ' czy mamy liczbę całkowitą?
            Dim qte As Integer
            Try
                qte = Integer.Parse(strQte)
                If (qte <= 0) Then Throw New Exception
            Catch ex As Exception
                 ' nieprawidłowe – wysyłamy stronę informacyjną z komunikatem o błędzie
                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
             ' wszystko w porządku – dodajemy zakup do koszyka klienta
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
            unPanier.ajouter(New Achat(unArticle, qte))
             ' wyświetla się lista produktów
            doListe()
        End Sub

Uwagi:

  • artykuł zapisany w sesji jest pobierany – wiersz 5
  • jeśli go tam nie ma (sesja mogła wygasnąć), wyświetlany jest widok [LISTE] – wiersze 7–10
  • ilość zakupiona jest pobierana z zapytania – wiersz 12
  • sprawdzana jest jego ważność – wiersze 13–29
  • w przypadku nieprawidłowości, w zależności od sytuacji, wysyłany jest widok [LISTE] – wiersz 16 lub widok [INFOS] – wiersze 24–28
  • jeśli wszystko jest w porządku, zakup jest zapisywany w koszyku – wiersze 31–32
  • następnie wysyłany jest widok [LISTE] – wiersz 34

1.5.7.7. Przetwarzanie akcji [panier]

Klient dokonał kilku zakupów i prosi o wyświetlenie koszyka:

Image

Kod wygląda następująco:

         ' wyświetlanie koszyka
        Public Sub doPanier()
             'pobieramy koszyk z sesji
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' koszyk pusty?
            If unPanier.achats.Count = 0 Then
                 ' wyświetlanie pustego koszyka
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlPanierVide"), String))
            End If
             ' wyświetlanie koszyka z produktami
            context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable), CType(options("validationpanier"), Hashtable)}
            Server.Transfer(CType(Application.Item("urlPanier"), String))
        End Sub

Uwagi:

  • koszyk pobieramy z sesji – wiersz 4. Nie sprawdzamy tutaj, czy faktycznie coś otrzymaliśmy. Należałoby to zrobić, ponieważ sesja mogła wygasnąć.
  • Jeśli koszyk jest pusty, wysyłamy widok [PANIERVIDE] – wiersze 6–10
  • w przeciwnym razie wysyłamy widok [PANIER] – wiersze 11–14

1.5.7.8. Przetwarzanie akcji [retirerachat]

Klient chce usunąć produkt z koszyka:

Image

Kod jest następujący:

         ' usunięcie zakupu
        Public Sub doRetirerAchat()
             'pobieranie koszyka z sesji
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' usuwanie produktu
            Try
                 ' pobieranie identyfikatora usuniętego produktu
                Dim idArticle As Integer = Integer.Parse(Request.QueryString("id"))
                 ' usuwanie go z koszyka
                unPanier.enlever(idArticle)
            Catch ex As Exception
                 ' wyświetla się lista produktów
                doListe()
            End Try
             ' wyświetla się koszyk
            doPanier()
        End Sub

Uwagi:

  • pobieramy zawartość koszyka z sesji – wiersz 4. Nie sprawdzamy tutaj, czy faktycznie coś udało się pobrać. Należałoby to zrobić, ponieważ sesja mogła wygasnąć.
  • W zapytaniu pobieramy identyfikator [id] produktu, który ma zostać usunięty – wiersz 8.
  • Odpowiedni zakup jest usuwany z koszyka – wiersz 10
  • W tym miejscu nie sprawdzono poprawności identyfikatora zakupionego produktu. Jeśli ma on nieprawidłowy typ, wystąpi wyjątek, który zostanie obsłużony w wierszach 11–13. Jeśli jest poprawny, ale produkt nie istnieje, metoda [panier.enlever] – wiersz 10 – nie wykonuje żadnej czynności.
  • wyświetlany jest nowy koszyk – wiersz 16

1.5.7.9. Przetwarzanie akcji [validerpanier]

Klient chce zatwierdzić swój koszyk:

Image

Kod wygląda następująco:

         ' zakup z koszyka
        Public Sub doValidationPanier()
             ' na początku nie ma błędów
            Dim erreurs As ArrayList
             'pobieranie koszyka z sesji
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' próba zatwierdzenia koszyka
            Try
                articlesDomain.acheter(unPanier)
                 ' odnotowuje się ewentualne błędy zakupów
                erreurs = articlesDomain.erreurs
            Catch ex As Exception
                 ' odnotowuje się błąd
                erreurs = New ArrayList
                erreurs.Add(String.Format("Erreur lors de la validation du panier [{0}]", ex.Message))
            End Try
             ' jeśli występują błędy, wyświetla się strona błędów
            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
             ' wszystko w porządku – wyświetla się lista produktów wraz z komunikatem o pomyślnym zakończeniu
            context.Items("message") = "Votre panier a été validé"
            doListe()
        End Sub

Uwagi:

  • koszyk pobieramy z sesji – wiersz 6. Nie sprawdzamy tutaj, czy faktycznie coś otrzymaliśmy. Należałoby to zrobić, ponieważ sesja mogła wygasnąć.
  • W przypadku wygaśnięcia sesji będziemy mieli wskaźnik [nothing] dla koszyka, a metoda [acheter] – wiersz 9 – wygeneruje wyjątek i zostanie wysłany widok [ERREURS]. Jednak komunikat o błędzie będzie mało zrozumiały dla użytkownika.
  • W wierszach 8–16 próbujemy zweryfikować koszyk pobrany z sesji. Niektóre zakupy mogą nie zostać zatwierdzone, jeśli zamówiona ilość przekracza stan magazynowy danego artykułu. Przypadki te są zapisywane przez metodę [acheter] na liście błędów, która jest pobierana w wierszu 11.
  • Jeśli wystąpią błędy, wysyłany jest widok [ERREURS] – wiersze 18–23
  • w przeciwnym razie wysyłany jest widok [LISTE] – wiersze 25–26

1.6. Conclusion

Opracowaliśmy tutaj aplikację zgodnie z wzorcem MVC. Wydaje się, że (w kwietniu 2005 r.) nie istnieją profesjonalne „frameworki” do tworzenia aplikacji w języku MVC, podobne do tych dostępnych w Javie (Struts, Spring, ...). Projekt [Spring.net] powinien wkrótce zaproponować taki framework. W oczekiwaniu na to wydarzenie powyższa metoda umożliwia realne tworzenie aplikacji MVC o średniej wielkości.