Skip to content

1. Parte 1

Il codice PDF del documento è disponibile |QUI|.

Gli esempi del documento sono disponibili |QUI|.

1.1. Introduction

Obiettivi dell’articolo:

  • scrivere un'applicazione web a 3 livelli [interface utilisateur, métier, accès aux données]
  • configurare l'applicazione con Spring IOC
  • scrivere diverse versioni modificando l'implementazione di uno o più dei tre livelli.

Strumenti utilizzati:

  • Visual Studio.net per lo sviluppo - si veda l'allegato, paragrafo 3.1;
  • server web Cassini per l'esecuzione - cfr. allegato, paragrafo 3.2;
  • Nunit per i test unitari – cfr. allegato, paragrafo 3.4;
  • Spring per l’integrazione e la configurazione dei livelli dell’applicazione web – si veda l’allegato, paragrafo 3.3;

In una scala che va da principiante a intermedio ad avanzato, questo documento si colloca nella parte [intermédiaire-avancé]. La sua comprensione richiede diversi prerequisiti. Alcuni di essi possono essere acquisiti nei documenti che ho scritto. In tal caso, li cito. È ovvio che si tratta solo di un suggerimento e che il lettore può utilizzare i propri documenti preferiti.

Questo documento riprende il filo conduttore di un documento scritto per Java [Architetture a tre livelli e architetture MVC con Struts, Spring e Java (2005)]. In VB.NET realizziamo l'applicazione web MVC a tre livelli scritta in Java. L’idea che vogliamo trasmettere qui è che le piattaforme di sviluppo Java e .NET sono sufficientemente simili tra loro da consentire il riutilizzo delle competenze acquisite in uno di questi due ambiti nell’altro.

Non sembra esistere una soluzione di sviluppo MVC ASP.NET ampiamente riconosciuta. La soluzione che segue riprende il metodo introdotto nel documento [Sviluppo Web con ASP.NET 1.1 (2004)]. Sebbene questa soluzione abbia il pregio di utilizzare concetti diffusi nello sviluppo J2EE, va tuttavia considerata per quello che è, c.a.d: uno dei tanti metodi di sviluppo MVC. Non appena un metodo di sviluppo MVC in ASP.NET sarà ampiamente accettato, sarà necessario adottare quest’ultimo. La versione .NET di Spring, attualmente in fase di sviluppo, potrebbe benissimo rappresentare una prima soluzione.

1.2. L'applicazione webarticles

Presentiamo qui gli elementi di un'applicazione web semplificata di e-commerce. Questa consentirà agli utenti del web di:

  • di consultare un elenco di articoli provenienti da un database
  • di inserirne alcuni in un carrello elettronico
  • confermarlo. Tale conferma avrà come unico effetto l’aggiornamento, nel database, delle scorte degli articoli acquistati.

Le diverse viste presentate all’utente saranno le seguenti:

- la vista "LISTE", che presenta un elenco degli articoli in vendita
- la vista [INFOS] che fornisce informazioni aggiuntive su un prodotto:
  • le viste [PANIER] e [PANIERVIDE] che mostrano il contenuto del carrello del cliente
  • la vista [ERREURS] che segnala eventuali errori dell’applicazione

Image

1.3. Architettura generale dell'applicazione

Si desidera realizzare un'applicazione con la seguente struttura a tre livelli:

  • i tre livelli sono resi indipendenti grazie all'utilizzo di interfacce
  • l'integrazione dei diversi livelli è realizzata da Spring
  • Ogni livello è associato a spazi dei nomi distinti: web (livello UI), domain (livello business) e dao (livello di accesso ai dati).

L’applicazione seguirà un’architettura MVC (Modello - Vista - Controller). Se riprendiamo lo schema a livelli sopra riportato, l’architettura MVC si integra in esso nel modo seguente:

L'elaborazione di una richiesta da parte di un cliente si svolge secondo le seguenti fasi:

  1. il cliente invia una richiesta al controller. In questo caso, il controller sarà una pagina .aspx a cui verrà assegnato un ruolo specifico. Essa riceve tutte le richieste dei clienti. È la porta d’ingresso dell’applicazione. È la C di MVC.
  1. il controller elabora questa richiesta. A tal fine, potrebbe aver bisogno dell’aiuto del livello di business, ovvero il modello M nella struttura MVC.
  2. Il controller riceve una risposta dal livello di business. La richiesta del cliente è stata elaborata. Ciò può comportare diverse possibili risposte. Un esempio classico è
    • una pagina di errore se la richiesta non è stata elaborata correttamente
    • una pagina di conferma in caso contrario
  3. il controller sceglie la risposta (= vista) da inviare al cliente. Si tratta, nella maggior parte dei casi, di una pagina contenente elementi dinamici. Il controller fornisce tali elementi alla vista.
  4. La vista viene inviata al cliente. È la V di MVC.

1.4. Il modello

Qui esaminiamo la M di MVC. Il modello è costituito dai seguenti elementi:

  1. le classi di business
  2. le classi di accesso ai dati
  3. il database

1.4.1. Il database

Il database contiene una sola tabella denominata ARTICLES. Questa è stata generata con i seguenti comandi 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
);
/* vincoli */
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);
/* chiave primaria */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
id
chiave primaria che identifica in modo univoco un articolo
nom
nome dell'articolo
prix
il suo prezzo
stockactuel
disponibilità attuale
stockminimum
la quantità di magazzino al di sotto della quale è necessario effettuare un ordine di rifornimento

1.4.2. Gli spazi dei nomi del modello

Il modello M è qui fornito sotto forma di due spazi dei nomi:

  • istia.st.articles.dao: contiene le classi di accesso ai dati del livello [dao]
  • istia.st.articles.domain: contiene le classi di business del livello [domain]

Ciascuno di questi spazi dei nomi verrà generato all’interno di un file “assembly” a esso dedicato:

assembly
contenu
rôle
webarticles-dao
- [IArticlesDao]: l'interfaccia di accesso al livello [dao].
È l'unica interfaccia che il livello [domain] vede. Non ne vede altre.
- [Article]: classe che definisce un articolo
- [ArticlesDaoArrayList]: classe di implementazione dell’
l’interfaccia [IArticlesDao] con una classe [ArrayList]
livello di accesso ai dati
- si trova interamente nel livello
[dao] dell'architettura a tre livelli dell'
dell’applicazione web
webarticles-domain
- [IArticlesDomain]: l'interfaccia di accesso al livello [domain]. È l'unica interfaccia visibile al livello web. Non ne vede altre.
- [AchatsArticles]: una classe che implementa [IArticlesDomain]
- [Achat]: classe che rappresenta l'acquisto di un cliente
- [Panier]: classe che rappresenta l'insieme degli acquisti di un cliente
rappresenta il modello degli acquisti sul
web - si trova interamente nel
livello [domain] dell’architettura
Struttura a 3 livelli dell'applicazione web

1.4.3. Il livello [dao]

Il livello [dao] contiene i seguenti elementi:

  • [IArticlesDao]: l'interfaccia di accesso al livello [dao]

  • [Article]: classe che definisce un articolo

  • [ArticlesDaoArrayList]: classe di implementazione dell'interfaccia [IArticlesDao] con una classe [ArrayList]

La struttura del progetto [Visual Studio] del livello [dao] è la seguente:

Image

Commenti:

  • il progetto [dao] è di tipo [bibliothèque de classes]
  • le classi sono state inserite in una struttura ad albero con radice nella cartella [istia]. Si trovano tutte nello spazio dei nomi [istia.st.articles.dao].

1.4.3.1. La classe [Article]

La classe che definisce un articolo è la seguente:

Imports System

Namespace istia.st.articles.dao

    Public Class Article

         ' campi privati
        Private _id As Integer
        Private _nom As String
        Private _prix As Double
        Private _stockactuel As Integer
        Private _stockminimum As Integer

         ' ID articolo
        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

         ' nome articolo
        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

         ' prezzo articolo
        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

         ' stock attuale articolo
        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

         ' scorta minima articolo
        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

         ' produttore predefinito
        Public Sub New()
        End Sub

         ' produttore con proprietà
        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

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

Questa classe offre:

  1. un costruttore che consente di impostare le 5 informazioni relative a un articolo: [id, nom, prix, stockactuel, stockminimum]
  2. proprietà pubbliche che consentono di leggere e scrivere le 5 informazioni.
  3. una verifica dei dati inseriti nell'articolo. In caso di dati errati, viene generata un'eccezione.
  4. un metodo toString che consente di ottenere il valore di un articolo sotto forma di stringa di caratteri. Ciò è spesso utile per il debug di un'applicazione.

1.4.3.2. L'interfaccia [IArticlesDao]

L’interfaccia [IArticlesDao] è definita come segue:

Imports System
Imports System.Collections

Namespace istia.st.articles.dao

    Public Interface IArticlesDao
         ' elenco di tutti gli articoli
        Function getAllArticles() As IList
         ' aggiunge un articolo
        Function ajouteArticle(ByVal unArticle As Article) As Integer
         ' elimina un articolo
        Function supprimeArticle(ByVal idArticle As Integer) As Integer
         ' modifica un articolo
        Function modifieArticle(ByVal unArticle As Article) As Integer
         ' cerca un articolo
        Function getArticleById(ByVal idArticle As Integer) As Article
         ' elimina tutti gli articoli
        Sub clearAllArticles()
         ' modifica le scorte di un articolo
        Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
    End Interface
End Namespace

Il ruolo dei diversi metodi dell'interfaccia è il seguente:

getAllArticles
restituisce tutti gli articoli presenti nella fonte dati
clearAllArticles
svuota la fonte dati
getArticleById
restituisce l'oggetto [Article] identificato dalla sua chiave primaria
ajouteArticle
consente di aggiungere un articolo alla fonte dati
modifieArticle
consente di modificare un articolo della fonte dati
supprimerArticle
consente di eliminare un articolo dalla fonte dati
changerStockArticle
consente di modificare lo stock di un articolo nella fonte dati

L'interfaccia mette a disposizione dei programmi client una serie di metodi definiti esclusivamente dalle loro firme. Non si occupa del modo in cui tali metodi saranno effettivamente implementati. Ciò garantisce flessibilità all'applicazione. Il programma client effettua le chiamate su un'interfaccia e non su una sua specifica implementazione.

La scelta di una specifica implementazione avverrà tramite un file di configurazione Spring. Per dimostrare che, ai fini del test dell’applicazione web, conta solo l’interfaccia di accesso ai dati e non la sua classe di implementazione, implementeremo innanzitutto la fonte di dati tramite un semplice oggetto [ArrayList]. Successivamente, presenteremo una soluzione basata su SGBD.

1.4.3.3. La classe di implementazione [ArticlesDaoArrayList]

