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.
- linguaggio VB.net: [Introduzione a VB.NET attraverso esempi (2004)];
- programmazione web in VB.net: [Sviluppo Web con ASP.NET 1.1 (2004)];
- utilizzo dell’aspect IoC di Spring: [Spring IoC per .NET (2005)];
- documentazione Spring.net: [Spring.NET | Homepage ]
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

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:
- 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.
- il controller elabora questa richiesta. A tal fine, potrebbe aver bisogno dell’aiuto del livello di business, ovvero il modello M nella struttura MVC.
- 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
- 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.
- 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:
- le classi di business
- le classi di accesso ai dati
- 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);
chiave primaria che identifica in modo univoco un articolo | |
nome dell'articolo | |
il suo prezzo | |
disponibilità attuale | |
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:
contenu | rôle | |
- [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 | |
- [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:

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:
- un costruttore che consente di impostare le 5 informazioni relative a un articolo: [id, nom, prix, stockactuel, stockminimum]
- proprietà pubbliche che consentono di leggere e scrivere le 5 informazioni.
- una verifica dei dati inseriti nell'articolo. In caso di dati errati, viene generata un'eccezione.
- 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:
restituisce tutti gli articoli presenti nella fonte dati | |
svuota la fonte dati | |
restituisce l'oggetto [Article] identificato dalla sua chiave primaria | |
consente di aggiungere un articolo alla fonte dati | |
consente di modificare un articolo della fonte dati | |
consente di eliminare un articolo dalla fonte dati | |
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:

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

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

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:

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:

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
restituisce l'elenco degli oggetti [Article] della fonte dati associata | |
restituisce l'oggetto [Article] identificato da [idArticle] | |
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 | |
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:
l'articolo acquistato | |
la quantità acquistata | |
l'importo dell'acquisto | |
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:
L'elenco degli acquisti del cliente - elenco di oggetti di tipo [Achat] | |
aggiunge un acquisto all'elenco degli acquisti | |
rimuove l'acquisto dell'articolo idAchat | |
l'importo totale degli acquisti nel carrello | |
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:
l'oggetto di accesso ai dati | |
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:
- 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:

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:

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:
![]() |
le classi di business [domain], le classi di accesso ai dati [dao] e la fonte dati | |
le pagine ASPX | |
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.aspx | Le viste sono raccolte nella cartella [vues] dell'applicazione ![]() | |
infos.aspx | ||
panier.aspx | ||
paniervide.aspx | ||
erreurs.aspx |
1.5.2. I controller
Come già indicato, il controller sarà composto da due elementi:
- [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
- [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 |
il cliente desidera l'elenco degli articoli | - richiede l'elenco degli articoli al livello di business | - [LISTE] - [ERREURS] | |
il cliente richiede informazioni su uno degli articoli visualizzati nella vista [LISTE] | - richiede l'articolo al livello di business | - [INFOS] - [ERREURS] | |
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 | |
il cliente desidera eliminare un acquisto dal proprio carrello | - recupera il carrello dalla sessione e lo modifica | - [PANIER] - [PANIERVIDE] - [ERREURS] | |
il cliente desidera visualizzare il proprio carrello | - recupera il carrello dalla sessione | - [PANIER] - [PANIERVIDE] - [ERREURS] | |
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:
- la modifica delle URL delle diverse viste
- la modifica delle classi che implementano le interfacce [IArticlesDao] e [IArticlesDomain]
- 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:

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
serve a ripetere un motivo HTML sui diversi elementi di una fonte di dati. I suoi diversi elementi sono i seguenti:
il motivo HTML da visualizzare prima che vengano visualizzati gli elementi della fonte dati | |
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 | |
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:
raggruppa le classi del livello di accesso ai dati | |
raggruppa le classi del livello di business | |
contiene le classi Spring che ci consentono di integrare i livelli web, domain e DAO | |
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:
![]() |
n° | 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:
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:
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:
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:
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:
oggetto Hashtable() - l'array delle opzioni del menu | |
ArrayList oggetti di tipo [Article] | |
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
![]() |
n° | type | nom | rôle |
componente utente | intestazione | visualizza l'intestazione | |
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 | |
etichetta | lblMessage | visualizza un messaggio |
Ricordiamo come procedere per definire queste proprietà:
- in Visual Studio, selezionare [DataGrid] per accedere alla relativa finestra delle proprietà:

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

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:
oggetto Hashtable() - l'array delle opzioni del menu | |
oggetto di tipo [Article] - articolo da visualizzare | |
oggetto String - messaggio da visualizzare in caso di errore sulla quantità | |
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à:

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
![]() |
n° | type | nom | rôle |
componente utente | intestazione | visualizza l'intestazione | |
letterale | litID | visualizza il numero dell'articolo | |
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 | |
HTML Invia | Invia il modulo | ||
HTML Input runat=server | txtQte | inserire la quantità acquistata | |
etichetta | lblMsgQte | eventuale messaggio di errore |
1.5.6.3.3. Il codice di presentazione [infos.aspx]
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]
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:
1.5.6.4. La vista [panier.aspx]
1.5.6.4.1. Introduction
Questa vista mostra il contenuto del carrello:

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:
oggetto Hashtable() - l'array delle opzioni del menu | |
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
![]() |
n° | type | nom | rôle |
componente utente | intestazione | visualizza l'intestazione | |
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 | |
etichetta | lblTotal | visualizza l'importo da pagare |
1.5.6.4.3. Il codice di presentazione [panier.aspx]
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]
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:

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:
oggetto Hashtable() - l'array delle opzioni del menu |
1.5.6.5.2. I componenti della pagina
![]() |
n° | type | nom | rôle |
componente utente | intestazione | visualizza l'intestazione |
1.5.6.5.3. Il codice di presentazione [paniervide.aspx]
Commenti:
- l'intestazione è inclusa alla riga 9
1.5.6.5.4. Il codice di controllo [paniervide.aspx.vb]
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:

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:
oggetto Hashtable() - la tabella delle opzioni del menu | |
ArrayList di oggetti [String] che rappresentano i messaggi di errore da visualizzare |
1.5.6.6.2. I componenti della pagina
![]() |
n° | type | nom | rôle |
componente utente | intestazione | visualizza l'intestazione | |
ripetitore | rptErreurs | Visualizza l'elenco degli errori |
1.5.6.6.3. Il codice di presentazione [erreurs.aspx]
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]
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:
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 |
GET /main?action=liste | - richiedere l'elenco degli articoli alla classe di business - visualizzarlo | [LISTE] o [ERREURS] | |
GET /main?action=infos&id=ID | - richiedere l'articolo con id=ID alla classe di business - visualizzarlo | [INFOS] o [ERREURS] | |
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] | |
GET /main?action=ritiro&id=ID | - rimuovere l'articolo con id=ID dall' elenco degli acquisti nel carrello sessione del cliente | [PANIER] | |
GET /main?action=carrello | - visualizza il carrello della sessione cliente | [PANIER] o [PANIERVIDE] | |
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:
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:
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:

Il codice è il seguente:
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:

Il codice è il seguente:
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].

Il codice è il seguente:
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:

Il codice è il seguente:
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:

Il codice è il seguente:
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:

Il codice è il seguente:
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.