La classe di implementazione [ArticlesDaoArrayList] è definita come segue:

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

         ' produttore predefinito
        Public Sub New()
             ' si producono alcuni articoli
            For i As Integer = 1 To nbArticles
                articles.Add(New Article(i, "article" + i.ToString, i * 10, i * 10, i * 10))
            Next
        End Sub

         ' elenco di tutti gli articoli
        Public Function getAllArticles() As IList Implements IArticlesDao.getAllArticles
             ' restituisce l'elenco degli articoli
            SyncLock Me
                Return articles
            End SyncLock
        End Function

         ' eliminazione di tutti gli articoli
        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
             ' si svuota l'elenco degli articoli
            SyncLock Me
                articles.Clear()
            End SyncLock
        End Sub

         ' recupera un articolo identificato dalla sua chiave
        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
             ' si sta cercando l'articolo nell'elenco
            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

         ' aggiungere un articolo all'elenco degli articoli
        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
             ' si aggiunge l'articolo all'elenco degli articoli
            SyncLock Me
                 ' si verifica che non esista già
                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
                 ' si aggiunge l'articolo
                articles.Add(unArticle)
                 ' si restituisce il risultato
                Return 1
            End SyncLock
        End Function

         ' modificare un articolo
        Public Function modifieArticle(ByVal articleNouveau As Article) As Integer Implements IArticlesDao.modifieArticle
             ' si modifica un articolo
            SyncLock Me
                 ' si verifica che esista
                Dim ipos As Integer = posArticle(articles, articleNouveau.id)
                 ' nel caso in cui non esista
                If ipos = -1 Then Return 0
                 ' esiste - lo si modifica
                articles(ipos) = articleNouveau
                 ' si restituisce il risultato
                Return 1
            End SyncLock
        End Function

         ' eliminare un articolo identificato dalla sua chiave
        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
             ' eliminazione di un articolo
            SyncLock Me
                 ' si verifica se esiste
                Dim ipos As Integer = posArticle(articles, idArticle)
                 ' nel caso in cui non esista
                If ipos = -1 Then Return 0
                 ' esiste - lo si elimina
                articles.RemoveAt(ipos)
                 ' si restituisce il risultato
                Return 1
            End SyncLock
        End Function

         ' modificare la giacenza di un articolo identificato dalla sua chiave
        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
             ' modificare la giacenza di un articolo
            SyncLock Me
                 ' si verifica se esiste
                Dim ipos As Integer = posArticle(articles, idArticle)
                 ' nel caso in cui non esista
                If ipos = -1 Then Return 0
                 ' se esiste, si modifica la sua giacenza se possibile
                Dim unArticle As Article = CType(articles(ipos), Article)
                 ' si modifica la giacenza solo se è sufficiente
                If unArticle.stockactuel + mouvement >= 0 Then
                    unArticle.stockactuel += mouvement
                    Return 1
                Else
                    Return 0
                End If
            End SyncLock
        End Function

         ' cercare un articolo identificato dalla sua chiave
        Private Function posArticle(ByVal listArticles As ArrayList, ByVal idArticle As Integer) As Integer
             ' restituisce la posizione dell'articolo [idArticle] nell'elenco oppure -1 se non trovato
            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
             ' non trovato
            Return -1
        End Function
    End Class

End Namespace

Note:

  • la fonte dati è simulata dal campo privato [articles] di tipo [ArrayList]
  • Il costruttore della classe crea per impostazione predefinita 4 voci nella fonte dati.
  • Tutti i metodi di accesso ai dati sono stati sincronizzati per evitare problemi di accesso concorrente alla fonte dati. In un dato momento, un solo thread ha accesso a un determinato metodo.
  • Il metodo [posArticle] consente di determinare la posizione [0..N] nella fonte [ArrayList] di un articolo identificato dal suo numero. Se l’articolo non esiste, il metodo restituisce la posizione -1. Questo metodo viene utilizzato ripetutamente dagli altri metodi.
  • Il metodo [ajouteArticle] consente di aggiungere un articolo all'elenco degli articoli. Restituisce il numero di articoli inseriti: 1. Se l'articolo esisteva già, viene generata un'eccezione.
  • Il metodo [modifieArticle] consente di modificare un articolo esistente. Restituisce il numero di articoli modificati: 1 se l’articolo esisteva, 0 in caso contrario.
  • Il metodo [supprimeArticle] consente di eliminare un articolo esistente. Restituisce il numero di articoli eliminati: 1 se l'articolo esisteva, 0 in caso contrario.
  • Il metodo [getAllArticles] fornisce l’elenco di tutti gli articoli
  • il metodo [getArticleById] consente di ottenere un articolo identificato dal suo numero. Si ottiene il valore [nothing] se l’articolo non esiste.
  • Il codice non presenta particolari difficoltà. Lasciamo che sia il lettore a esaminarlo e comprenderlo.

1.4.3.4. Generazione dell’assembly del livello [dao]

Il progetto Visual Studio è configurato per generare l’assembly [webarticles-dao.dll]. Questo viene generato nella cartella [bin] del progetto:

1.4.3.5. Test NUnit del livello [dao]

In Java, le classi vengono testate con il framework [Junit]. In .NET, il framework Nunit offre le stesse possibilità di test unitari:

Image

La struttura del progetto di test in Visual Studio è la seguente:

Image

Note:

  • il progetto [tests] è di tipo [bibliothèque de classes]
  • i test [NUnit] richiedono un riferimento all'assembly [nunit.framework.dll]
  • la classe di test [NUnit] recupera un'istanza dell'oggetto da testare tramite Spring. Pertanto,
    • nella cartella [bin], i file delle classi di Spring
    • in [References], un riferimento all'assembly [Spring-Core.dll] della cartella [bin]
    • in [bin], un file di configurazione per Spring
  • la classe di test necessita dell'assembly [webarticles-dao.dll] del livello [dao]. Questo è stato inserito nella cartella [bin] e il relativo riferimento è stato aggiunto ai riferimenti del progetto.

Una classe di test [NUnit] richiede l'accesso alle classi dello spazio dei nomi [NUnit.Framework]. Vi si trova quindi la seguente istruzione di importazione:

Imports NUnit.Framework

Lo spazio dei nomi [NUnit.Framework] si trova nell’“assembly” [nunit.framework.dll], che deve essere aggiunto ai riferimenti del progetto:

L'assembly [nunit.framework.dll] dovrebbe essere presente nell'elenco proposto se è stata effettuata l'installazione di [Nunit]. È sufficiente fare doppio clic sull'assembly per aggiungerlo al progetto:

Image

La classe di test [NUnit] del livello [dao] potrebbe essere la seguente:

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
         ' l'oggetto da testare
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
             ' si recupera un'istanza del generatore di oggetti Spring
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
             ' si richiede l'istanziazione dell'oggetto articlesdao
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
        End Sub

        <Test()> _
        Public Sub testGetAllArticles()
             ' verifica visiva
            listArticles()
        End Sub

        <Test()> _
        Public Sub testClearAllArticles()
             ' si eliminano tutti gli articoli
            articlesDao.clearAllArticles()
             ' si richiedono tutti gli articoli
            Dim articles As IList = articlesDao.getAllArticles
             ' verifica: il numero deve essere 0
            Assert.AreEqual(0, articles.Count)
        End Sub

        <Test()> _
        Public Sub testAjouteArticle()
             ' eliminazione di tutti gli articoli
            articlesDao.clearAllArticles()
             ' verifica: la tabella degli articoli deve essere vuota
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' si aggiungono due articoli
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' verifica: devono esserci due articoli
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' verifica visiva
            listArticles()
        End Sub

        <Test()> _
        Public Sub testSupprimeArticle()
             ' eliminazione di tutti gli articoli
            articlesDao.clearAllArticles()
             ' verifica: la tabella degli articoli deve essere vuota
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' si aggiungono due articoli
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' verifica: devono esserci 2 articoli
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' si elimina l'articolo 4
            articlesDao.supprimeArticle(4)
             ' verifica: deve rimanere 1 articolo
            articles = articlesDao.getAllArticles
            Assert.AreEqual(1, articles.Count)
             ' verifica visiva
            listArticles()
        End Sub

        <Test()> _
        Public Sub testModifieArticle()
             ' eliminazione di tutti gli articoli
            articlesDao.clearAllArticles()
             ' verifica
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' aggiunta di 2 articoli
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' verifica
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' ricerca dell'articolo 3
            Dim unArticle As Article = articlesDao.getArticleById(3)
             ' verifica
            Assert.AreEqual(unArticle.nom, "article3")
             ' ricerca dell'articolo 4
            unArticle = articlesDao.getArticleById(4)
             ' verifica
            Assert.AreEqual(unArticle.nom, "article4")
             ' modifica articolo 4
            articlesDao.modifieArticle(New Article(4, "article4", 44, 44, 44))
             ' verifica
            unArticle = articlesDao.getArticleById(4)
            Assert.AreEqual(unArticle.prix, 44, 0.000001)
             ' verifica visiva
            listArticles()
        End Sub

        <Test()> _
        Public Sub testGetArticleById()
             ' eliminazione degli articoli
            articlesDao.clearAllArticles()
             ' verifica
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' aggiunta di 2 articoli
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' verifica
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' ricerca articolo 3
            Dim unArticle As Article = articlesDao.getArticleById(3)
             ' verifica
            Assert.AreEqual(unArticle.nom, "article3")
             ' ricerca articolo 4
            unArticle = articlesDao.getArticleById(4)
             ' verifica
            Assert.AreEqual(unArticle.nom, "article4")
        End Sub

         ' elenco su schermo
        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()
             ' eliminazione di tutti gli articoli
            articlesDao.clearAllArticles()
             ' ricerca articolo 1
            Dim article As article = articlesDao.getArticleById(1)
             ' verifica
            Assert.IsNull(article)
             ' modifica di un articolo inesistente
            Dim i As Integer = articlesDao.modifieArticle(New article(1, "1", 1, 1, 1))
             ' non è stata modificata alcuna riga
            Assert.AreEqual(i, 0)
             ' eliminazione di un articolo inesistente
            i = articlesDao.supprimeArticle(1)
             ' non è stato necessario eliminare alcuna riga
            Assert.AreEqual(0, i)
        End Sub

        <Test()> _
        Public Sub testChangerStockArticle()
             ' eliminazione di tutti gli articoli
            articlesDao.clearAllArticles()
             ' aggiunta di un articolo
            Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
            Assert.AreEqual(nbArticles, 1)
             ' aggiunta di un articolo
            nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            Assert.AreEqual(nbArticles, 1)
             ' creazione di 100 thread
            Dim taches(99) As Thread
            For i As Integer = 0 To taches.Length - 1
                 ' si crea il thread i
                taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
                 ' si imposta il nome del thread
                taches(i).Name = "tache_" & i
                 ' si avvia l'esecuzione del thread i
                taches(i).Start()
            Next
             ' si attende il completamento di tutti i thread
            For i As Integer = 0 To taches.Length - 1
                taches(i).Join()
            Next
             ' verifiche: l'articolo 3 deve avere una disponibilità di 1
            Dim unArticle As Article = articlesDao.getArticleById(3)
            Assert.AreEqual(unArticle.nom, "article3")
            Assert.AreEqual(1, unArticle.stockactuel)
             ' si decrementa la giacenza dell'articolo 4
            Dim erreur As Boolean = False
            Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
             ' verifica: la sua disponibilità non deve essere cambiata
            Assert.AreEqual(0, nbLignes)
             ' controllo visivo
            listArticles()
        End Sub

        Public Sub décrémente()
             ' thread avviato
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
             ' thread che riduce le scorte
            articlesDao.changerStockArticle(3, -1)
             ' thread terminato
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
        End Sub

    End Class
End Namespace

Commenti:

  • si è voluto scrivere un programma di test dell'interfaccia [IArticlesDao] che fosse indipendente dalla classe di implementazione della stessa. Abbiamo quindi utilizzato Spring per nascondere al programma di test il nome della classe di implementazione.
  • Il metodo dell'attributo <Setup()> recupera da Spring un riferimento all'oggetto [articlesdao] da testare. Quest'ultimo è definito nel seguente file [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>

Questo file indica il nome della classe di implementazione [istia.st.articles.dao.ArticlesDaoArrayList] dell'interfaccia [IArticlesDao] e dove trovarla [ webarticles-dao.dll]. Poiché l'istanziazione non richiede parametri, qui non ne viene definito alcuno.

  • La maggior parte dei test è di facile comprensione. Si invita il lettore a leggere i commenti.
  • Il metodo [testChangerStockArticle] richiede alcune spiegazioni. Crea 100 thread incaricati di decrementare lo stock di un determinato articolo.
        <Test()> _
        Public Sub testChangerStockArticle()
             ' eliminazione di tutti gli articoli
            articlesDao.clearAllArticles()
             ' aggiunta di un articolo
            Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
            Assert.AreEqual(nbArticles, 1)
             ' aggiunta di un articolo
            nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            Assert.AreEqual(nbArticles, 1)
             ' creazione di 100 thread
            Dim taches(99) As Thread
            For i As Integer = 0 To taches.Length - 1
                 ' si crea il thread i
                taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
                 ' si imposta il nome del thread
                taches(i).Name = "tache_" & i
                 ' si avvia l'esecuzione del thread i
                taches(i).Start()
            Next
             ' si attende il completamento di tutti i thread
            For i As Integer = 0 To taches.Length - 1
                taches(i).Join()
            Next
             ' verifiche: l'articolo 3 deve avere una disponibilità di 1
            Dim unArticle As Article = articlesDao.getArticleById(3)
            Assert.AreEqual(unArticle.nom, "article3")
            Assert.AreEqual(1, unArticle.stockactuel)
             ' si decrementa la giacenza dell'articolo 4
            Dim erreur As Boolean = False
            Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
             ' verifica: la sua disponibilità non deve essere cambiata
            Assert.AreEqual(0, nbLignes)
             ' verifica visiva
            listArticles()
        End Sub

In questo caso si tratta di testare gli accessi concorrenti alla fonte di dati. Il metodo incaricato di aggiornare le scorte è il seguente:

        Public Sub décrémente()
             ' thread avviato
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
             ' il thread riduce le scorte
            articlesDao.changerStockArticle(3, -1)
             ' thread terminato
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
        End Sub

Diminuisce di un'unità lo stock dell'articolo n. 3. Se si fa riferimento al codice del metodo [testChangerStockArticle], si nota che:

  • lo stock dell'articolo n. 3 viene inizializzato a 101
  • i 100 thread ridurranno ciascuno di un'unità tale stock
  • si dovrebbe quindi avere una quantità pari a 1 al termine dell’esecuzione di tutti i thread

Inoltre, sempre in questo metodo, si tenta di portare lo stock dell’articolo n. 4 a un valore negativo. L’operazione non deve andare a buon fine.

Per testare il livello [dao], generiamo DLL e [tests-webarticles-dao.dll] nella cartella [bin] del progetto [tests]:

Quindi, utilizzando l'applicazione [Nunit-Gui], carichiamo questo DLL ed eseguiamo i test:

Image

Nella finestra a sinistra è visibile l’elenco dei metodi testati. Il colore del punto che precede il nome di ciascun metodo indica se il metodo ha avuto esito positivo (verde) o negativo (rosso). Il lettore che visualizza questo documento sullo schermo potrà constatare che tutti i test sono stati superati con successo. Di conseguenza, considereremo che disponiamo di un livello [dao] operativo.

1.4.4. Il livello [domain]

Il livello [domain] contiene i seguenti elementi:

  • [IArticlesDomain]: l’interfaccia di accesso al livello [domain]

  • [Achat]: classe che definisce un acquisto

  • [Panier]: classe che definisce un carrello della spesa

  • [AchatsArticles]: classe di implementazione dell’interfaccia [IArticlesDomain]

La struttura della soluzione [Visual Studio] del livello [domain] è la seguente:

Image

Commenti:

  • il progetto [domain] è di tipo [bibliothèque de classes]
  • le classi sono state inserite in una struttura ad albero con radice nella cartella [istia]. Si trovano tutte nello spazio dei nomi [istia.st.articles.domain].
  • la classe DLL del livello [dao] è stata inserita nella cartella [bin] del nuovo progetto. Inoltre, questa classe DLL è stata aggiunta come riferimento al progetto.

1.4.4.1. L'interfaccia [IArticlesDomain]

L'interfaccia [IArticlesDomain] disaccoppia il livello [métier] dal livello [web]. Quest'ultimo accede al livello [métier/domain] tramite questa interfaccia senza preoccuparsi della classe che lo implementa effettivamente. L'interfaccia definisce le seguenti azioni per l'accesso al livello di business:

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

Namespace istia.st.articles.domain
    Public Interface IArticlesDomain
         ' metodi
        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
restituisce l'elenco degli oggetti [Article] della fonte dati associata
Function getArticleById(ByVal idArticle As Integer) As Article
restituisce l'oggetto [Article] identificato da [idArticle]
acheter(ByVal panier As Panier)
conferma il carrello del cliente sottraendo dalle scorte degli articoli acquistati la quantità acquistata - l'operazione potrebbe non andare a buon fine se le scorte sono insufficienti
ReadOnly Property erreurs() As ArrayList
restituisce l'elenco degli errori verificatisi - vuoto se non ci sono errori

1.4.4.2. La classe [Achat]

La classe [Achat] rappresenta un acquisto del cliente:

Imports istia.st.articles.dao

Namespace istia.st.articles.domain

    Public Class Achat

         ' campi privati
        Private _article As article
        Private _qte As Integer

         ' costruttore predefinito
        Public Sub New()
        End Sub

         ' costruttore con parametri
        Public Sub New(ByVal unArticle As article, ByVal qte As Integer)
             ' si passa tramite le proprietà
            Me.article = unArticle
            Me.qte = qte
        End Sub

         ' articolo acquistato
        Public Property article() As article
            Get
                Return _article
            End Get
            Set(ByVal Value As article)
                _article = Value
            End Set
        End Property

         ' quantità acquistata
        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

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

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

End Namespace

Note:

  • la classe [Achat] presenta le seguenti proprietà e metodi:
Public Property article() As article
l'articolo acquistato
Public Property qte() As Integer
la quantità acquistata
Public ReadOnly Property totalAchat() As Double
l'importo dell'acquisto
Public Overrides Function ToString() As String
stringa di identificazione dell'oggetto
  • dispone di un costruttore che consente di inizializzare le proprietà [article, qte] che definiscono un acquisto.

1.4.4.3. La classe [Panier]

La classe [Panier] rappresenta l'insieme degli acquisti del cliente:

Namespace istia.st.articles.domain

Public Class Panier

     ' campi privati
    Private _achats As New ArrayList
        Private _totalPanier As Double = 0

     ' produttore predefinito
    Public Sub New()
    End Sub

     ' elenco degli acquisti
    Public ReadOnly Property achats() As ArrayList
        Get
            Return _achats
        End Get
    End Property

     ' totale acquisti
    Public ReadOnly Property totalPanier() As Double
        Get
            Return _totalPanier
        End Get
    End Property

     ' metodi
    Public Sub ajouter(ByVal unAchat As Achat)
         ' si verifica se l'acquisto esiste già
        Dim iAchat As Integer = posAchat(unAchat.article.id)
        If iAchat <> -1 Then
             ' è stato trovato
                Dim achatCourant As Achat = CType(_achats(iAchat), Achat)
            achatCourant.qte += unAchat.qte
        Else
             ' non trovato
            _achats.Add(unAchat)
        End If
         ' si incrementa il totale del carrello
        _totalPanier += unAchat.totalAchat
    End Sub

     ' rimuovere un articolo
    Public Sub enlever(ByVal idAchat As Integer)
         ' si cerca l'articolo
            Dim iachat As Integer = posAchat(idAchat)
         ' se trovato, lo si rimuove
        If iachat <> -1 Then
                Dim achatCourant As Achat = CType(_achats(iachat), Achat)
             ' si rimuove dal carrello
            _achats.RemoveAt(iachat)
             ' ridurre il totale del carrello
            _totalPanier -= achatCourant.totalAchat
        End If
    End Sub

    Private Function posAchat(ByVal idArticle As Integer) As Integer
         ' cerca un acquisto nell'elenco degli acquisti
         ' restituisce la sua posizione nell'elenco oppure -1 se non trovato
        Dim achatCourant As Achat
        Dim trouvé As Boolean = False
        Dim i As Integer = 0
            While Not trouvé AndAlso i < _achats.Count
                 ' acquisto corrente
                achatCourant = CType(_achats(i), Achat)
                 ' confronto con l'articolo cercato
                If achatCourant.article.id = idArticle Then
                    Return i
                End If
                 'acquisto successivo
                i += 1
            End While
             ' non trovato
            Return -1
    End Function

     ' funzione identità
    Public Overrides Function ToString() As String
        Return _achats.ToString
    End Function
End Class

End Namespace

Note:

  • la classe [Panier] presenta le seguenti proprietà e metodi:
ReadOnly Property achats() As ArrayList
L'elenco degli acquisti del cliente - elenco di oggetti di tipo [Achat]
ajouter(ByVal unAchat As Achat)
aggiunge un acquisto all'elenco degli acquisti
enlever(ByVal idAchat As Integer)
rimuove l'acquisto dell'articolo idAchat
ReadOnly Property totalPanier() As Double
l'importo totale degli acquisti nel carrello
Function ToString() As String
restituisce la stringa identificativa del carrello
  • il metodo [posAchat] è un metodo di utilità che consente di ottenere la posizione nell'elenco degli acquisti di un acquisto identificato dal numero dell'articolo acquistato. L'elenco degli acquisti è gestito in modo tale che un articolo acquistato più volte occupi una sola posizione nell'elenco. In questo modo, un acquisto può essere identificato tramite il numero dell'articolo acquistato. Il metodo [posAchat] restituisce -1 se l'acquisto ricercato non esiste.
  • Il metodo [ajouter] aggiunge un nuovo acquisto all’elenco degli acquisti. Ciò equivale ad aggiungere una nuova voce all’elenco degli acquisti se l’articolo acquistato non era già presente nell’elenco, oppure ad incrementare la quantità acquistata se era già presente.
  • Il metodo [enlever] consente di rimuovere un acquisto identificato da un numero dall'elenco degli acquisti. Se l'acquisto non esiste, il metodo non genera alcun messaggio e non esegue alcuna operazione.
  • L’importo degli acquisti [totalPanier] viene mantenuto anche in seguito all’aggiunta o alla rimozione di acquisti.

1.4.4.4. La classe [AchatsArticles]

L’interfaccia [IArticlesDomain] sarà implementata dalla seguente classe [AchatsArticles]:

Imports istia.st.articles.dao

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

         'campi privati
        Private _articlesDao As IArticlesDao
        Private _erreurs As ArrayList

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

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

         ' elenco degli articoli
        Public Function getAllArticles() As IList Implements IArticlesDomain.getAllArticles
             ' elenco di tutti gli articoli
            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

         ' recupera un articolo identificato dal suo n.
        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDomain.getArticleById
             ' un articolo specifico
            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


         ' acquistare un carrello
        Public Sub acheter(ByVal panier As Panier) Implements IArticlesDomain.acheter
             ' acquisto di un carrello - le scorte degli articoli acquistati devono essere decresciute
            _erreurs = New ArrayList
            Dim achat As achat
            Dim achats As ArrayList = panier.achats
            For i As Integer = achats.Count - 1 To 0 Step -1
                 ' ridurre le scorte dell'articolo i
                achat = CType(achats(i), achat)
                Try
                    If _articlesDao.changerStockArticle(achat.article.id, -achat.qte) = 0 Then
                         ' non è stato possibile eseguire l'operazione
                        _erreurs.Add("L'achat " + achat.ToString + " n'a pu se faire - Vérifiez les stocks")
                    Else
                         ' l'operazione è stata eseguita - l'acquisto viene rimosso dal carrello
                        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

Commenti:

  • questa classe implementa i quattro metodi dell'interfaccia [IArticlesDomain]. Ha due campi privati:
_articlesDao As IArticlesDao
l'oggetto di accesso ai dati
_erreurs As ArrayList
l'elenco degli eventuali errori. È accessibile tramite la proprietà pubblica [erreurs]
  • per creare un’istanza della classe, è necessario fornire l’oggetto che consente l’accesso ai dati:
Sub New(ByVal articlesDao As IArticlesDao)
  • i metodi [getAllArticles] e [getArticleById] si basano sui metodi omonimi del livello [dao]
  • il metodo [acheter] convalida l'acquisto di un carrello. Tale convalida consiste semplicemente nel ridurre le scorte degli articoli acquistati. L'acquisto di un articolo è possibile solo se le scorte lo consentono. In caso contrario, l'acquisto viene rifiutato: l'articolo rimane nel carrello e viene segnalato un errore nell'elenco [erreurs]. Un acquisto convalidato viene rimosso dal carrello e le scorte dell'articolo corrispondente vengono ridotte della quantità acquistata.

1.4.4.5. Generazione dell’assembly del livello [domain]

Il progetto Visual Studio è configurato per generare l’assembly [webarticles-domain.dll]. Questo viene generato nella cartella [bin] del progetto:

1.4.4.6. Test NUnit del livello [domain]

La struttura del progetto di test in Visual Studio è la seguente:

Image

Note:

  • il progetto [tests] è di tipo [bibliothèque de classes]
  • i test [NUnit] richiedono un riferimento all'assembly [nunit.framework.dll]
  • la classe di test [NUnit] recupera un'istanza dell'oggetto da testare tramite Spring. Pertanto,
  • nella cartella [bin], i file delle classi di Spring
  • in [References], un riferimento all'assembly [Spring-Core.dll] della cartella [bin]
  • in [bin], un file di configurazione per Spring
  • la classe di test richiede l'assembly [webarticles-dao.dll] del livello [dao] e l'assembly [webarticles-domain.dll] del livello [domain]. Questi sono stati inseriti nella cartella [bin] e i relativi riferimenti sono stati aggiunti ai riferimenti del progetto.

Una classe di test NUnit del livello [domain] potrebbe essere la seguente:

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

         ' l'oggetto da testare
        Private articlesDomain As IArticlesDomain
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
             ' si recupera un'istanza del generatore di oggetti Spring
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
             ' si richiede l'istanziazione dell'oggetto DAO articoli
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
             ' e successivamente quella dell'oggetto "articlesdomain"
            articlesDomain = CType(factory.GetObject("articlesdomain"), IArticlesDomain)
        End Sub

        <Test()> _
        Public Sub getAllArticles()
             ' verifica visiva
            listArticles()
        End Sub

        <Test()> _
        Public Sub getArticleById()
             ' eliminazione degli articoli
            articlesDao.clearAllArticles()
             ' verifica
            Dim articles As IList = articlesDomain.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' aggiunta di 2 articoli
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' verifica
            articles = articlesDomain.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' ricerca articolo 3
            Dim unArticle As Article = articlesDomain.getArticleById(3)
             ' verifica
            Assert.AreEqual(unArticle.nom, "article3")
             ' ricerca articolo 4
            unArticle = articlesDao.getArticleById(4)
             ' verifica
            Assert.AreEqual(unArticle.nom, "article4")
        End Sub

        <Test()> _
        Public Sub acheterPanier()
             ' eliminazione articoli
            articlesDao.clearAllArticles()
             ' verifica
            Dim articles As IList = articlesDomain.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' aggiunta di 2 articoli
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' verifica
            articles = articlesDomain.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' creazione di un carrello con due articoli
            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))
             ' verifiche
            Assert.AreEqual(700, panier.totalPanier, 0.000001)
            Assert.AreEqual(2, panier.achats.Count)
             ' conferma carrello
            articlesDomain.acheter(panier)
             ' verifiche
            Assert.AreEqual(0, articlesDomain.erreurs.Count)
            Assert.AreEqual(0, panier.achats.Count)
             ' ricerca articolo 3
            Dim unArticle As Article = articlesDomain.getArticleById(3)
             ' verifica
            Assert.AreEqual(unArticle.stockactuel, 20)
             ' ricerca articolo 4
            unArticle = articlesDao.getArticleById(4)
             ' verifica
            Assert.AreEqual(unArticle.stockactuel, 30)
             ' nuovo carrello
            panier.ajouter(New Achat(New Article(3, "article3", 30, 30, 3), 100))
             ' conferma carrello
            articlesDomain.acheter(panier)
             ' verifiche
            Assert.AreEqual(1, articlesDomain.erreurs.Count)
             ' ricerca articolo 3
            unArticle = articlesDomain.getArticleById(3)
             ' verifica
            Assert.AreEqual(unArticle.stockactuel, 20)
        End Sub

        <Test()> _
        Public Sub testRetirerAchats()
             ' eliminazione del contenuto di ARTICLES
            articlesDao.clearAllArticles()
             ' legge la tabella ARTICLES
            Dim articles As IList = articlesDao.getAllArticles()
            Assert.AreEqual(0, articles.Count)
             ' inserimento
            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)
             ' legge la tabella ARTICLES
            articles = articlesDomain.getAllArticles()
            Assert.AreEqual(2, articles.Count)
             ' creazione di un carrello con due articoli
            Dim monPanier As New Panier
            monPanier.ajouter(New Achat(article3, 10))
            monPanier.ajouter(New Achat(article4, 10))
             ' verifiche
            Assert.AreEqual(700.0, monPanier.totalPanier, 0.000001)
            Assert.AreEqual(2, monPanier.achats.Count)
             ' aggiungere un articolo già acquistato
            monPanier.ajouter(New Achat(article3, 10))
             ' verifiche
             ' il totale deve essere portato a 1000
            Assert.AreEqual(1000.0, monPanier.totalPanier, 0.000001)
             ' ci sono ancora 2 articoli nel carrello
            Assert.AreEqual(2, monPanier.achats.Count)
             ' la quantità dell'articolo 3 deve essere portata a 20
            Dim unAchat As Achat = CType(monPanier.achats(0), Achat)
            Assert.AreEqual(20, unAchat.qte)
             ' l'articolo 3 viene rimosso dal carrello
            monPanier.enlever(3)
             ' verifiche
             ' il totale deve essere stato portato a 400
            Assert.AreEqual(400.0, monPanier.totalPanier, 0.000001)
             ' Solo 1 articolo nel carrello
            Assert.AreEqual(1, monPanier.achats.Count)
             ' deve trattarsi dell'articolo n. 4
            Assert.AreEqual(4, CType(monPanier.achats(0), Achat).article.id)
        End Sub

         ' elenco sullo schermo
        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

Commenti:

  • si è voluto scrivere un programma di test dell'interfaccia [IArticlesDomain] che fosse indipendente dalla classe di implementazione della stessa. Abbiamo quindi utilizzato Spring per nascondere al programma di test il nome della classe di implementazione.
  • Il metodo dell'attributo <Setup()> recupera da Spring un riferimento agli oggetti [articlesdomain] e [articlesdao] da testare. Questi sono definiti nel seguente file [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>

Questo file riporta:

  • (continua)
    • per il singleton [articlesdao], il nome della classe di implementazione [istia.st.articles.dao.ArticlesDaoArrayList] e dove trovarla [ webarticles-dao.dll]. Poiché l'istanziazione non richiede parametri, qui non ne viene definito alcuno.
    • per il singleton [articlesdomain], il nome della classe di implementazione [istia.st.articles.domain.AchatsArticles] e dove trovarla [ webarticles-domain.dll]. La classe [AchatsArticles] ha un costruttore con un parametro: il singleton che gestisce l’accesso al livello [dao]. In questo caso, quest’ultimo è definito come il singleton [articlesdao] definito in precedenza.
  • La classe di test ottiene un'istanza della classe da testare [articlesdomain] e un'istanza della classe di accesso ai dati [articlesdao]. Quest'ultimo punto è controverso. In teoria, la classe di test non dovrebbe aver bisogno di accedere al livello [dao], che non dovrebbe nemmeno conoscere. In questo caso, abbiamo ignorato questa “etica” che, se rispettata, ci avrebbe costretti a creare nuovi metodi nella nostra interfaccia [IArticlesDomain].

Per testare il livello [domain], generiamo DLL e [tests-webarticles-domain.dll] nella cartella [bin] del progetto [tests]:

Quindi, utilizzando l'applicazione [Nunit-Gui], carichiamo questo DLL ed eseguiamo i test:

Image

Il lettore che visualizza questo documento sullo schermo potrà constatare che tutti i test sono stati superati con successo. Di conseguenza, considereremo che disponiamo di un livello [domain] operativo.

1.4.5. Conclusione

Ricordiamo che vogliamo realizzare la seguente applicazione web a tre livelli:

Il modello M della nostra applicazione MVC è ora scritto e testato. Ci viene fornito in due file: DLL e [webarticles-dao.dll, webarticles-domain.dll]. Possiamo passare all’ultimo livello, il livello [web], che contiene il controller C e le viste V. Considereremo innanzitutto un metodo presentato nel documento [Développement WEB avec ASP.NET 1.1 ]

  • il controller C è gestito da due file [global.asax, main.aspx]
  • le viste V sono gestite da pagine aspx

1.5. Il livello [web]

L'architettura MVC dell'applicazione web sarà la seguente:

M=modèle
le classi di business [domain], le classi di accesso ai dati [dao] e la fonte dati
V=vues
le pagine ASPX
C=contrôleur
tutte le richieste dei client HTTP transitano attraverso i due controller seguenti:
global.asax: gestisce gli eventi relativi all'avvio iniziale dell'applicazione
main.aspx: elabora singolarmente la richiesta di ciascun cliente

1.5.1. Le viste

Le viste corrispondono a quelle presentate all'inizio del documento:

LISTE
liste.aspx
Le viste sono raccolte nella cartella [vues] dell'applicazione
INFOS
infos.aspx
PANIER
panier.aspx
PANIERVIDE
paniervide.aspx
ERREURS
erreurs.aspx

1.5.2. I controller

Come già indicato, il controller sarà composto da due elementi:

  1. [global.asax,global.asax.vb]: utilizzato principalmente per inizializzare l’applicazione e inserire nel contesto della stessa tutti i dati da condividere tra i diversi client
  2. [main.aspx, main.aspx.vb]: il vero e proprio controller, ovvero quello che gestisce le richieste HTTP provenienti dai client.

Le diverse richieste dei client saranno indirizzate al controller [main.aspx] e conterranno un parametro denominato [action] che specifica l’azione richiesta dal client:

richiesta
significato
azione del controller
risposte possibili
action=liste
il cliente desidera l'elenco degli
articoli
- richiede l'elenco degli articoli al livello
di business
- [LISTE]
- [ERREURS]
action=infos
il cliente richiede
informazioni su uno degli
articoli visualizzati nella vista
[LISTE]
- richiede l'articolo al livello di business
- [INFOS]
- [ERREURS]
action=achat
il cliente acquista un articolo
- richiede l'articolo al livello applicativo
e lo aggiunge al carrello del cliente
- [INFOS] in caso di errore nella quantità
- [LISTE] se non c'è errore
action=retirerachat
il cliente desidera eliminare un
acquisto dal proprio carrello
- recupera il carrello dalla sessione
e lo modifica
- [PANIER]
- [PANIERVIDE]
- [ERREURS]
action=panier
il cliente desidera visualizzare il proprio
carrello
- recupera il carrello dalla sessione
- [PANIER]
- [PANIERVIDE]
- [ERREURS]
action=validationpanier
il cliente ha completato gli acquisti
e passa alla fase di pagamento
- aggiorna le scorte nel database
degli articoli acquistati
- svuota il carrello del cliente dagli articoli
il cui acquisto è stato confermato
- [LISTE]
- [ERREURS]

1.5.3. Configurazione dell'applicazione

Cercheremo di configurare l'applicazione in modo da renderla il più flessibile possibile rispetto a modifiche quali:

  1. la modifica delle URL delle diverse viste
  2. la modifica delle classi che implementano le interfacce [IArticlesDao] e [IArticlesDomain]
  3. la modifica del SGBD, del database, della tabella degli articoli

1.5.3.1. Le modifiche a URL

I nomi delle viste URL saranno inseriti nel file di configurazione dell'applicazione [web.config] insieme ad alcuni altri parametri:

<?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. La modifica delle classi di implementazione delle interfacce

In linea con le architetture a tre livelli, i livelli devono essere isolati l'uno dall'altro. Tale isolamento si ottiene nel modo seguente:

  • i livelli comunicano tra loro tramite interfacce e non tramite classi concrete
  • il codice di un livello non istanzia mai direttamente la classe di un altro livello per utilizzarla. Si limita semplicemente a richiedere a uno strumento esterno, in questo caso Spring, un'istanza di implementazione dell'interfaccia del livello che desidera utilizzare. A tal fine, sappiamo che non ha bisogno di conoscere il nome della classe di implementazione, ma solo il nome del singleton Spring di cui desidera un riferimento.

Nella nostra applicazione, Spring verrà configurato nel file [web.config] dell’applicazione web nel modo seguente:

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

Per accedere al livello [métier], una classe del livello [web] potrà richiedere il singleton [articlesDomain]. Spring istanzierà quindi un oggetto di tipo [istia.st.articles.domain.AchatsArticles]. Per questa istanziazione, ha bisogno di un oggetto di tipo [articlesDao], ovvero di un oggetto di tipo [istia.st.articles.dao.ArticlesDaoArrayList]. Spring istanzierà quindi un oggetto di questo tipo. Al termine dell’operazione, il livello [web] che ha richiesto il singleton [articlesDomain] dispone dell’intera catena che lo collega alla fonte di dati:

1.5.3.3. Modifiche relative a SGBD o al database

Questo punto verrà qui ignorato poiché ci troviamo in un'applicazione di test senza SGBD. Affronteremo in un secondo momento l'implementazione di un livello [dao] basato su un SGBD.

1.5.4. La libreria di tag <asp:>

Consideriamo la vista [ERREURS] che visualizza un elenco di errori:

Image

La vista [ERREURS] ha il compito di visualizzare un elenco di errori che il controller [main.aspx] ha inserito nel contesto della richiesta con il nome [context.Items("erreurs")]. Esistono diversi modi per scrivere una pagina di questo tipo. In questa sede ci interessiamo solo alla parte relativa alla visualizzazione degli errori.

Ricordiamo che una pagina ASPX è composta da una parte di presentazione HTML e da una parte di codice .NET che prepara i dati che la parte di presentazione deve visualizzare. Queste due parti possono trovarsi in un unico file [aspx] (soluzione WebMatrix) oppure in due file: [aspx] per la presentazione e [aspx.vb] per il codice. Quest’ultima soluzione è quella adottata da Visual Studio. A complicare le cose, anche la parte di presentazione HTML può contenere codice .NET, il che tende a confondere la separazione tra [contrôleur] e [présentation] nella vista. Questa soluzione è generalmente sconsigliata. La rimozione di tutto il codice dalla parte [présentation] ha richiesto la creazione di librerie di tag. Queste «nascondono» il codice sotto forma di tag analoghi ai tag HTML. Presentiamo due possibili soluzioni per la pagina [ERREURS].

La nostra prima soluzione utilizza il codice .NET nella sezione [présentation] della pagina. La pagina ASPX recupera l’elenco degli errori presenti nella richiesta nella sua sezione controller [erreurs.aspx.vb]:

        Protected erreurs As ArrayList

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

e lo visualizza nella sezione [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>

La seconda soluzione utilizza il tag <asp:repeater> della libreria di tag <asp:> di ASP.NET. Se si crea una pagina ASPX graficamente, questo tag è disponibile sotto forma di componente server che si trascina sul modulo di progettazione. Se si scrive il codice ASPX manualmente, si parla di libreria di tag.

Con la libreria di tag <asp:>, il codice ASPX della vista precedente [ERREURS] diventa il seguente:

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

Il tag

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

serve a ripetere un motivo HTML sui diversi elementi di una fonte di dati. I suoi diversi elementi sono i seguenti:

HeaderTemplate
il motivo HTML da visualizzare prima che vengano visualizzati gli elementi della fonte dati
ItemTemplate
il motivo HTML da ripetere per ciascuno degli elementi della fonte dati. L'espressione [<%# Container.DataItem %>] serve a visualizzare il valore dell'elemento corrente della fonte dati
FooterTemplate
il motivo HTML da visualizzare dopo che sono stati visualizzati gli elementi della fonte dati

La fonte dati è collegata al tag solitamente nella sezione [contrôleur] della pagina:

        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
..
             ' si collegano gli errori a rptErreurs
            With rptErreurs
                .DataSource = context.Items("erreurs")
                .DataBind()
            End With
        End Sub

Questo collegamento può essere effettuato anche in fase di progettazione della pagina se la fonte dei dati è già nota, ad esempio un database esistente.

Nelle nostre viste utilizzeremo un altro tag: <asp:datagrid>, che consente di visualizzare una fonte di dati sotto forma di tabella.

1.5.5. Struttura della soluzione Visual Studio dell’applicazione [webarticles]

Un’applicazione web è un puzzle composto da numerosi elementi. Dotarla di un’architettura MVC ne aumenta in genere il numero. La struttura dell’applicazione [webarticles] sotto [Visual Studio] è la seguente:

  

Commenti:

  • il progetto [web] è di tipo [bibliothèque de classes] e non di tipo [Application web ASP.NET] come ci si potrebbe logicamente aspettare. Il tipo [Application web ASP.NET] richiede la presenza del server web IIS sul computer di sviluppo o su un computer remoto. Il server IIS non è presente di serie sui computer Windows XP Home. Tuttavia, molti PC vengono venduti con questa versione. Per consentire ai lettori che dispongono di Windows XP di implementare l’applicazione in esame, utilizzeremo il server Web Cassini (vedi allegato), disponibile gratuitamente presso Microsoft, e sostituiremo il progetto [Application web ASP.NET] con un progetto [bibliothèque de classes]. Ciò comporta alcuni inconvenienti, spiegati negli allegati.
  • I file DLL utilizzati dall’applicazione sono i seguenti:
webarticles-dao.dll
raggruppa le classi del livello di accesso ai dati
webarticles-domain.dll
raggruppa le classi del livello di business
Spring.Core.dll
contiene le classi Spring che ci consentono di integrare i livelli web, domain e DAO
log4net.dll
classi di log - utilizzate da Spring

Questi file DLL vengono inseriti nella cartella [bin] e aggiunti ai riferimenti del progetto.

1.5.6. Le viste ASPX

Come consigliato in precedenza, utilizzeremo la libreria di tag <asp:> nelle nostre viste ASPX.

1.5.6.1. Il componente utente [entete.ascx]

Per garantire una certa uniformità tra le diverse viste, queste condivideranno la stessa intestazione, quella che mostra il nome dell’applicazione con il menu:

Il menu è dinamico e definito dal controller. Quest’ultimo inserisce nella richiesta inviata alla pagina ASPX un attributo chiave "actions" il cui valore associato è un array di elementi di tipo Hashtable(). Ogni elemento di questo array è un dizionario destinato a generare un’opzione del menu dell’intestazione. Ogni dizionario ha due chiavi:

  • href: l'URL associata all'opzione di menu
  • link: il testo del menu

Trasformeremo l'intestazione in un controllo utente. Un controllo utente incapsula una parte della pagina (presentazione e codice associato) in un componente riutilizzabile in altre pagine. In questo caso, vogliamo riutilizzare il componente [entete] nelle altre viste dell'applicazione. Il codice di presentazione sarà contenuto in [entete.ascx] e il codice di controllo associato in [entete.ascx.vb]. Il codice di presentazione utilizzerà un componente <asp:repeater> per visualizzare la tabella delle opzioni del menu:

type
nom
rôle
1
ripetitore
rptMenu
fonte dei dati: un array di dizionari
con due chiavi: href, link
visualizza le opzioni del menu

Il codice di presentazione della pagina sarà il seguente:

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

Commenti:

  • il componente [repeater] è definito alle righe 6-14
  • ogni elemento della fonte dati associata al ripetitore è un dizionario con due chiavi: href - riga 9 e link - riga 10

Il codice di controllo associato sarà il seguente:

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())
                ' si associa l'array delle azioni al relativo componente
                With rptMenu
                    .DataSource = Value
                    .DataBind()
                End With
            End Set
        End Property
    End Class
End Namespace

Commenti:

  • il componente di tipo [EnteteWebArticles] ha una proprietà pubblica [actions] di sola scrittura - riga 7
  • questa proprietà consente di associare al componente <asp:repeater> denominato [rptMenu] - riga 10 - la tabella delle opzioni calcolata dal controller dell'applicazione - righe 11-12.

Le altre viste dell’applicazione utilizzeranno l’intestazione definita da [entete.ascx]. La pagina [erreurs.aspx], ad esempio, includerà l’intestazione utilizzando il seguente codice:

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

Commenti:

  • La riga 1 dichiara che il tag <WA:entete> dovrà essere associato al componente definito dal file [entete.ascx]. Gli attributi [TagPrefix] e [TagName] sono liberi.
  • Fatto ciò, l’inserimento del componente nel codice di presentazione della pagina avviene con la riga 9. All’esecuzione, questo tag avrà l’effetto di includere nel codice della pagina ASPX, che lo contiene, quello della pagina [entete.ascx]. Il codice di controllo [erreurs.aspx.vb] provvederà a inizializzare questo componente. Può farlo nel modo seguente:
    Public Class ErreursWebarticles
        Inherits System.Web.UI.Page

         ' componenti della pagina
...
        Protected WithEvents entete As New EnteteWebArticles

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
             ' si collegano le opzioni di menu a rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
...
        End Sub
    End Class

Commenti:

  • la riga 6 crea un oggetto di tipo [EnteteWebArticles], che è il tipo del componente creato
  • la riga 11 inizializza la proprietà [actions] di questo oggetto

1.5.6.2. La vista [liste.aspx]

1.5.6.2.1. Introduction

Questa vista mostra l'elenco degli articoli disponibili per la vendita:

Viene visualizzata a seguito di una richiesta /main?action=liste o /main?action=validationpanier. Gli elementi della richiesta del controller sono i seguenti:

actions
oggetto Hashtable() - l'array delle opzioni del menu
listarticles
ArrayList oggetti di tipo [Article]
message
oggetto String - messaggio da visualizzare in fondo alla pagina

Ogni link [Infos] della tabella HTML degli articoli ha un URL della forma [?action=infos&id=ID] dove ID è il campo id dell’articolo visualizzato.

1.5.6.2.2. Componenti della pagina
type
nom
rôle
1
componente utente
intestazione
visualizza l'intestazione
2
DataGrid
DataGridArticles
3 - colonna correlata: intestazione: Nome, campo: nome
4 - colonna correlata: intestazione: Prezzo, campo: prezzo
5 - colonna ipertestuale: testo: Informazioni, campo URL: id,
formato URL: /webarticles/main.aspx?action=infos&id={0}
visualizza gli articoli in vendita
6
etichetta
lblMessage
visualizza un messaggio

Ricordiamo come procedere per definire queste proprietà:

  • in Visual Studio, selezionare [DataGrid] per accedere alla relativa finestra delle proprietà:

Image

  • utilizzare il link [Mise en forme automatique] sopra riportato per gestire la forma della tabella visualizzata
  • e il link [Générateur de propriétés] per gestirne il contenuto
1.5.6.2.3. Codice di presentazione [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>

Commenti:

  • la riga 9 definisce l'intestazione della pagina
  • le righe 12-24 definiscono le caratteristiche di [DataGrid]
  • la riga 26 definisce l'etichetta [lblMessage]
1.5.6.2.4. Codice del controller [liste.aspx.vb]
Imports System
Imports System.Collections
Imports System.Data
Imports istia.st.articles.dao

Namespace istia.st.articles.web

     ' gestisce la pagina di visualizzazione dell'elenco degli articoli
    Public Class ListeWebarticles
        Inherits System.Web.UI.Page

         ' componenti della pagina
        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
             ' prepara la vista [liste] in base alle informazioni del contesto
             ' si collegano le opzioni di menu a rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
             ' si recuperano gli articoli in una tabella dati
            Dim articles As ArrayList = CType(context.Items("articles"), ArrayList)
             ' li si collega al componente [DataGrid] della pagina
            With DataGridArticles
                .DataSource = articles
                .DataBind()
            End With
             ' viene visualizzato il messaggio
            lblMessage.Text = context.Items("message").ToString
        End Sub
    End Class
End Namespace

Commenti:

  • i componenti della pagina compaiono nelle righe 13-15. Da notare che è stato necessario creare un oggetto [EnteteWebArticles] con un operatore [new], mentre ciò non è stato necessario con gli altri componenti. Senza questa creazione esplicita, si verificava un errore di esecuzione che indicava che l’oggetto [entete] non faceva riferimento a nulla. Questo aspetto meriterebbe di essere approfondito. Non è stato fatto.
  • la tabella delle opzioni del menu dell’intestazione viene prelevata dal contesto per inizializzare il componente [entete] della pagina - riga 20
  • l'elenco degli articoli viene prelevato dal contesto - riga 22
  • per inizializzare il componente [DataGridArticles] - righe 24-27
  • Il componente [lblMessage] viene inizializzato con un messaggio inserito nel contesto - riga 29

1.5.6.3. La vista [infos.aspx]

1.5.6.3.1. Introduction

Questa vista mostra le informazioni relative a un articolo e ne consente anche l'acquisto:

Image

Viene visualizzata a seguito di una richiesta /main?action=infos&id=ID o di una richiesta /main?action=acquisto&id=ID quando la quantità acquistata è errata. Gli elementi della richiesta del controller sono i seguenti:

actions
oggetto Hashtable() - l'array delle opzioni del menu
article
oggetto di tipo [Article] - articolo da visualizzare
msg
oggetto String - messaggio da visualizzare in caso di errore sulla quantità
qte
oggetto String - valore da visualizzare nel campo di immissione [Qte]

I campi [msg] e [qte] vengono utilizzati in caso di errore di immissione della quantità:

Image

Questa pagina contiene un modulo che viene inviato tramite il pulsante [Acheter]. La pagina di destinazione di URL, che a sua volta è la pagina di destinazione di POST, è [?action=achat&id=ID], dove ID è l'ID dell'articolo acquistato.

1.5.6.3.2. I componenti della pagina
type
nom
rôle
1
componente utente
intestazione
visualizza l'intestazione
2
letterale
litID
visualizza il numero dell'articolo
3 à 6
DataGrid
DataGridArticle
3 - colonna correlata: intestazione: Nome, campo: nome
4 - colonna correlata: intestazione: Prezzo, campo: prezzo
5 - colonna correlata: intestazione: Stock attuale, campo: stockActuel
6 - colonna correlata: intestazione: Stock minimo, campo: stockMinimum
visualizza un articolo
7
HTML Invia
 
Invia il modulo
8
HTML Input
runat=server
txtQte
inserire la quantità acquistata
9
etichetta
lblMsgQte
eventuale messaggio di errore
1.5.6.3.3. Il codice di presentazione [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>

Commenti:

  • l'intestazione è inclusa nella pagina - riga 9
  • il valore letterale [litId] è definito alla riga 10
  • il DataGrid [DataGridArticles] è definito alle righe 12-34
  • il modulo è definito alle righe 36-46. È di tipo POST.
  • Il valore di destinazione di POST è fornito da una variabile [strAction] - riga 36. Questa variabile dovrà essere definita dal controllore.
  • Il campo di immissione della quantità acquistata è definito alla riga 41. Si tratta di un componente server HTML (runat=server). A livello di codice, vi si accede tramite un oggetto.
  • La riga 42 definisce l'etichetta [lblMsgQte] che conterrà un eventuale messaggio di errore relativo alla quantità inserita
1.5.6.3.4. Il codice di controllo [infos.aspx.vb]
Imports istia.st.articles.dao
Imports System
Imports System.Collections

Namespace istia.st.articles.web

     ' gestisce la pagina delle informazioni su un articolo
    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

         ' l'URL dove verrà inviato il modulo
        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

         ' visualizzazione della pagina
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' si recuperano le informazioni della richiesta
            Dim unArticle As Article = CType(Session.Item("article"), Article)
             ' si collegano le opzioni di menu a rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
             ' si collega l'articolo a [DataGrid]
            Dim articles As New ArrayList
            articles.Add(unArticle)
            With DataGridArticle
                .DataSource = articles
                .DataBind()
            End With
             ' l'ID dell'etichetta
            litId.Text = unArticle.id.ToString
             ' il messaggio di errore
            lblMsgQte.Text = context.Items("msg").ToString
             ' la quantità precedente
            txtQte.Value = context.Items("qte").ToString
             ' l'azione
            strAction = "?action=achat&id=" + unArticle.id.ToString
        End Sub
End Namespace

Commenti:

  • i componenti della pagina sono definiti alle righe 10-14
  • la classe definisce una proprietà pubblica [strAction] che serve a definire la destinazione del POST del modulo - righe 17-25
  • l'articolo da visualizzare viene recuperato dal contesto dell'applicazione - riga 30
  • la tabella delle opzioni del menu dell'intestazione viene prelevata dal contesto per inizializzare il componente [entete] della pagina - riga 32
  • righe 33-39: il componente [DataGridArticle] viene collegato a una fonte dati di tipo [ArrayList] contenente esclusivamente l’articolo recuperato alla riga 30
  • i componenti [lblMsgQte, txtQte] vengono inizializzati con informazioni ricavate dal contesto - righe 42-45
  • anche la proprietà [straction] viene inizializzata con un'informazione ricavata dal contesto - riga 47. Questa variabile serve a generare l'attributo [action] del modulo HTML presente nella pagina:
        <form method="post" action="<%=strAction%>">
....
        </form>

1.5.6.4. La vista [panier.aspx]

1.5.6.4.1. Introduction

Questa vista mostra il contenuto del carrello:

Image

Viene visualizzata a seguito di una richiesta /main?action=carrello o /main?action=ritiraacquisto&id=ID. Gli elementi della richiesta del controller sono i seguenti:

actions
oggetto Hashtable() - l'array delle opzioni del menu
panier
oggetto di tipo [Panier] - il carrello da visualizzare

Ogni collegamento [Retirer] della tabella HTML degli acquisti nel carrello ha un URL della forma [?action=retirerachat&id=ID] dove ID è il campo [id] dell’articolo che si desidera rimuovere dal carrello.

1.5.6.4.2. I componenti della pagina
type
nom
rôle
1
componente utente
intestazione
visualizza l'intestazione
2
DataGrid
DataGridAchats
3 - colonna correlata - intestazione: Articolo, campo: nome
4 - colonna correlata - intestazione: Qtà, campo: qtà
5 - colonna correlata - intestazione: Prezzo, campo: prezzo
6 - colonna correlata - intestazione: Totale, campo: totale, formattazione {0:C}
7 - colonna ipertestuale - Testo: Rimuovi, URL: id, formato URL: /webarticles/main.aspx?action=retirerachat&id={0}
visualizza l'elenco degli articoli acquistati
8
etichetta
lblTotal
visualizza l'importo da pagare
1.5.6.4.3. Il codice di presentazione [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>

Commenti

  • la riga 9 include l'intestazione
  • nelle righe 12-27 è definito il componente [DataGridAchats]
  • alla riga 29 viene definito il componente [lblTotal]
1.5.6.4.4. Il codice di controllo [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
     ' gestisce la pagina di visualizzazione del carrello
    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
             ' collega le opzioni di menu a rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
             ' si recupera il carrello
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' si trasferiscono gli acquisti in una tabella di righe di acquisto
            Dim achats(unPanier.achats.Count - 1) As LigneAchat
             ' si modifica il tipo degli elementi di ArrayList
            For i As Integer = 0 To achats.Length - 1
                achats(i) = New LigneAchat(CType(unPanier.achats(i), Achat))
            Next
             ' si collegano i dati ai componenti [DataGrid] della pagina
            With DataGridAchats
                .DataSource = achats
                .DataBind()
            End With
             ' viene visualizzato il totale da pagare
            lblTotal.Text = unPanier.totalPanier.ToString
        End Sub

         ' riga d'acquisto creata a partire da un oggetto Acquisto
        Private Class LigneAchat
            Inherits Achat

             ' il costruttore riceve un acquisto
            Public Sub New(ByVal unAchat As Achat)
                Me.article = unAchat.article
                Me.qte = unAchat.qte
            End Sub

             ' id: restituisce l'ID dell'articolo acquistato
            Public ReadOnly Property id() As Integer
                Get
                    Return article.id
                End Get
            End Property

             ' nome: nome dell'articolo acquistato
            Public ReadOnly Property nom() As String
                Get
                    Return article.nom
                End Get
            End Property

             ' prezzo dell'articolo acquistato
            Public ReadOnly Property prix() As Double
                Get
                    Return article.prix
                End Get
            End Property

        End Class
    End Class
End Namespace

Commenti:

  • i componenti della pagina sono dichiarati alle righe 11-13
  • l'inizializzazione del componente [entete] è identica a quella presente nelle pagine già esaminate - riga 17
  • il carrello da visualizzare viene recuperato dalla sessione - riga 19
  • la visualizzazione di questo carrello tramite il componente [DataGridAchats] presenta dei problemi. La difficoltà deriva dall’inizializzazione del componente. Ricordiamo le colonne di quest’ultimo:
    • colonna [Article] associata al campo [nom] della fonte dati
    • colonna [Qté] associata al campo [qte] della fonte dati
    • colonna [Prix] associata al campo [prix] della fonte dati
    • colonna [Total] associata al campo [total] della fonte dati

La fonte dati di cui disponiamo è il carrello e la relativa lista della spesa. Quest’ultima costituirà la fonte dati del [DataGrid]. Solo gli oggetti [Achat] che andranno ad alimentare le righe del [DataGrid] non presentano le proprietà [nom, qte, prix, total] previste dal [DataGrid]. Pertanto, in questo caso si crea, appositamente per il [DataGrid], una fonte di dati i cui elementi presentano le caratteristiche previste dal [DataGrid]. Tali elementi saranno di tipo [LigneAchat], una classe creata appositamente e derivata dalla classe [Achat] (righe 36-66)

  • una volta definita la classe [LigneAchat), la fonte dati di [DataGridAchats] viene costruita a partire dal carrello individuato nella sessione - righe 20-30
  • l’importo degli acquisti viene visualizzato grazie alla proprietà [totalPanier] della classe [Panier] - riga 32

1.5.6.5. La vista [paniervide.aspx]

1.5.6.5.1. Introduction

Questa vista mostra l’informazione che indica che il carrello è vuoto:

Image

Viene visualizzata a seguito di una richiesta /main?action=carrello o /main?action=ritiraacquisto&id=ID. Gli elementi della richiesta del controller sono i seguenti:

actions
oggetto Hashtable() - l'array delle opzioni del menu
1.5.6.5.2. I componenti della pagina
type
nom
rôle
1
componente utente
intestazione
visualizza l'intestazione
1.5.6.5.3. Il codice di presentazione [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>

Commenti:

  • l'intestazione è inclusa alla riga 9
1.5.6.5.4. Il codice di controllo [paniervide.aspx.vb]
Namespace istia.st.articles.web
     ' gestisce la pagina di visualizzazione di un carrello vuoto
    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
             ' prepara la vista [paniervide] in base alle informazioni del contesto
             ' si collegano le opzioni di menu a rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
        End Sub
    End Class
End Namespace

Commenti:

  • ci si limita a inizializzare l'unico componente dinamico della pagina - riga 10

1.5.6.6. La vista [erreurs.aspx]

1.5.6.6.1. Introduction

Questa schermata viene visualizzata in caso di errori:

Image

Viene visualizzata a seguito di qualsiasi richiesta che generi un errore, ad eccezione dell'azione di acquisto con una quantità errata, che viene invece gestita dalla vista [INFOS]. Gli elementi della richiesta del controller sono i seguenti:

actions
oggetto Hashtable() - la tabella delle opzioni del menu
erreurs
ArrayList di oggetti [String] che rappresentano i messaggi di errore da visualizzare
1.5.6.6.2. I componenti della pagina
type
nom
rôle
1
componente utente
intestazione
visualizza l'intestazione
2
ripetitore
rptErreurs
Visualizza l'elenco degli errori
1.5.6.6.3. Il codice di presentazione [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>

Commenti:

  • l'intestazione è definita alla riga 9
  • il componente [rptErreurs] è definito alle righe 13-19. Il suo contenuto proviene da una fonte di dati che sarà di tipo [ArrayList] di oggetti [String].
1.5.6.6.4. Il codice di controllo [erreurs.aspx.vb]
Namespace istia.st.articles.web

     ' gestisce la pagina degli errori
    Public Class ErreursWebarticles
        Inherits System.Web.UI.Page

         ' componenti della pagina
        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
             ' si prepara la vista [erreurs] in base alle informazioni del contesto
             ' si associano le opzioni di menu a rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
             ' si associano gli errori a rptErreurs
            With rptErreurs
                .DataSource = context.Items("erreurs")
                .DataBind()
            End With
        End Sub
    End Class

End Namespace

Commenti:

  • il componente [entete] viene inizializzato come di consueto, righe 9 e 14
  • il componente [rptErreurs] viene inizializzato con l'elenco degli errori di tipo [ArrayList] rilevato nel contesto - righe 16-19

1.5.7. I controller global.asax, main.aspx

Resta da scrivere il cuore della nostra applicazione web, il controller. Il suo ruolo consiste nel:

  • recuperare la richiesta del client,
  • elaborare l'azione richiesta dal cliente utilizzando le classi di business,
  • inviare in risposta la vista appropriata.

1.5.7.1. Il controller [global.asax.vb]

Quando l’applicazione riceve la sua primissima richiesta, viene eseguita la procedura [Application_Start] contenuta nel file [global.asax.vb]. Ciò avverrà una sola volta. La procedura [Application_Start] ha lo scopo di inizializzare gli oggetti necessari all’applicazione web, che saranno condivisi in sola lettura da tutti i thread client. Questi oggetti condivisi possono essere collocati in due posizioni:

  • i campi privati del controller
  • il contesto di esecuzione dell’applicazione (Application)

Il metodo [Application_Start] dell'applicazione [global.asax.vb] eseguirà le seguenti azioni:

  • verificherà la presenza, nel file [web.config], dei parametri necessari al corretto funzionamento dell’applicazione. Questi sono stati descritti nel paragrafo 1.5.3.
  • inserirà nel contesto dell’applicazione l’elenco degli eventuali errori sotto forma di un oggetto [ArrayList erreurs]. Tale elenco sarà vuoto se non vi sono errori, ma esisterà comunque.
  • Se si sono verificati errori, il metodo [Application_Start] termina qui. In caso contrario, richiede il riferimento a un singleton di tipo [IArticlesDomain], che sarà l’oggetto di business che il controller utilizzerà per le proprie esigenze. Come spiegato al punto 1.5.3.2, il controller richiederà questo singleton al framework Spring. Questa operazione di istanziazione può causare diversi errori. In tal caso, anche questi saranno memorizzati nell’oggetto [erreurs] del contesto dell’applicazione.

Il controller [global.asax.vb] dispone di una procedura [Session_Start] eseguita ogni volta che arriva un nuovo cliente. In questa procedura verrà creato un carrello vuoto per il cliente. Questo carrello verrà mantenuto nel corso delle richieste di quel particolare cliente. Il codice potrebbe essere il seguente:

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

         ' si avvia l'applicazione
        Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)

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

             ' si recuperano i parametri di inizializzazione dell'applicazione
            Dim param As String
            For i As Integer = 0 To parameters.Length - 1
                 ' lettura dal file di configurazione
                param = ConfigurationSettings.AppSettings(parameters(i))
                If param Is Nothing Then
                     ' si registra l'errore
                    erreurs.Add("Paramètre [" + parameters(i) + "] absent dans le fichier [web.config]")
                Else
                     ' si memorizza il parametro nell'applicazione
                    Application.Item(parameters(i)) = param
                End If
            Next
             ' Ci sono errori?
            If erreurs.Count = 0 Then
                 ' si crea un oggetto IArticlesDomain per l'accesso al livello business
                Dim contexte As IApplicationContext = CType(ConfigurationSettings.GetConfig("spring/context"), IApplicationContext)
                Dim articlesDomain As IArticlesDomain
                Try
                    articlesDomain = CType(contexte.GetObject("articlesDomain"), IArticlesDomain)
                     ' si memorizza l'oggetto nell'applicazione
                    Application.Item("articlesDomain") = articlesDomain
                Catch ex As Exception
                     ' si memorizza l'errore
                    erreurs.Add("Erreur lors de la construction de l'objet d'accès à la couche métier [" + ex.ToString + "]")
                End Try
            End If
             ' gli errori vengono inseriti nell'applicazione
            Application.Item("erreurs") = erreurs
             ' il processo termina se si sono verificati errori
            If erreurs.Count <> 0 Then Return
             ' si crea un array di opzioni di menu
            Dim options As New Hashtable
             ' si recupera il codice URL dal controller
            Dim urlMain As String = CType(Application.Item("urlMain"), String)
            Dim uneOption As Hashtable
             ' elenco degli articoli
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=liste")
            uneOption.Add("lien", "Liste des articles")
            options.Add("liste", uneOption)
             ' carrello
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=panier")
            uneOption.Add("lien", "Voir le panier")
            options.Add("panier", uneOption)
             ' conferma carrello
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=validationpanier")
            uneOption.Add("lien", "Valider le panier")
            options.Add("validationpanier", uneOption)
             ' si inseriscono le opzioni del menu nell'applicazione
            Application.Item("options") = options
            Return
        End Sub

         ' inizializzazione della sessione
        Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
             ' creazione del carrello per il cliente
            Session.Item("panier") = New Panier
        End Sub
    End Class
End Namespace

Commenti:

  • i parametri previsti in [web.config] sono definiti in un array - riga 18
  • vengono ricercati in [web.config]. Se presenti, vengono memorizzati nel contesto dell’applicazione; in caso contrario, viene registrato un errore nell’elenco degli errori [erreurs] – righe 21-33
  • se non ci sono errori, si richiede a Spring un riferimento al singleton [articlesDomain] che gestisce l'accesso al livello [domain] dell'applicazione - righe 35-47. Eventuali errori vengono registrati in [erreurs].
  • Gli errori vengono registrati nel contesto dell’applicazione – riga 49
  • si esce dalla procedura se si sono verificati errori - riga 51
  • viene creato un array composto da tre dizionari. Ciascuno di essi ha due chiavi: href e link. Questo array rappresenta le tre possibili opzioni di menu - righe 52-71
  • questo array viene memorizzato nel contesto dell'applicazione - riga 73
  • ad ogni nuovo cliente, viene eseguita la procedura [Session_Start]. In essa viene creato un carrello vuoto nella sessione del cliente - righe 78-81

1.5.7.2. Il controller [main.aspx.vb]

Il controller [main.aspx.vb] riceve tutte le richieste dei clienti. Infatti, queste hanno tutte la forma [/webarticles/main.aspx?action=XX]. Una richiesta verrà elaborata nel modo seguente:

  • viene verificato l’oggetto [erreurs] del contesto dell’applicazione. Se non è vuoto, significa che si sono verificati errori durante l’inizializzazione dell’applicazione e che questa non può funzionare. Verrà quindi inviata, in risposta, la vista [ERREURS].
  • il parametro [action] della richiesta verrà recuperato e verificato. Se non corrisponde a un’azione nota, verrà inviata la vista [ERREURS] con un messaggio di errore appropriato.
  • Se il parametro [action] è valido, la richiesta del client viene inoltrata a una procedura specifica dell’azione per l’elaborazione:
metodo
richiesta
elaborazione
risposte possibili
doListe
GET /main?action=liste
- richiedere l'elenco degli articoli
alla classe di business
- visualizzarlo
[LISTE] o [ERREURS]
doInfos
GET /main?action=infos&id=ID
- richiedere l'articolo con id=ID alla
classe di business
- visualizzarlo
[INFOS] o [ERREURS]
doAchat
POST /main?action=acquisto&id=ID
- la quantità acquistata fa parte dei parametri inviati
- richiedere l'articolo con id=ID a
classe di business
- inserirlo nel carrello durante
sessione del cliente
[LISTE] oppure [INFOS] oppure [ERREURS]
doRetirerAchat
GET /main?action=ritiro&id=ID
- rimuovere l'articolo con id=ID dall'
elenco degli acquisti nel carrello
sessione del cliente
[PANIER]
doPanier
GET /main?action=carrello
- visualizza il carrello della
sessione cliente
[PANIER] o [PANIERVIDE]
doValidationPanier
GET /main?action=confermacarrello
- ridurre nel database le
scorte di tutti gli articoli
presenti nel carrello della
sessione del cliente
[LISTE] o [ERREURS]

Lo scheletro del controller [main.aspx.vb] potrebbe essere il seguente:

Imports System.Collections
Imports System
Imports System.Data

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

Namespace istia.st.articles.web

     ' classe controller dell'applicazione web
    Public Class MainWebArticles
        Inherits System.Web.UI.Page

         ' campi privati
        Private articlesDomain As IArticlesDomain
        Private options As Hashtable

         ' caricamento della pagina
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
....
        End Sub

         ' metodi di elaborazione delle azioni

         ' elenco degli articoli
        Public Sub doListe()
...
        End Sub

         ' informazioni su un articolo
        Public Sub doInfos()
...
        End Sub

         ' acquisto di un articolo
        Public Sub doAchat()
...
        End Sub

         ' cancellazione di un acquisto
        Public Sub doRetirerAchat()
...
        End Sub

         ' visualizzazione del carrello
        Public Sub doPanier()
...
        End Sub

         ' acquisto dal carrello
        Public Sub doValidationPanier()
...
        End Sub

    End Class

End Namespace

Commenti:

  • la classe ha due campi privati che saranno condivisi tra i metodi - righe 15-16:
    • articlesDomain: il singleton di accesso al livello [domain]
    • opzioni: l'array dei dizionari delle opzioni di menu
  • la procedura [Page_Load]:
    • inizializzerà i due campi privati della classe
    • recupererà il parametro [action] dalla richiesta ed eseguirà il metodo di elaborazione di tale azione.

1.5.7.3. Il metodo [Page_Load]

Questo evento è il primo a verificarsi sulla pagina. Il codice è il seguente:

         ' caricamento della pagina
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' si verifica se l'applicazione si è avviata correttamente
            Dim erreurs As ArrayList = CType(Application.Item("erreurs"), ArrayList)
             ' in caso di errori, si invia la vista [erreurs]
            If erreurs.Count <> 0 Then
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {}
                Server.Transfer(CType(Application("urlErreurs"), String))
            End If
             ' si recupera l'oggetto di accesso alla classe di business
            articlesDomain = CType(Application.Item("articlesDomain"), IArticlesDomain)
             ' insieme alle opzioni di menu
            options = CType(Application.Item("options"), Hashtable)
             ' si recupera l'azione da eseguire
            Dim action As String = Request.QueryString("action")
            If action Is Nothing Then
                action = "liste"
            End If
             ' si esegue l'azione
            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

Commenti:

  • ad ogni caricamento della pagina, ci si assicura che l'inizializzazione dell'applicazione effettuata da [global.asax] sia avvenuta correttamente.
  • A tal fine, si recupera dal contesto dell’applicazione l’elenco degli errori inserito da [global.asax] - riga 4
  • se l'elenco non è vuoto, si visualizza la vista [ERREURS] - righe 6-10
  • si recupera il singleton [articlesDomain] inserito da [global.asax] nel contesto dell'applicazione e lo si memorizza nel campo privato [articlesDomain] affinché sia disponibile per i vari metodi della classe - riga 12
  • si esegue un'operazione analoga con l'array delle opzioni di menu - riga 14
  • si recupera il parametro [action] dalla richiesta - riga 16
  • si esegue il metodo corrispondente all’azione richiesta. Un’azione non prevista viene considerata come l’azione [liste] - righe 16-36

1.5.7.4. Elaborazione dell'azione [liste]

Si tratta di visualizzare l’elenco degli articoli:

Image

Il codice è il seguente:

         ' elenco degli articoli
        Public Sub doListe()
             ' gestione degli errori
            Dim erreurs As New ArrayList
             ' si richiede l'elenco degli articoli
            Dim articles As IList
            Try
                articles = articlesDomain.getAllArticles
            Catch ex As Exception
                 ' si registra l'errore
                erreurs.Add(ex.ToString)
            End Try
             ' Ci sono errori?
            If erreurs.Count <> 0 Then
                 ' visualizzazione vista [erreurs]
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
            End If
             ' visualizzazione vista [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

Commenti:

  • gli eventuali errori vengono inseriti in un [ArrayList] - riga 4
  • si richiede l'elenco degli articoli al singleton [articlesDomain] - righe 5-12
  • se si sono verificati errori, si invia la vista [ERREURS] - righe 13-19
  • altrimenti si invia la vista [LISTE] - righe 20-24

1.5.7.5. Elaborazione dell'azione [infos]

Il cliente ha richiesto informazioni su un determinato articolo:

Image

Il codice è il seguente:

         ' informazioni su un articolo
        Public Sub doInfos()
             ' gestione degli errori
            Dim erreurs As New ArrayList
             ' si recupera l'ID dell'articolo richiesto
            Dim strId As String = Request.QueryString("id")
             ' c'è qualcosa?
            If strId Is Nothing Then
                 ' Situazione anomala: si invia la pagina degli errori
                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
             ' si ha un numero intero?
            Dim id As Integer
            Try
                id = Integer.Parse(strId)
            Catch ex As Exception
                 ' non è normale - si invia la pagina di errore
                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
             ' si richiede l'articolo con ID chiave
            Dim unArticle As Article
            Try
                unArticle = articlesDomain.getArticleById(id)
            Catch ex As Exception
                 ' problema di accesso ai dati
                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
             ' È stato recuperato un articolo?
            If unArticle Is Nothing Then
                 ' l'articolo non esiste
                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
             ' l'articolo è presente - lo si inserisce nella sessione corrente
            Session.Item("article") = unArticle
             ' si prepara la visualizzazione
            context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
            context.Items("msg") = ""
            context.Items("qte") = ""
            Server.Transfer(CType(Application.Item("urlInfos"), String))
        End Sub

Commenti:

  • gli eventuali errori vengono inseriti in un [ArrayList] - riga 4
  • l'identificativo dell'articolo richiesto viene recuperato dalla richiesta - riga 6
  • tale identificativo viene verificato. Deve essere presente e deve essere un numero intero. In caso contrario, viene inviata la vista [ERREURS] con il messaggio di errore appropriato - righe 7-27
  • una volta verificato l'identificativo, l'articolo viene richiesto al singleton [articlesDomain]. Se si verifica un'eccezione, viene inviata la vista [ERREURS] - righe 29-39
  • se l’articolo non è stato trovato, viene inviata la vista [ERREURS] - righe 41-48
  • se l'articolo è stato trovato, viene inserito nella sessione dell'utente e quindi visualizzato nella vista [INFOS] - righe 50-56

1.5.7.6. Elaborazione dell'azione [achat]

Il cliente ha acquistato l'articolo visualizzato nella vista [INFOS].

Image

Il codice è il seguente:

         ' acquisto di un articolo
        Public Sub doAchat()
             ' acquisto di un articolo
             ' si recupera l'articolo che era nella sessione
            Dim unArticle As Article = CType(Session.Item("article"), Article)
             ' c'è qualcosa?
            If unArticle Is Nothing Then
                 ' Non è normale - si visualizza l'elenco degli articoli
                doListe()
            End If
             ' si recupera la quantità inserita
            Dim strQte As String = Request.Form("txtQte")
             ' c'è qualcosa?
            If strQte Is Nothing Then
                 ' Non è normale: si invia l'elenco degli articoli
                doListe()
            End If
             ' si ha un numero intero?
            Dim qte As Integer
            Try
                qte = Integer.Parse(strQte)
                If (qte <= 0) Then Throw New Exception
            Catch ex As Exception
                 ' non è normale - inviamo la pagina informativa con il messaggio di errore
                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
             ' tutto a posto - si aggiunge l'acquisto al carrello del cliente
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
            unPanier.ajouter(New Achat(unArticle, qte))
             ' viene visualizzato l'elenco degli articoli
            doListe()
        End Sub

Commenti:

  • l'articolo inserito nella sessione viene recuperato - riga 5
  • se non è presente (la sessione potrebbe essere scaduta), viene visualizzata la vista [LISTE] - righe 7-10
  • la quantità acquistata viene recuperata dalla query - riga 12
  • ne viene verificata la validità - righe 13-29
  • in caso di invalidità, a seconda dei casi, viene inviata la vista [LISTE] - riga 16 oppure la vista [INFOS] - righe 24-28
  • se tutto è regolare, l’acquisto viene registrato nel carrello - righe 31-32
  • quindi viene inviata la vista [LISTE] - riga 34

1.5.7.7. Elaborazione dell’azione [panier]

Il cliente ha effettuato diversi acquisti e chiede di visualizzare il carrello:

Image

Il codice è il seguente:

         ' visualizzazione del carrello
        Public Sub doPanier()
             'si recupera il carrello dalla sessione
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' carrello vuoto?
            If unPanier.achats.Count = 0 Then
                 ' visualizzazione del carrello vuoto
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlPanierVide"), String))
            End If
             ' visualizzazione del carrello non vuoto
            context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable), CType(options("validationpanier"), Hashtable)}
            Server.Transfer(CType(Application.Item("urlPanier"), String))
        End Sub

Commenti:

  • il carrello viene recuperato dalla sessione - riga 4. Qui non si verifica se effettivamente viene recuperato qualcosa. Sarebbe necessario farlo perché la sessione potrebbe essere scaduta.
  • Se il carrello è vuoto, si invia la vista [PANIERVIDE] - righe 6-10
  • altrimenti si invia la vista [PANIER] - righe 11-14

1.5.7.8. Elaborazione dell’azione [retirerachat]

Il cliente desidera rimuovere un articolo dal carrello:

Image

Il codice è il seguente:

         ' eliminazione di un acquisto
        Public Sub doRetirerAchat()
             'recupero del carrello dalla sessione
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' rimuoviamo l'articolo
            Try
                 ' recupera l'ID dell'articolo rimosso
                Dim idArticle As Integer = Integer.Parse(Request.QueryString("id"))
                 ' lo si rimuove dal carrello
                unPanier.enlever(idArticle)
            Catch ex As Exception
                 ' si visualizza l'elenco degli articoli
                doListe()
            End Try
             ' visualizza il carrello
            doPanier()
        End Sub

Commenti:

  • si recupera il carrello dalla sessione - riga 4. Qui non si verifica se è stato effettivamente recuperato qualcosa. Sarebbe necessario farlo perché la sessione potrebbe essere scaduta.
  • Si recupera dalla richiesta l'identificativo [id] dell'articolo da rimuovere - riga 8.
  • L'acquisto corrispondente viene rimosso dal carrello - riga 10
  • La validità dell'identificativo dell'articolo acquistato non è stata verificata qui. Se questo ha un tipo non valido, si verificherà un'eccezione che verrà gestita alle righe 11-13. Se è valido ma inesistente, il metodo [panier.enlever] - riga 10 - non esegue alcuna operazione.
  • Viene visualizzato il nuovo carrello - riga 16

1.5.7.9. Elaborazione dell'azione [validerpanier]

Il cliente desidera confermare il proprio carrello:

Image

Il codice è il seguente:

         ' acquisto dal carrello
        Public Sub doValidationPanier()
             ' all'inizio, nessun errore
            Dim erreurs As ArrayList
             'si recupera il carrello dalla sessione
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' si tenta di confermare il carrello
            Try
                articlesDomain.acheter(unPanier)
                 ' si registrano eventuali errori di acquisto
                erreurs = articlesDomain.erreurs
            Catch ex As Exception
                 ' si registra l'errore
                erreurs = New ArrayList
                erreurs.Add(String.Format("Erreur lors de la validation du panier [{0}]", ex.Message))
            End Try
             ' se ci sono errori, si passa alla pagina degli errori
            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
             ' tutto ok - si visualizza l'elenco degli articoli con un messaggio di conferma
            context.Items("message") = "Votre panier a été validé"
            doListe()
        End Sub

Commenti:

  • il carrello viene recuperato dalla sessione - riga 6. Qui non si verifica se effettivamente si è recuperato qualcosa. Sarebbe necessario farlo perché la sessione potrebbe essere scaduta.
  • Nel caso in cui la sessione sia scaduta, si avrà un puntatore [nothing] per il carrello e il metodo [acheter] - riga 9 - genererà un'eccezione e verrà inviata la vista [ERREURS]. Tuttavia, il messaggio di errore risulterà poco chiaro per l’utente.
  • Alle righe 8-16 si tenta di convalidare il carrello recuperato dalla sessione. Alcuni acquisti potrebbero non essere convalidati se la quantità richiesta supera la disponibilità dell’articolo richiesto. Questi casi vengono memorizzati dal metodo [acheter] in un elenco di errori che viene recuperato alla riga 11.
  • Se sono presenti errori, viene inviata la vista [ERREURS] - righe 18-23
  • altrimenti viene inviata la vista [LISTE] - righe 25-26

1.6. Conclusion

Qui abbiamo sviluppato un'applicazione basata su un modello MVC. Ad aprile 2005 non sembrano esistere "framework" professionali per lo sviluppo in MVC, come invece ce ne sono in Java (Struts, Spring, ...). Il progetto [Spring.net] dovrebbe proporne uno a breve. In attesa di tale sviluppo, il metodo precedente consente uno sviluppo MVC valido per applicazioni di medie dimensioni.