1. Parte 1
Il PDF del documento è disponibile |QUI|.
Gli esempi nel documento sono disponibili |QUI|.
1.1. Introduzione
Obiettivi di questo articolo:
- Scrivere un'applicazione web a tre livelli [interfaccia utente, logica di business, accesso ai dati]
- 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 — vedere Appendice, Sezione 3.1;
- server web Cassini per l'esecuzione — vedi Appendice, Sezione 3.2;
- Nunit per i test unitari — vedi Appendice, Sezione 3.4;
- Spring per l'integrazione e la configurazione dei livelli dell'applicazione web — vedi Appendice, Sezione 3.3;
Su una scala che va da principiante a intermedio ad avanzato, questo documento rientra nella categoria [intermedio-avanzato]. Per comprenderlo sono necessari diversi prerequisiti. Alcuni di questi si trovano nei documenti che ho scritto. In tali casi, li cito. Va da sé che questo è solo un suggerimento e che il lettore è libero di utilizzare le risorse che preferisce.
- Linguaggio VB.NET: [Introduzione a VB.NET attraverso esempi];
- Programmazione web in VB.NET: [Sviluppo web con ASP.NET 1.1];
- Utilizzo dell'aspetto IoC di Spring: [Spring IoC per .NET];
- Documentazione Spring.NET: [Spring.NET | Pagina iniziale]
Questo documento segue la stessa struttura di un documento scritto per Java [Architetture a 3 livelli e architetture MVC con Struts, Spring e Java]. Stiamo realizzando l'applicazione web MVC a tre livelli scritta in Java utilizzando VB.NET. Il punto che vogliamo sottolineare qui è che le piattaforme di sviluppo Java e .NET sono sufficientemente simili da consentire il riutilizzo delle competenze acquisite in uno di questi due ambiti nell'altro.
Non sembra esserci una soluzione di sviluppo ASP.NET MVC ampiamente riconosciuta. La soluzione seguente adotta il metodo introdotto nel documento [Sviluppo Web con ASP.NET 1.1]. Sebbene questo metodo abbia il pregio di utilizzare concetti comuni nello sviluppo J2EE, dovrebbe comunque essere considerato per quello che è: uno dei tanti metodi di sviluppo MVC. Non appena un metodo di sviluppo MVC in ASP.NET sarà ampiamente accettato, dovrebbe essere adottato. La versione .NET di Spring, attualmente in fase di sviluppo, potrebbe benissimo essere una prima soluzione.
1.2. L'applicazione webarticles
Qui presentiamo i componenti di un'applicazione web di e-commerce semplificata. Questa applicazione consentirà agli utenti web di:
- visualizzare un elenco di articoli da un database
- aggiungerne alcuni a un carrello online
- confermare il carrello. Questa conferma aggiornerà semplicemente l'inventario degli articoli acquistati nel database.
Le diverse schermate che verranno visualizzate all'utente saranno le seguenti:
- la visualizzazione "LIST", che mostra un elenco di articoli in vendita ![]() | - la vista [INFO], che fornisce ulteriori informazioni su un prodotto: ![]() |
- le schede [CARRELLO] e [SVUOTA CARRELLO], che mostrano il contenuto del carrello del cliente
![]() | ![]() |
- la vista [ERRORI], che segnala eventuali errori dell'applicazione

1.3. Architettura generale dell'applicazione
Vogliamo realizzare un'applicazione con la seguente struttura a tre livelli:
![]() |
- I tre livelli sono resi indipendenti tramite l'uso di interfacce
- L'integrazione dei diversi livelli è gestita da Spring
- Ogni livello ha il proprio namespace: web (livello UI), domain (livello business) e dao (livello di accesso ai dati).
L'applicazione seguirà un'architettura MVC (Model-View-Controller). Se ci riferiamo al diagramma a livelli sopra riportato, l'architettura MVC si inserisce in esso come segue:
![]() |
L'elaborazione di una richiesta del client segue questi passaggi:
- Il client invia una richiesta al controller. In questo caso, il controller è una pagina .aspx che svolge un ruolo specifico. Gestisce tutte le richieste del client. È il punto di ingresso dell'applicazione. È la C in MVC.
- Il controller elabora questa richiesta. Per farlo, potrebbe aver bisogno dell'assistenza del livello business, noto come la M nella struttura MVC.
- Il controller riceve una risposta dal livello di business. La richiesta del client è stata elaborata. Ciò può innescare diverse possibili risposte. Un classico esempio è
- 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 molto spesso di una pagina contenente elementi dinamici. Il controller fornisce questi alla vista.
- La vista viene inviata al cliente. Questa è la V in MVC.
1.4. Il Modello
Qui esaminiamo la M in MVC. Il modello è costituito dai seguenti elementi:
1.4.1. Il database
Il database contiene una sola tabella denominata ARTICLES. Questa tabella è stata generata utilizzando 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
);
/* constraints */
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_ID check (ID>0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_PRIX check (PRIX>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKACTUEL check (STOCKACTUEL>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKMINIMUM check (STOCKMINIMUM>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_NOM check (NOM<>'');
ALTER TABLE ARTICLES ADD CONSTRAINT UNQ_NOM UNIQUE (NOM);
/* primary key */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
chiave primaria che identifica in modo univoco un articolo | |
nome dell'elemento | |
il suo prezzo | |
scorte attuali | |
il livello di scorte al di sotto del quale è necessario effettuare un riordino |
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 del proprio file "assembly":
contenuto | ruolo | |
- [IArticlesDao]: l'interfaccia per accedere al livello [dao]. Questa è l'unica interfaccia visibile al livello [domain]. Non ne vede altre. - [Article]: classe che definisce un articolo - [ArticlesDaoArrayList]: classe di implementazione dell' dell'interfaccia [IArticlesDao] con un [ArrayList] | livello di accesso ai dati - si trova interamente all'interno del livello [DAO] dell'architettura a 3 livelli dell' dell'applicazione web | |
- [IArticlesDomain]: l'interfaccia per accedere al livello [domain]. Questa è l'unica interfaccia visibile al livello web. Non ne vede altre. - [AchatsArticles]: una classe che implementa [IArticlesDomain] - [Purchase]: una classe che rappresenta l'acquisto di un cliente - [Cart]: una classe che rappresenta il totale degli acquisti di un cliente | rappresenta il modello degli acquisti web web - risiede interamente nel livello [domain] dell' architettura a 3 livelli dell'applicazione web |
1.4.3. Il livello [DAO]
Il livello [DAO] contiene i seguenti elementi:
-
[IArticlesDao]: l'interfaccia per accedere al livello [dao]
-
[Article]: classe che definisce un articolo
-
[ArticlesDaoArrayList]: classe di implementazione per l'interfaccia [IArticlesDao] che utilizza una classe [ArrayList]
La struttura del progetto [Visual Studio] per il livello [dao] è la seguente:

Commenti:
- Il progetto [dao] è di tipo [libreria di classi]
- Le classi sono state inserite in una struttura ad albero a partire dalla 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
' private fields
Private _id As Integer
Private _nom As String
Private _prix As Double
Private _stockactuel As Integer
Private _stockminimum As Integer
' id article
Public Property id() As Integer
Get
Return _id
End Get
Set(ByVal Value As Integer)
If Value <= 0 Then
Throw New Exception("Le champ id [" + Value.ToString + "] est invalide")
End If
Me._id = Value
End Set
End Property
' item name
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
If Value Is Nothing OrElse Value.Trim.Equals("") Then
Throw New Exception("Le champ nom [" + Value + "] est invalide")
End If
Me._nom = Value
End Set
End Property
' item price
Public Property prix() As Double
Get
Return _prix
End Get
Set(ByVal Value As Double)
If Value < 0 Then
Throw New Exception("Le champ prix [" + Value.ToString + "] est invalide")
End If
Me._prix = Value
End Set
End Property
' current stock item
Public Property stockactuel() As Integer
Get
Return _stockactuel
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New Exception("Le champ stockActuel [" + Value.ToString + "] est invalide")
End If
Me._stockactuel = Value
End Set
End Property
' minimum stock item
Public Property stockminimum() As Integer
Get
Return _stockminimum
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New Exception("Le champ stockMinimum [" + Value.ToString + "] est invalide")
End If
Me._stockminimum = Value
End Set
End Property
' default builder
Public Sub New()
End Sub
' builder with properties
Public Sub New(ByVal id As Integer, ByVal nom As String, ByVal prix As Double, ByVal stockactuel As Integer, ByVal stockminimum As Integer)
Me.id = id
Me.nom = nom
Me.prix = prix
Me.stockactuel = stockactuel
Me.stockminimum = stockminimum
End Sub
' article identification method
Public Overrides Function ToString() As String
Return "[" + id.ToString + "," + nom + "," + prix.ToString + "," + stockactuel.ToString + "," + stockminimum.ToString + "]"
End Function
End Class
End Namespace
Questa classe fornisce:
- un costruttore per impostare le 5 informazioni relative a un articolo: [id, nome, prezzo, stockAttuale, stockMinimo]
- proprietà pubbliche per la lettura e la scrittura delle 5 informazioni.
- una convalida dei dati inseriti per l'articolo. Se i dati non sono validi, viene generata un'eccezione.
- un metodo toString che restituisce il valore di un elemento sotto forma di stringa. Ciò risulta 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
' list of all items
Function getAllArticles() As IList
' add an article
Function ajouteArticle(ByVal unArticle As Article) As Integer
' deletes an article
Function supprimeArticle(ByVal idArticle As Integer) As Integer
' modify an article
Function modifieArticle(ByVal unArticle As Article) As Integer
' search for an article
Function getArticleById(ByVal idArticle As Integer) As Article
' deletes all articles
Sub clearAllArticles()
' changes the stock of an item
Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
End Interface
End Namespace
I ruoli dei vari metodi nell'interfaccia sono i seguenti:
restituisce tutti gli elementi dall'origine dati | |
cancella l'origine dati | |
restituisce l'oggetto [Article] identificato dalla sua chiave primaria | |
consente di aggiungere un articolo all'origine dati | |
consente di modificare un articolo nell'origine dati | |
consente di eliminare un elemento dall'origine dati | |
consente di modificare la quantità di un articolo nell'origine dati |
L'interfaccia fornisce ai programmi client una serie di metodi definiti esclusivamente dalle loro firme. Non si occupa di come questi metodi saranno effettivamente implementati. Ciò garantisce flessibilità all'applicazione. Il programma client effettua chiamate a un'interfaccia piuttosto che a una specifica implementazione di tale interfaccia.
![]() |
La scelta di una specifica implementazione verrà effettuata 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 dati utilizzando un semplice oggetto [ArrayList]. Successivamente, presenteremo una soluzione basata su database.
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
' default builder
Public Sub New()
' we build a few items
For i As Integer = 1 To nbArticles
articles.Add(New Article(i, "article" + i.ToString, i * 10, i * 10, i * 10))
Next
End Sub
' list of all items
Public Function getAllArticles() As IList Implements IArticlesDao.getAllArticles
' returns the list of items
SyncLock Me
Return articles
End SyncLock
End Function
' delete all items
Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
' empty the list of items
SyncLock Me
articles.Clear()
End SyncLock
End Sub
' obtain an item identified by its key
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
' search for the item in the list
SyncLock Me
Dim ipos As Integer = posArticle(articles, idArticle)
If ipos <> -1 Then
Return CType(articles(ipos), Article)
Else
Return Nothing
End If
End SyncLock
End Function
' add an item to the list of items
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
' add the item to the list of items
SyncLock Me
' we check that it doesn't already exist
Dim ipos As Integer = posArticle(articles, unArticle.id)
If ipos <> -1 Then
Throw New Exception("L'article d'id [" + unArticle.id.ToString + "] existe déjà")
End If
' we add the article
articles.Add(unArticle)
' we return the result
Return 1
End SyncLock
End Function
' modify an article
Public Function modifieArticle(ByVal articleNouveau As Article) As Integer Implements IArticlesDao.modifieArticle
' modify an article
SyncLock Me
' we check that
Dim ipos As Integer = posArticle(articles, articleNouveau.id)
' if it doesn't exist
If ipos = -1 Then Return 0
' it exists - we modify it
articles(ipos) = articleNouveau
' we return the result
Return 1
End SyncLock
End Function
' delete an item identified by its key
Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
' article deletion
SyncLock Me
' we check that
Dim ipos As Integer = posArticle(articles, idArticle)
' if it doesn't exist
If ipos = -1 Then Return 0
' it exists - we remove it
articles.RemoveAt(ipos)
' we return the result
Return 1
End SyncLock
End Function
' change the stock of an item identified by its key
Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
' change the stock of an item
SyncLock Me
' we check that
Dim ipos As Integer = posArticle(articles, idArticle)
' if it doesn't exist
If ipos = -1 Then Return 0
' it exists - you modify your stock if you can
Dim unArticle As Article = CType(articles(ipos), Article)
' only change stock if it is sufficient
If unArticle.stockactuel + mouvement >= 0 Then
unArticle.stockactuel += mouvement
Return 1
Else
Return 0
End If
End SyncLock
End Function
' search for an item identified by its key
Private Function posArticle(ByVal listArticles As ArrayList, ByVal idArticle As Integer) As Integer
' returns the position of item [idArticle] in the list or -1 if not found
Dim unArticle As Article
For i As Integer = 0 To listArticles.Count - 1
unArticle = CType(listArticles(i), Article)
If unArticle.id = idArticle Then
Return i
End If
Next
' not found
Return -1
End Function
End Class
End Namespace
Commenti:
- L'origine dati è simulata dal campo privato [articles] di tipo [ArrayList]
- Il costruttore della classe crea 4 elementi nell'origine dati per impostazione predefinita.
- Tutti i metodi di accesso ai dati sono stati sincronizzati per evitare problemi di accesso simultaneo all'origine dati. In un dato momento, solo un thread ha accesso a un determinato metodo.
- Il metodo [posArticle] restituisce la posizione [0..N] nell'origine dati [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 [addArticle] aggiunge un articolo all'elenco degli articoli. Restituisce il numero di articoli inseriti: 1. Se l'articolo esisteva già, viene generata un'eccezione.
- Il metodo [modifyItem] consente di modificare un elemento esistente. Restituisce il numero di elementi modificati: 1 se l'elemento esisteva, 0 in caso contrario.
- Il metodo [deleteArticle] elimina un articolo esistente. Restituisce il numero di articoli eliminati: 1 se l'articolo esisteva, 0 in caso contrario.
- Il metodo [getAllArticles] restituisce un elenco di tutti gli articoli
- Il metodo [getArticleById] recupera un articolo identificato dal suo ID. Restituisce il valore [nothing] se l'articolo non esiste.
- Il codice non presenta alcuna difficoltà reale. Lasciamo al lettore il compito di 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 per il livello [dao]
In Java, le classi vengono testate utilizzando il framework [JUnit]. In .NET, il framework NUnit offre le stesse funzionalità di test unitari:

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

Commenti:
- Il progetto [tests] è una [libreria di classi]
- I test [NUnit] richiedono un riferimento all'assembly [nunit.framework.dll]
- la classe di test [NUnit] recupera un'istanza dell'oggetto in fase di test tramite Spring. Pertanto,
- nella cartella [bin], i file di classe Spring
- in [Riferimenti], un riferimento all'assembly [Spring-Core.dll] dalla cartella [bin]
- in [bin], un file di configurazione per Spring
- La classe di test richiede l'assembly [webarticles-dao.dll] dal livello [dao]. Questo è stato collocato nella cartella [bin] e il suo riferimento è stato aggiunto ai riferimenti del progetto.
Una classe di test [NUnit] richiede l'accesso alle classi nello spazio dei nomi [NUnit.Framework]. Pertanto, è inclusa 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 fornito se [NUnit] è stato installato. È sufficiente fare doppio clic sull'assembly per aggiungerlo al progetto:

La classe di test [NUnit] per il livello [DAO] potrebbe apparire così:
Imports System
Imports System.Collections
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports System.Threading
Imports Spring.Objects.Factory.Xml
Imports System.IO
Namespace istia.st.articles.tests
<TestFixture()> _
Public Class NunitTestArticlesArrayList
' the test object
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' retrieve an instance of the Spring object manufacturer
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
' request instantiation of articlesdao object
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
<Test()> _
Public Sub testGetAllArticles()
' visual check
listArticles()
End Sub
<Test()> _
Public Sub testClearAllArticles()
' delete all articles
articlesDao.clearAllArticles()
' all articles are requested
Dim articles As IList = articlesDao.getAllArticles
' verification: there must be 0
Assert.AreEqual(0, articles.Count)
End Sub
<Test()> _
Public Sub testAjouteArticle()
' delete all items
articlesDao.clearAllArticles()
' check: the item table must be empty
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' we add two items
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' check: there must be two items
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' visual check
listArticles()
End Sub
<Test()> _
Public Sub testSupprimeArticle()
' delete all items
articlesDao.clearAllArticles()
' check: the item table must be empty
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' we add two items
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' check: there must be 2 items
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' we delete article 4
articlesDao.supprimeArticle(4)
' check: there must be 1 item left
articles = articlesDao.getAllArticles
Assert.AreEqual(1, articles.Count)
' visual check
listArticles()
End Sub
<Test()> _
Public Sub testModifieArticle()
' delete all items
articlesDao.clearAllArticles()
' check
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' 2 items added
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' check
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' article 3 search
Dim unArticle As Article = articlesDao.getArticleById(3)
' check
Assert.AreEqual(unArticle.nom, "article3")
' research article 4
unArticle = articlesDao.getArticleById(4)
' check
Assert.AreEqual(unArticle.nom, "article4")
' modification article 4
articlesDao.modifieArticle(New Article(4, "article4", 44, 44, 44))
' check
unArticle = articlesDao.getArticleById(4)
Assert.AreEqual(unArticle.prix, 44, 0.000001)
' visual check
listArticles()
End Sub
<Test()> _
Public Sub testGetArticleById()
' article deletion
articlesDao.clearAllArticles()
' check
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' 2 items added
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' check
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' research article 3
Dim unArticle As Article = articlesDao.getArticleById(3)
' check
Assert.AreEqual(unArticle.nom, "article3")
' research article 4
unArticle = articlesDao.getArticleById(4)
' check
Assert.AreEqual(unArticle.nom, "article4")
End Sub
' screen listing
Private Sub listArticles()
Dim articles As IList = articlesDao.getAllArticles
For i As Integer = 0 To articles.Count - 1
Console.WriteLine(CType(articles(i), Article).ToString)
Next
End Sub
<Test()> _
Public Sub testArticleAbsent()
' delete all items
articlesDao.clearAllArticles()
' research article 1
Dim article As article = articlesDao.getArticleById(1)
' check
Assert.IsNull(article)
' modification of a non-existent item
Dim i As Integer = articlesDao.modifieArticle(New article(1, "1", 1, 1, 1))
' had to modify no line
Assert.AreEqual(i, 0)
' deletion of non-existent item
i = articlesDao.supprimeArticle(1)
' had to delete no line
Assert.AreEqual(0, i)
End Sub
<Test()> _
Public Sub testChangerStockArticle()
' delete all items
articlesDao.clearAllArticles()
' add an item
Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
Assert.AreEqual(nbArticles, 1)
' add an item
nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
Assert.AreEqual(nbArticles, 1)
' creation of 100 threads
Dim taches(99) As Thread
For i As Integer = 0 To taches.Length - 1
' create thread i
taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
' set the thread name
taches(i).Name = "tache_" & i
' start execution of thread i
taches(i).Start()
Next
' wait for all threads to finish
For i As Integer = 0 To taches.Length - 1
taches(i).Join()
Next
' checks - item 3 must have a stock of 1
Dim unArticle As Article = articlesDao.getArticleById(3)
Assert.AreEqual(unArticle.nom, "article3")
Assert.AreEqual(1, unArticle.stockactuel)
' item 4 stock is decremented
Dim erreur As Boolean = False
Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
' check: its stock must not have changed
Assert.AreEqual(0, nbLignes)
' visual check
listArticles()
End Sub
Public Sub décrémente()
' thread launched
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
' thread decrements stock
articlesDao.changerStockArticle(3, -1)
' thread terminated
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
End Sub
End Class
End Namespace
Commenti:
- Volevamo scrivere un programma di test per l'interfaccia [IArticlesDao] che fosse indipendente dalla sua classe di implementazione. Pertanto, abbiamo utilizzato Spring per nascondere il nome della classe di implementazione al programma di test.
- Il metodo <Setup()> recupera da Spring un riferimento all'oggetto [articlesdao] da testare. Questo è 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 specifica il nome della classe di implementazione [istia.st.articles.dao.ArticlesDaoArrayList] per l'interfaccia [IArticlesDao] e dove trovarla [webarticles-dao.dll]. Poiché l'istanziazione non richiede alcun parametro, qui non ne viene definito nessuno.
- 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()
' delete all items
articlesDao.clearAllArticles()
' add an item
Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
Assert.AreEqual(nbArticles, 1)
' add an item
nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
Assert.AreEqual(nbArticles, 1)
' creation of 100 threads
Dim taches(99) As Thread
For i As Integer = 0 To taches.Length - 1
' create thread i
taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
' set the thread name
taches(i).Name = "tache_" & i
' start execution of thread i
taches(i).Start()
Next
' wait for all threads to finish
For i As Integer = 0 To taches.Length - 1
taches(i).Join()
Next
' checks - item 3 must have a stock of 1
Dim unArticle As Article = articlesDao.getArticleById(3)
Assert.AreEqual(unArticle.nom, "article3")
Assert.AreEqual(1, unArticle.stockactuel)
' item 4 stock is decremented
Dim erreur As Boolean = False
Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
' check: its stock must not have changed
Assert.AreEqual(0, nbLignes)
' visual check
listArticles()
End Sub
Questo serve a testare l'accesso simultaneo all'origine dati. Il metodo responsabile dell'aggiornamento delle scorte è il seguente:
Public Sub décrémente()
' thread launched
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
' thread decrements stock
articlesDao.changerStockArticle(3, -1)
' thread terminated
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
End Sub
Diminuisce di una unità lo stock dell'articolo n. 3. Se ci riferiamo al codice del metodo [testChangerStockArticle], vediamo che:
- lo stock dell'articolo n. 3 è inizializzato a 101
- i 100 thread decrementeranno ciascuno questa scorta di una unità
- dovremmo quindi avere una quantità pari a 1 al termine dell'esecuzione di tutti i thread
Inoltre, sempre all'interno di questo metodo, proviamo a impostare lo stock dell'articolo n. 4 su un valore negativo. Questo dovrebbe fallire.
Per testare il livello [dao], generiamo la DLL [tests-webarticles-dao.dll] nella cartella [bin] del progetto [tests]:
![]() | ![]() |
Quindi, utilizzando l'applicazione [Nunit-Gui], carichiamo questa DLL ed eseguiamo i test:

Nella finestra a sinistra è riportato l'elenco dei metodi sottoposti a test. Il colore del puntino che precede il nome di ciascun metodo indica se il test è stato superato (verde) o fallito (rosso). I lettori che visualizzano questo documento sullo schermo noteranno che tutti i test sono stati superati. Considereremo quindi il livello [dao] operativo.
1.4.4. Il livello [domain]
Il livello [domain] contiene i seguenti elementi:
-
[IArticlesDomain]: l'interfaccia per accedere al livello [domain]
-
[Purchase]: classe che definisce un acquisto
-
[ShoppingCart]: classe che definisce un carrello
-
[ProductPurchases]: la classe che implementa l'interfaccia [IArticlesDomain]
La struttura della soluzione [Visual Studio] per il livello [domain] è la seguente:

Commenti:
- Il progetto [domain] è di tipo [libreria di classi]
- Le classi sono state inserite in una struttura ad albero a partire dalla cartella [istia]. Si trovano tutte nello spazio dei nomi [istia.st.articles.domain].
- La DLL del livello [dao] è stata collocata nella cartella [bin] del nuovo progetto. Inoltre, questa DLL è stata aggiunta come riferimento al progetto.
1.4.4.1. L'interfaccia [IArticlesDomain]
L'interfaccia [IArticlesDomain] disaccoppia il livello [business] dal livello [web]. Quest'ultimo accede al livello [business/domain] tramite questa interfaccia senza preoccuparsi della classe che la implementa effettivamente. L'interfaccia definisce le seguenti azioni per accedere al livello business:
Imports Article = istia.st.articles.dao.Article
Namespace istia.st.articles.domain
Public Interface IArticlesDomain
' methods
Sub acheter(ByVal panier As Panier)
Function getAllArticles() As IList
Function getArticleById(ByVal idArticle As Integer) As Article
ReadOnly Property erreurs() As ArrayList
End Interface
End Namespace
restituisce l'elenco degli oggetti [Article] dall'origine dati associata | |
restituisce l'oggetto [Article] identificato da [idArticle] | |
elabora il carrello del cliente sottraendo la quantità acquistata dalle scorte degli articoli acquistati - potrebbe fallire se le scorte sono insufficienti | |
restituisce l'elenco degli errori verificatisi - vuoto se non ci sono errori |
1.4.4.2. La classe [Purchase]
La classe [Purchase] rappresenta un acquisto effettuato da un cliente:
Imports istia.st.articles.dao
Namespace istia.st.articles.domain
Public Class Achat
' private fields
Private _article As article
Private _qte As Integer
' default builder
Public Sub New()
End Sub
' builder with parameters
Public Sub New(ByVal unArticle As article, ByVal qte As Integer)
' we go through the properties
Me.article = unArticle
Me.qte = qte
End Sub
' item purchased
Public Property article() As article
Get
Return _article
End Get
Set(ByVal Value As article)
_article = Value
End Set
End Property
' qty purchased
Public Property qte() As Integer
Get
Return _qte
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New Exception("Quantité [" + Value.ToString + "] invalide")
End If
_qte = Value
End Set
End Property
' total purchase
Public ReadOnly Property totalAchat() As Double
Get
Return _qte * _article.prix
End Get
End Property
' identity
Public Overrides Function ToString() As String
Return "[" + _article.ToString + "," + _qte.ToString + "]"
End Function
End Class
End Namespace
Commenti:
- La classe [Purchase] presenta le seguenti proprietà e metodi:
l'articolo acquistato | |
la quantità acquistata | |
l'importo dell'acquisto | |
rappresentazione stringa dell'oggetto |
- Dispone di un costruttore che inizializza le proprietà [item, qty] che definiscono un acquisto.
1.4.4.3. La classe [Cart]
La classe [Cart] rappresenta il totale degli acquisti del cliente:
Namespace istia.st.articles.domain
Public Class Panier
' private fields
Private _achats As New ArrayList
Private _totalPanier As Double = 0
' default builder
Public Sub New()
End Sub
' list of purchases
Public ReadOnly Property achats() As ArrayList
Get
Return _achats
End Get
End Property
' total purchases
Public ReadOnly Property totalPanier() As Double
Get
Return _totalPanier
End Get
End Property
' methods
Public Sub ajouter(ByVal unAchat As Achat)
' find out if the purchase already exists
Dim iAchat As Integer = posAchat(unAchat.article.id)
If iAchat <> -1 Then
' we found
Dim achatCourant As Achat = CType(_achats(iAchat), Achat)
achatCourant.qte += unAchat.qte
Else
' we didn't find
_achats.Add(unAchat)
End If
' increment the basket total
_totalPanier += unAchat.totalAchat
End Sub
' remove a purchase
Public Sub enlever(ByVal idAchat As Integer)
' we're looking to buy
Dim iachat As Integer = posAchat(idAchat)
' if found, remove
If iachat <> -1 Then
Dim achatCourant As Achat = CType(_achats(iachat), Achat)
' remove from basket
_achats.RemoveAt(iachat)
' decrement the basket total
_totalPanier -= achatCourant.totalAchat
End If
End Sub
Private Function posAchat(ByVal idArticle As Integer) As Integer
' search for a purchase in the purchase list
' returns its position in the list or -1 if not found
Dim achatCourant As Achat
Dim trouvé As Boolean = False
Dim i As Integer = 0
While Not trouvé AndAlso i < _achats.Count
' regular purchase
achatCourant = CType(_achats(i), Achat)
' comparison with article searched
If achatCourant.article.id = idArticle Then
Return i
End If
'next purchase
i += 1
End While
' not found
Return -1
End Function
' identity function
Public Overrides Function ToString() As String
Return _achats.ToString
End Function
End Class
End Namespace
Commenti:
- La classe [ShoppingCart] presenta le seguenti proprietà e metodi:
la lista della spesa del cliente - un elenco di oggetti di tipo [Purchase] | |
aggiunge un acquisto all'elenco degli acquisti | |
rimuove l'acquisto con idAcquisto | |
l'importo totale degli acquisti nel carrello | |
restituisce la stringa che rappresenta il carrello |
- Il metodo [posAchat] è un metodo di utilità che restituisce la posizione nell'elenco degli acquisti di un acquisto identificato dal numero dell'articolo. L'elenco degli acquisti è gestito in modo tale che un articolo acquistato più volte occupi una sola posizione nell'elenco. Pertanto, un acquisto può essere identificato dal numero dell'articolo. Il metodo [posAchat] restituisce -1 se l'acquisto ricercato non esiste.
- Il metodo [add] aggiunge un nuovo acquisto all'elenco degli acquisti. Questo aggiunge una nuova voce all'elenco degli acquisti se l'articolo acquistato non era già presente nell'elenco, oppure incrementa la quantità acquistata se era già presente.
- Il metodo [remove] consente di rimuovere un acquisto identificato da un numero dall'elenco degli acquisti. Se l'acquisto non esiste, il metodo non esegue alcuna operazione.
- L'importo totale dell'acquisto [totalPanier] viene aggiornato man mano che gli articoli vengono aggiunti e rimossi.
1.4.4.4. La classe [PurchaseItems]
L'interfaccia [IArticlesDomain] sarà implementata dalla seguente classe [PurchaseItems]:
Imports istia.st.articles.dao
Namespace istia.st.articles.domain
Public Class AchatsArticles
Implements IArticlesDomain
'private fields
Private _articlesDao As IArticlesDao
Private _erreurs As ArrayList
' manufacturer
Public Sub New(ByVal articlesDao As IArticlesDao)
_articlesDao = articlesDao
End Sub
' error list
Public ReadOnly Property erreurs() As ArrayList Implements IArticlesDomain.erreurs
Get
Return _erreurs
End Get
End Property
' list of items
Public Function getAllArticles() As IList Implements IArticlesDomain.getAllArticles
' list of all items
Try
Return _articlesDao.getAllArticles
Catch ex As Exception
_erreurs = New ArrayList
_erreurs.Add("Erreur d'accès aux données : " + ex.Message)
End Try
End Function
' get an item identified by its number
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDomain.getArticleById
' a special item
Try
Return _articlesDao.getArticleById(idArticle)
Catch ex As Exception
_erreurs = New ArrayList
_erreurs.Add("Erreur d'accès aux données : " + ex.Message)
End Try
End Function
' buy a basket
Public Sub acheter(ByVal panier As Panier) Implements IArticlesDomain.acheter
' basket purchase - stocks of purchased items must be decremented
_erreurs = New ArrayList
Dim achat As achat
Dim achats As ArrayList = panier.achats
For i As Integer = achats.Count - 1 To 0 Step -1
' decrement stock item i
achat = CType(achats(i), achat)
Try
If _articlesDao.changerStockArticle(achat.article.id, -achat.qte) = 0 Then
' we couldn't do the operation
_erreurs.Add("L'achat " + achat.ToString + " n'a pu se faire - Vérifiez les stocks")
Else
' the transaction has been completed - the purchase is removed from the basket
panier.enlever(achat.article.id)
End If
Catch ex As Exception
_erreurs = New ArrayList
_erreurs.Add("Erreur d'accès aux données : " + ex.Message)
End Try
Next
End Sub
End Class
End Namespace
Commenti:
- Questa classe implementa i quattro metodi dell'interfaccia [IArticlesDomain]. Ha due campi privati:
l'oggetto di accesso ai dati | |
l'elenco dei possibili errori. È accessibile tramite la proprietà pubblica [errors] |
- 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 presenti nel livello [dao]
- Il metodo [buy] convalida l'acquisto di un carrello. Questa convalida comporta semplicemente la diminuzione delle scorte degli articoli acquistati. Un articolo può essere acquistato solo se le scorte sono sufficienti. In caso contrario, l'acquisto viene rifiutato: l'articolo rimane nel carrello e viene segnalato un errore nell'elenco [errors]. Un acquisto convalidato viene rimosso dal carrello e le scorte dell'articolo corrispondente vengono diminuite 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 per il livello [domain]
La struttura del progetto di test di Visual Studio è la seguente:

Commenti:
- Il progetto [tests] è di tipo [libreria di classi]
- i test [NUnit] richiedono un riferimento all'assembly [nunit.framework.dll]
- La classe di test [NUnit] recupera un'istanza dell'oggetto in fase di test tramite Spring. Pertanto,
- nella cartella [bin], i file di classe Spring
- in [Riferimenti], un riferimento all'assembly [Spring-Core.dll] dalla cartella [bin]
- in [bin], un file di configurazione per Spring
- La classe di test richiede l'assembly [webarticles-dao.dll] dal livello [dao] e l'assembly [webarticles-domain.dll] dal livello [domain]. Questi sono stati collocati nella cartella [bin] e i relativi riferimenti sono stati aggiunti ai riferimenti del progetto.
Una classe di test NUnit per il livello [domain] potrebbe apparire così:
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports istia.st.articles.domain
Imports Spring.Objects.Factory.Xml
Imports System.IO
Namespace istia.st.articles.tests
<TestFixture()> _
Public Class NunitTestArticlesDomain
' the test object
Private articlesDomain As IArticlesDomain
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' retrieve an instance of the Spring object manufacturer
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
' request instantiation of the articles dao object
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
' then the articlesdomain aobject
articlesDomain = CType(factory.GetObject("articlesdomain"), IArticlesDomain)
End Sub
<Test()> _
Public Sub getAllArticles()
' visual check
listArticles()
End Sub
<Test()> _
Public Sub getArticleById()
' article deletion
articlesDao.clearAllArticles()
' check
Dim articles As IList = articlesDomain.getAllArticles
Assert.AreEqual(0, articles.Count)
' 2 items added
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' check
articles = articlesDomain.getAllArticles
Assert.AreEqual(2, articles.Count)
' research article 3
Dim unArticle As Article = articlesDomain.getArticleById(3)
' check
Assert.AreEqual(unArticle.nom, "article3")
' research article 4
unArticle = articlesDao.getArticleById(4)
' check
Assert.AreEqual(unArticle.nom, "article4")
End Sub
<Test()> _
Public Sub acheterPanier()
' article deletion
articlesDao.clearAllArticles()
' check
Dim articles As IList = articlesDomain.getAllArticles
Assert.AreEqual(0, articles.Count)
' 2 items added
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' check
articles = articlesDomain.getAllArticles
Assert.AreEqual(2, articles.Count)
' create a basket with two purchases
Dim panier As New panier
panier.ajouter(New Achat(New Article(3, "article3", 30, 30, 3), 10))
panier.ajouter(New Achat(New Article(4, "article4", 40, 40, 4), 10))
' checks
Assert.AreEqual(700, panier.totalPanier, 0.000001)
Assert.AreEqual(2, panier.achats.Count)
' shopping cart validation
articlesDomain.acheter(panier)
' checks
Assert.AreEqual(0, articlesDomain.erreurs.Count)
Assert.AreEqual(0, panier.achats.Count)
' research article 3
Dim unArticle As Article = articlesDomain.getArticleById(3)
' check
Assert.AreEqual(unArticle.stockactuel, 20)
' research article 4
unArticle = articlesDao.getArticleById(4)
' check
Assert.AreEqual(unArticle.stockactuel, 30)
' new basket
panier.ajouter(New Achat(New Article(3, "article3", 30, 30, 3), 100))
' shopping cart validation
articlesDomain.acheter(panier)
' checks
Assert.AreEqual(1, articlesDomain.erreurs.Count)
' research article 3
unArticle = articlesDomain.getArticleById(3)
' check
Assert.AreEqual(unArticle.stockactuel, 20)
End Sub
<Test()> _
Public Sub testRetirerAchats()
' delete contents of ARTICLES
articlesDao.clearAllArticles()
' reads the ARTICLES table
Dim articles As IList = articlesDao.getAllArticles()
Assert.AreEqual(0, articles.Count)
' insertion
Dim article3 As New Article(3, "article3", 30, 30, 3)
articlesDao.ajouteArticle(article3)
Dim article4 As New Article(4, "article4", 40, 40, 4)
articlesDao.ajouteArticle(article4)
' reads the ARTICLES table
articles = articlesDomain.getAllArticles()
Assert.AreEqual(2, articles.Count)
' create a basket with two purchases
Dim monPanier As New Panier
monPanier.ajouter(New Achat(article3, 10))
monPanier.ajouter(New Achat(article4, 10))
' checks
Assert.AreEqual(700.0, monPanier.totalPanier, 0.000001)
Assert.AreEqual(2, monPanier.achats.Count)
' add a previously purchased item
monPanier.ajouter(New Achat(article3, 10))
' checks
' the total must be increased to 1000
Assert.AreEqual(1000.0, monPanier.totalPanier, 0.000001)
' always 2 items in the basket
Assert.AreEqual(2, monPanier.achats.Count)
' qty item 3 increased to 20
Dim unAchat As Achat = CType(monPanier.achats(0), Achat)
Assert.AreEqual(20, unAchat.qte)
' article 3 is removed from the basket
monPanier.enlever(3)
' checks
' the total must be increased to 400
Assert.AreEqual(400.0, monPanier.totalPanier, 0.000001)
' 1 item only in basket
Assert.AreEqual(1, monPanier.achats.Count)
' this must be article no. 4
Assert.AreEqual(4, CType(monPanier.achats(0), Achat).article.id)
End Sub
' screen listing
Private Sub listArticles()
Dim articles As IList = articlesDomain.getAllArticles
For i As Integer = 0 To articles.Count - 1
Console.WriteLine(CType(articles(i), Article).ToString)
Next
End Sub
End Class
End Namespace
Commenti:
- Volevamo scrivere un programma di test per l'interfaccia [IArticlesDomain] che fosse indipendente dalla sua classe di implementazione. Pertanto, abbiamo utilizzato Spring per nascondere il nome della classe di implementazione al programma di test.
- Il metodo con 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 indica:
- (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 alcun parametro, qui non ne viene definito nessuno.
- 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]. Qui, questo è definito come il singleton [articlesdao] definito in precedenza.
- La classe di test ottiene un'istanza della classe sotto test [articlesdomain] e un'istanza della classe di accesso ai dati [articlesdao]. Quest'ultimo punto è controverso. La classe di test, in teoria, non dovrebbe aver bisogno di accedere al livello [dao], di cui non dovrebbe nemmeno essere a conoscenza. Qui abbiamo ignorato questa "etica", che, se seguita, ci avrebbe richiesto di creare nuovi metodi nella nostra interfaccia [IArticlesDomain].
Per testare il livello [domain], generiamo la DLL [tests-webarticles-domain.dll] nella cartella [bin] del progetto [tests]:
![]() | ![]() |
Quindi, utilizzando l'applicazione [Nunit-Gui], carichiamo questa DLL ed eseguiamo i test:

I lettori che visualizzano questo documento sullo schermo vedranno che tutti i test hanno avuto esito positivo. Considereremo ora il 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 DLL [webarticles-dao.dll, webarticles-domain.dll]. Possiamo passare all'ultimo livello, il livello [web], che contiene il controller C e le viste V. Prenderemo innanzitutto in considerazione un metodo presentato nel documento [Sviluppo Web con ASP.NET 1.1]
- Il controller C è implementato 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 [dominio], le classi di accesso ai dati [DAO] e la fonte dati | |
le pagine ASPX | |
Tutte le richieste HTTP dei client passano attraverso i seguenti due controller: global.asax: gestisce gli eventi relativi all'avvio iniziale dell'applicazione main.aspx: elabora singolarmente ogni richiesta del client |
1.5.1. Le viste
Le viste corrispondono a quelle presentate all'inizio di questo documento:
list.aspx | Le viste si trovano nella cartella [views] dell'applicazione ![]() | |
info.aspx | ||
carrello.aspx | ||
empty-cart.aspx | ||
errori.aspx |
1.5.2. I controller
Come accennato, il controller sarà composto da due elementi:
- [global.asax, global.asax.vb]: utilizzati principalmente per inizializzare l'applicazione e impostare il contesto per tutti i dati da condividere tra i diversi client
- [main.aspx, main.aspx.vb]: il controller vero e proprio, che gestisce le richieste HTTP provenienti dai client.
Le varie richieste dei client saranno inviate 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 client desidera l'elenco degli elementi | - richiede l'elenco degli elementi dal livello professione | - [LIST] - [ERRORI] | |
Il cliente richiede informazioni su uno degli elementi visualizzati nella vista [LIST] | - richiede l'elemento dal livello di business | - [INFO] - [ERRORI] | |
Il cliente acquista un articolo | - richiede l'articolo dal livello aziendale e lo aggiunge al carrello del cliente | - [INFO] in caso di errore nella quantità - [LIST] se non ci sono errori | |
il cliente desidera rimuovere un articolo dal carrello | - recupera il carrello dalla sessione e lo modifica | - [CARRELLO] - [SVUOTA CARRELLO] - [ERRORI] | |
Il cliente desidera visualizzare il proprio carrello | - recupera il carrello dalla sessione | - [CARRELLO] - [CARRELLO VUOTO] - [ERRORI] | |
Il cliente ha terminato gli acquisti e procede al checkout | - Aggiorna il database con i livelli di stock degli articoli acquistati - svuota il carrello del cliente dagli articoli il cui acquisto è stato confermato | - [LISTA] - [ERRORI] |
1.5.3. Configurazione dell'applicazione
Cercheremo di configurare l'applicazione in modo da renderla il più flessibile possibile rispetto a modifiche quali:
- modifiche agli URL delle varie viste
- modifiche alle classi che implementano le interfacce [IArticlesDao] e [IArticlesDomain]
- modifiche al DBMS, al database o alla tabella degli articoli
1.5.3.1. Modifiche agli URL
I nomi degli URL delle viste saranno inseriti nel file di configurazione [web.config] dell'applicazione 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. Modifica delle classi che implementano le interfacce
In linea con i principi delle 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, non classi concrete
- il codice di un livello non istanzia mai la classe di un altro livello per poterla utilizzare. Richiede semplicemente un'istanza dell'implementazione dell'interfaccia da uno strumento esterno — in questo caso, Spring — per il livello che desidera utilizzare. Per farlo, sappiamo che non ha bisogno di conoscere il nome della classe di implementazione, ma solo il nome del singleton Spring per il quale vuole un riferimento.
Nella nostra applicazione, Spring verrà configurato nel file [web.config] dell'applicazione web come segue:
<?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 [business], una classe nel livello [web] può 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 un oggetto di tipo [istia.st.articles.dao.ArticlesDaoArrayList]. Spring istanzierà quindi tale oggetto. Al termine dell'operazione, il livello [web] che ha richiesto il singleton [articlesDomain] dispone dell'intera catena che lo collega alla fonte dati:
![]() |
1.5.3.3. Modifiche relative al DBMS o al database
Questo punto verrà ignorato in questa sede poiché stiamo lavorando in un'applicazione di test senza un DBMS. Affronteremo l'implementazione di un livello [dao] basato su un DBMS in una sezione successiva.
1.5.4. La libreria di tag <asp:>
Consideriamo la vista [ERRORS], che visualizza un elenco di errori:

La vista [ERRORS] è responsabile della visualizzazione di un elenco di errori che il controller [main.aspx] ha inserito nel contesto della richiesta con il nome [context.Items("errors")]. Esistono diversi modi per scrivere una pagina di questo tipo. In questa sede, ci interessa solo la parte relativa alla visualizzazione degli errori.
Ricordiamo che una pagina ASPX presenta un livello di presentazione HTML e un livello di codice .NET che prepara i dati che il livello di presentazione deve visualizzare. Queste due parti possono trovarsi nello stesso file [aspx] (soluzione WebMatrix) o in due file: [aspx] per la presentazione, [aspx.vb] per il codice. Quest'ultima soluzione è quella utilizzata da Visual Studio. A complicare le cose, il livello di presentazione HTML può contenere anche codice .NET, il che tende a rendere meno netta la separazione tra i livelli [controller] e [presentation] della vista. Questo approccio è generalmente fortemente sconsigliato. Rimuovere tutto il codice dalla sezione [presentation] richiedeva la creazione di librerie di tag. Queste "nascondono" il codice sotto le spoglie di tag simili a quelli HTML. Presentiamo due possibili soluzioni per la pagina [ERRORS].
La nostra prima soluzione utilizza codice .NET nella sezione [presentation] della pagina. La pagina ASPX recupera l'elenco degli errori presenti nella richiesta nella sua sezione controller [errors.aspx.vb]:
Protected erreurs As ArrayList
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
' error recovery
erreurs = CType(context.Items("erreurs"), ArrayList)
End Sub
quindi visualizzarli nella sezione [presentazione, errors.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 come controllo server che è possibile trascinare nel modulo di progettazione. Se si scrive il codice ASPX manualmente, è possibile fare riferimento alla libreria di tag.
Con la libreria di tag <asp:>, il codice ASPX per la precedente vista [ERRORS] 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
Il tag viene utilizzato per ripetere un modello HTML nei vari elementi di un'origine dati. I suoi vari elementi sono i seguenti:
il modello HTML da visualizzare prima degli elementi dell'origine dati | |
il modello HTML da ripetere per ogni elemento nell'origine dati. L'espressione [<%# Container.DataItem %>] viene utilizzata per visualizzare il valore dell'elemento corrente nell'origine dati | |
il modello HTML da visualizzare dopo che sono stati visualizzati gli elementi dell'origine dati |
L'origine dati è associata al tag, solitamente nella sezione [controller] 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
..
' link errors to rptErreurs
With rptErreurs
.DataSource = context.Items("erreurs")
.DataBind()
End With
End Sub
Questo binding può essere effettuato anche durante la progettazione della pagina se l'origine dati è già nota, come ad esempio un database esistente.
Nelle nostre viste useremo un altro tag: <asp:datagrid>, che ci permette di visualizzare un'origine dati sotto forma di tabella.
1.5.5. Struttura della soluzione Visual Studio per l'applicazione [webarticles]
Un'applicazione web è un puzzle composto da molti pezzi. Dotarla di un'architettura MVC generalmente aumenta il numero di questi pezzi. La struttura dell'applicazione [webarticles] in [Visual Studio] è la seguente:
![]() | ![]() | ![]() |
![]() |
Commenti:
- Il progetto [web] è di tipo [Class Library] e non di tipo [ASP.NET Web Application], come ci si potrebbe logicamente aspettare. Il tipo [ASP.NET Web Application] richiede la presenza del server web IIS sul computer di sviluppo o su un computer remoto. Il server IIS non è incluso di default sui computer con Windows XP Home Edition. Tuttavia, molti PC vengono venduti con questa versione. Per consentire ai lettori che utilizzano Windows XP di implementare l'applicazione in esame, useremo il server web Cassini (vedi appendice), disponibile gratuitamente da Microsoft, e sostituiremo il progetto [Applicazione Web ASP.NET] con un progetto [Libreria di classi]. Ciò comporta alcuni inconvenienti, che sono spiegati nelle appendici.
- Le DLL utilizzate dall'applicazione sono le seguenti:
contiene le classi del livello di accesso ai dati | |
contiene le classi del livello di business | |
contiene le classi Spring che ci consentono di integrare i livelli web, di dominio e DAO | |
classi di logging — utilizzate da Spring |
Queste DLL vengono collocate nella cartella [bin] e aggiunte ai riferimenti del progetto.
1.5.6. Le viste ASPX
Come raccomandato in precedenza, useremo la libreria di tag <asp:> nelle nostre viste ASPX.
1.5.6.1. Il componente utente [entete.ascx]
Per garantire la coerenza tra le diverse viste, queste condivideranno la stessa intestazione, che mostra il nome dell'applicazione insieme al menu:
![]() | ![]() |
Il menu è dinamico e viene impostato dal controller. Il controller include un attributo chiave "actions" nella richiesta inviata alla pagina ASPX, con un valore associato costituito da un array di elementi 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 associato all'opzione del menu
- link: il testo del menu
Trasformeremo l'intestazione in un controllo utente. Un controllo utente incapsula una sezione di una pagina (layout e codice associato) in un componente che può poi essere riutilizzato in altre pagine. In questo caso, vogliamo riutilizzare il componente [entete] in altre viste dell'applicazione. Il codice di presentazione sarà 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:
![]() |
No. | tipo | nome | ruolo |
1 | ripetitore | rptMenu fonte 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 nell'origine dati associata al repeater è un dizionario con due chiavi: href (riga 9) e link (riga 10)
Il codice di controllo associato sarà il seguente:
Commenti:
- Il componente [EnteteWebArticles] ha una proprietà [actions] pubblica e di sola scrittura - riga 7
- Questa proprietà consente al componente <asp:repeater> denominato [rptMenu] — riga 10 — di essere associato all'array di opzioni calcolato 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 codice seguente:
Commenti:
- La riga 1 specifica che il tag <WA:entete> deve essere associato al componente definito dal file [entete.ascx]. Gli attributi [TagPrefix] e [TagName] sono facoltativi.
- Una volta fatto ciò, il componente viene inserito nel codice di presentazione della pagina utilizzando la riga 9. In fase di esecuzione, questo tag includerà il codice della pagina [entete.ascx] nella pagina ASPX che lo contiene. Il codice di controllo [erreurs.aspx.vb] gestirà l'inizializzazione di questo componente. Può farlo come segue:
Commenti:
- La riga 6 crea un oggetto di tipo [EnteteWebArticles], che è il tipo del componente che si sta creando
- La riga 11 inizializza la proprietà [actions] di questo oggetto
1.5.6.2. La vista [liste.aspx]
1.5.6.2.1. Introduzione
Questa schermata mostra l'elenco degli articoli disponibili per la vendita:
![]() | ![]() |
Viene visualizzata in seguito a una richiesta a /main?action=list o /main?action=cartvalidation. Gli elementi della richiesta al controller sono i seguenti:
Oggetto Hashtable() - l'array delle opzioni di menu | |
ArrayList di oggetti di tipo [Item] | |
Oggetto String - messaggio da visualizzare in fondo alla pagina |
Ogni link [Info] nella tabella HTML degli articoli ha un URL nel formato [?action=info&id=ID], dove ID è il campo id dell'articolo visualizzato.
1.5.6.2.2. Componenti della pagina
![]() |
No. | Tipo | nome | ruolo |
componente utente | intestazione | intestazione di visualizzazione | |
DataGrid | DataGridArticoli 3 - colonna correlata: intestazione: Nome, campo: nome 4 - colonna correlata: intestazione: Prezzo, campo: prezzo 5 - colonna ipertestuale: testo: Info, campo URL: id, formato URL: /webarticles/main.aspx?action=info&id={0} | visualizza articoli in vendita | |
etichetta | lblMessage | visualizza un messaggio |
Vediamo come impostare queste proprietà:
- In Visual Studio, selezionare [DataGrid] per accedere alla finestra delle proprietà:

- Utilizza il collegamento [AutoFormat] in alto per gestire il layout della griglia visualizzata
- e il link [Generatore di proprietà] 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 proprietà 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 alle righe 13–15. Si noti che è stato necessario creare un oggetto [EnteteWebArticles] utilizzando l'operatore [new], mentre ciò non era necessario con gli altri componenti. Senza questa creazione esplicita, si è verificato un errore di runtime che indicava che l'oggetto [entete] non faceva riferimento a nulla. Questo punto merita un'ulteriore indagine. Non è stato ancora approfondito.
- La tabella delle opzioni del menu di intestazione viene recuperata 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. Introduzione
Questa vista mostra le informazioni relative a un articolo e ne consente l'acquisto:

Viene visualizzata in seguito a una richiesta /main?action=infos&id=ID o a una richiesta /main?action=achat&id=ID quando la quantità acquistata non è corretta. I parametri di richiesta del controller sono i seguenti:
Oggetto Hashtable() - l'array delle opzioni di menu | |
oggetto di tipo [Articolo] - elemento da visualizzare | |
Oggetto stringa - messaggio da visualizzare in caso di errore con la quantità | |
Oggetto stringa - valore da visualizzare nel campo di immissione [Qty] |
I campi [msg] e [qte] vengono utilizzati in caso di errore di immissione relativo alla quantità:

Questa pagina contiene un modulo che viene inviato tramite il pulsante [Acquista]. L'URL di destinazione per la richiesta POST è [?action=purchase&id=ID], dove ID è l'ID dell'articolo acquistato.
1.5.6.3.2. Componenti della pagina
![]() |
No. | Tipo | nome | ruolo |
componente utente | intestazione | intestazione di visualizzazione | |
letterale | litID | visualizza numero articolo | |
DataGrid | DataGridArticolo 3 - colonna correlata: intestazione: Nome, campo: nome 4 - colonna correlata: intestazione: Prezzo, campo: prezzo 5 - colonna correlata: intestazione: Stock attuale, campo: currentStock 6 - colonna correlata: intestazione: Stock minimo, campo: stockMinimo | visualizza un articolo | |
Invia HTML | Invia il modulo | ||
Input HTML runat=server | txtQty | Inserisci la quantità acquistata | |
label | lblMsgQte | qualsiasi messaggio di errore |
1.5.6.3.3. Il codice di presentazione [infos.aspx]
Commenti:
- l'intestazione è inclusa nella pagina - riga 9
- Il letterale [litId] è definito alla riga 10
- Il DataGrid [DataGridArticles] è definito alle righe 12–34
- Il modulo è definito alle righe 36–46. È di tipo POST.
- La destinazione POST è fornita da una variabile [strAction] - riga 36. Questa variabile deve essere definita dal controller.
- Il campo di immissione per la quantità acquistata è definito alla riga 41. Si tratta di un componente HTML lato server (runat=server). Dal punto di vista del codice, vi si accede tramite un oggetto.
- La riga 42 definisce l'etichetta [lblMsgQte], che conterrà eventuali messaggi di errore relativi alla quantità inserita
1.5.6.3.4. Il codice di controllo [infos.aspx.vb]
Commenti:
- I componenti della pagina sono definiti nelle righe 10–14
- La classe definisce una proprietà pubblica [strAction] utilizzata per definire la destinazione POST del modulo - righe 17-25
- L'articolo da visualizzare viene recuperato dal contesto dell'applicazione - riga 30
- L'array delle opzioni del menu di intestazione viene recuperato dal contesto per inizializzare il componente [entete] della pagina — riga 32
- righe 33–39: il componente [DataGridArticle] è associato a un'origine dati di tipo [ArrayList] contenente solo l'articolo recuperato alla riga 30
- i componenti [lblMsgQte, txtQte] vengono inizializzati con le informazioni prese dal contesto - righe 42-45
- anche la proprietà [straction] viene inizializzata con le informazioni prese dal contesto - riga 47. Questa variabile viene utilizzata per generare l'attributo [action] del modulo HTML presente nella pagina:
1.5.6.4. La vista [panier.aspx]
1.5.6.4.1. Introduzione
Questa vista mostra il contenuto del carrello:

Viene visualizzata in risposta a una richiesta del tipo /main?action=cart o /main?action=cancelpurchase&id=ID. I parametri di richiesta del controller sono i seguenti:
object Hashtable() - l'array delle opzioni di menu | |
oggetto di tipo [Carrello] - il carrello da visualizzare |
Ogni link [Rimuovi] nella tabella HTML degli articoli nel carrello ha un URL nella forma [?action=removeitem&id=ID], dove ID è il campo [id] dell'articolo da rimuovere dal carrello.
1.5.6.4.2. Componenti della pagina
![]() |
No. | tipo | nome | ruolo |
componente utente | intestazione | intestazione di visualizzazione | |
DataGrid | DataGridAcquisti 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: total, 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 dovuto |
1.5.6.4.3. Il codice di presentazione [panier.aspx]
Commenti
- La riga 9 include l'intestazione
- Le righe 12–27 definiscono il componente [DataGridAchats]
- 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 [header] è identica a quella presente nelle pagine già studiate — riga 17
- Il carrello da visualizzare viene recuperato dalla sessione — riga 19
- La visualizzazione di questo carrello utilizzando il componente [DataGridAchats] pone dei problemi. La difficoltà deriva dall'inizializzazione del componente. Esaminiamo le sue colonne:
- colonna [Article] associata al campo [name] nell'origine dati
- Colonna [Qty] associata al campo [qty] nell'origine dati
- Colonna [Price] associata al campo [price] nell'origine dati
- Colonna [Total] associata al campo [total] nell'origine dati
L'origine dati di cui disponiamo è il carrello della spesa e la relativa lista della spesa. Quest'ultima fungerà da origine dati per il [DataGrid]. Tuttavia, gli oggetti [Purchase] che popoleranno le righe del [DataGrid] non dispongono delle proprietà [name, qty, price, total] previste dal [DataGrid]. Pertanto, creiamo qui, specificamente per il [DataGrid], un'origine dati i cui elementi abbiano le caratteristiche previste dal [DataGrid]. Questi elementi saranno di tipo [PurchaseLine], una classe creata a questo scopo e derivata dalla classe [Purchase] — righe 36–66
- Una volta definita la classe [PurchaseLine], l'origine dati per [DataGridPurchases] viene costruita a partire dal carrello presente nella sessione — righe 20–30
- L'importo totale dell'acquisto viene visualizzato utilizzando la proprietà [totalPanier] della classe [Panier] — riga 32
1.5.6.5. La vista [emptyCart.aspx]
1.5.6.5.1. Introduzione
Questa vista visualizza le informazioni che indicano che il carrello è vuoto:

Viene visualizzata in seguito a una richiesta a /main?action=panier o /main?action=retirerachat&id=ID. I parametri della richiesta al controller sono i seguenti:
Oggetto Hashtable() - l'array delle opzioni del menu |
1.5.6.5.2. Componenti della pagina
![]() |
N. | Tipo | nome | ruolo |
componente utente | intestazione | visualizza intestazione |
1.5.6.5.3. Il codice di presentazione [emptycart.aspx]
Commenti:
- L'intestazione è inclusa alla riga 9
1.5.6.5.4. Il codice di controllo [paniervide.aspx.vb]
Commenti:
- Inizializziamo semplicemente l'unico componente dinamico della pagina - riga 10
1.5.6.6. La vista [errors.aspx]
1.5.6.6.1. Introduzione
Questa vista 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 gestita dalla vista [INFOS]. Gli elementi della richiesta del controller sono i seguenti:
Oggetto Hashtable() - l'array delle opzioni di menu | |
ArrayList di oggetti [String] che rappresentano i messaggi di errore da visualizzare |
1.5.6.6.2. Componenti della pagina
![]() |
N. | tipo | nome | ruolo |
componente utente | intestazione | intestazione di visualizzazione | |
ripetitore | rptErrors | visualizza l'elenco degli errori |
1.5.6.6.3. Il codice di presentazione [errors.aspx]
Commenti:
- L'intestazione è definita alla riga 9
- Il componente [rptErrors] è definito alle righe 13–19. Il suo contenuto proviene da una fonte dati di tipo [ArrayList] contenente oggetti [String].
1.5.6.6.4. Il codice di controllo [errors.aspx.vb]
Commenti:
- Il componente [header] viene inizializzato come di consueto, righe 9 e 14
- Il componente [rptErrors] viene inizializzato con l'elenco degli errori [ArrayList] presente nel contesto — righe 16–19
1.5.7. I controller global.asax e main.aspx
Dobbiamo ancora scrivere il cuore della nostra applicazione web: il controller. Il suo ruolo è quello di:
- recuperare la richiesta del client,
- elaborare l'azione richiesta dal client utilizzando le classi di business,
- inviare la vista appropriata in risposta.
1.5.7.1. Il controller [global.asax.vb]
Quando l'applicazione riceve la sua primissima richiesta, viene eseguita la procedura [Application_Start] nel file [global.asax.vb]. Ciò accadrà una sola volta. Lo scopo della procedura [Application_Start] è inizializzare gli oggetti richiesti dall'applicazione web, che saranno condivisi in modalità di 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] nel file [global.asax.vb] eseguirà le seguenti azioni:
- verificherà il file [web.config] alla ricerca dei parametri necessari per il corretto funzionamento dell'applicazione. Questi sono stati descritti nella sezione 1.5.3.
- inserirà un elenco di eventuali errori nel contesto dell'applicazione sotto forma di un oggetto [ArrayList errors]. Questo elenco sarà vuoto se non ci sono errori, ma esisterà comunque.
- Se ci sono stati errori, il metodo [Application_Start] si ferma lì. Altrimenti, richiede un riferimento a un singleton di tipo [IArticlesDomain], che sarà l'oggetto di business che il controller utilizzerà per le proprie esigenze. Come spiegato in 1.5.3.2, il controller richiederà questo singleton al framework Spring. Questa operazione di istanziazione può causare vari errori. In tal caso, questi errori verranno nuovamente memorizzati nell'oggetto [errors] del contesto dell'applicazione.
Il controller [global.asax.vb] dispone di una procedura [Session_Start] che viene eseguita ogni volta che arriva un nuovo client. In questa procedura, creeremo un carrello della spesa vuoto per il client. Questo carrello della spesa verrà mantenuto per tutta la durata delle richieste di questo particolare client. Il codice potrebbe essere il seguente:
Commenti:
- I parametri previsti in [web.config] sono definiti in un array - riga 18
- Vengono cercati in [web.config]. Se presenti, vengono memorizzati nel contesto dell'applicazione; in caso contrario, viene registrato un errore nell'elenco degli errori [errors] - righe 21-33
- Se non ci sono errori, viene richiesto a Spring un riferimento al singleton [articlesDomain], che gestisce l'accesso al livello [domain] dell'applicazione - righe 35-47. Eventuali errori vengono registrati in [errors].
- Gli errori vengono registrati nel contesto dell'applicazione - riga 49
- La procedura termina se si sono verificati errori - riga 51
- Creiamo un array di tre dizionari. Ciascuno 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
- Per ogni nuovo cliente, viene eseguita la procedura [Session_Start]. 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] gestisce tutte le richieste dei client. Queste richieste seguono tutte il formato [/webarticles/main.aspx?action=XX]. Una richiesta viene elaborata come segue:
- Viene controllato l'oggetto [errors] nel contesto dell'applicazione. Se non è vuoto, significa che si sono verificati errori durante l'inizializzazione dell'applicazione e che l'applicazione non può essere eseguita. In risposta, viene inviata la vista [ERRORS].
- Il parametro [action] della richiesta viene recuperato e controllato. Se non corrisponde a un'azione nota, viene inviata la vista [ERRORS] con un messaggio di errore appropriato.
- Se il parametro [action] è valido, la richiesta del client viene passata a una procedura specifica per l'azione per l'elaborazione:
metodo | richiesta | elaborazione | possibili risposte |
GET /main?action=list | - richiedi l'elenco degli elementi dalla classe di business - visualizzarlo | [LIST] o [ERRORS] | |
GET /main?action=info&id=ID | - recupera l'elemento con id=ID da classe di business - visualizzarlo | [INFO] o [ERRORI] | |
POST /main?action=purchase&id=ID - La quantità acquistata è inclusa nei parametri inviati | - richiedi l'articolo con id=ID da classe business - aggiungilo al carrello nella sessione del cliente | [LIST] o [INFO] o [ERRORS] | |
GET /main?action=removePurchase&id=ID | - Rimuovi l'articolo con id=ID dalla lista della spesa nella sessione del cliente | [CART] | |
GET /main?action=cart | - Visualizza la sessione del cliente | [CART] o [EMPTY_CART] | |
GET /main?action=cartvalidation | - Ridurre le livelli di stock di tutti gli articoli nel [LIST] o [ERRORS] | [LIST] o [ERRORS] |
Il modello del controller [main.aspx.vb] potrebbe apparire così:
Commenti:
- La classe ha due campi privati che saranno condivisi tra i metodi — righe 15–16:
- articlesDomain: il singleton per accedere al livello [domain]
- options: l'array di dizionari contenente le opzioni di menu
- La procedura [Page_Load]:
- inizializzerà i due campi privati della classe
- recupera il parametro [action] dalla richiesta ed esegue il metodo che gestisce quell'azione.
1.5.7.3. Il metodo [Page_Load]
Questo evento è il primo a verificarsi sulla pagina. Il codice è il seguente:
Commenti:
- Ogni volta che la pagina viene caricata, ci assicuriamo che l'inizializzazione dell'applicazione eseguita da [global.asax] sia andata a buon fine.
- Per farlo, recuperiamo l'elenco degli errori inseriti da [global.asax] dal contesto dell'applicazione - riga 4
- Se questo elenco non è vuoto, visualizziamo la vista [ERRORS] — righe 6–10
- Recuperiamo il singleton [articlesDomain] inserito da [global.asax] nel contesto dell'applicazione e lo memorizziamo nel campo privato [articlesDomain] in modo che sia disponibile per i vari metodi della classe — riga 12
- Eseguiamo un'operazione simile con l'array delle opzioni di menu - riga 14
- recuperiamo il parametro [action] dalla richiesta - riga 16
- Eseguiamo il metodo corrispondente all'azione richiesta. Un'azione non specificata viene trattata come l'azione [list] — righe 16-36
1.5.7.4. Gestione dell'azione [list]
Ciò comporta la visualizzazione dell'elenco degli elementi:

Il codice è il seguente:
Commenti:
- Eventuali errori vengono inseriti in un [ArrayList] - riga 4
- L'elenco degli elementi viene richiesto al singleton [articlesDomain] - righe 5-12
- Se sono presenti errori, viene visualizzata la vista [ERRORS] - righe 13-19
- altrimenti, viene restituita la vista [LIST] - righe 20-24
1.5.7.5. Gestione dell'azione [info]
Il client ha richiesto informazioni su un elemento specifico:

Il codice è il seguente:
Commenti:
- Eventuali errori vengono inseriti in un [ArrayList] - riga 4
- L'ID dell'elemento richiesto viene recuperato dalla richiesta - riga 6
- Questo ID viene convalidato. Deve esistere e deve essere un numero intero. In caso contrario, viene visualizzata la vista [ERRORS] con il messaggio di errore appropriato - righe 7-27
- Una volta verificato l'ID, l'elemento viene richiesto al singleton [articlesDomain]. Se si verifica un'eccezione, viene inviata la vista [ERRORS] - righe 29-39
- Se l'elemento non viene trovato, viene inviata la vista [ERRORS] — righe 41–48
- Se l'articolo viene trovato, viene inserito nella sessione dell'utente e quindi visualizzato nella vista [INFO] - righe 50-56
1.5.7.6. Elaborazione dell'azione [purchase]
Il cliente ha acquistato l'elemento visualizzato nella vista [INFO].

Il codice è il seguente:
Commenti:
- Viene recuperato l'elemento inserito nella sessione - riga 5
- Se non è presente (la sessione potrebbe essere scaduta), viene visualizzata la vista [LIST] - righe 7-10
- La quantità acquistata viene recuperata dalla query - riga 12
- Se non è valida, a seconda del caso, viene visualizzata la vista [LIST] - riga 16
- se non è valida, a seconda dei casi, viene visualizzata la vista [LIST] - riga 16 oppure la vista [INFO] - righe 24-28
- Se tutto è normale, l'acquisto viene aggiunto al carrello - righe 31-32
- quindi viene inviata la vista [LIST] - riga 34
1.5.7.7. Elaborazione dell'azione [cart]
Il cliente ha effettuato diversi acquisti e desidera visualizzare il carrello:

Il codice è il seguente:
Commenti:
- Recuperiamo il carrello dalla sessione - riga 4. Qui non verifichiamo se effettivamente abbiamo recuperato qualcosa. Dovremmo farlo perché la sessione potrebbe essere scaduta.
- Se il carrello è vuoto, inviamo la vista [EMPTY CART] - righe 6-10
- Altrimenti, inviamo la vista [SHOPPINGCART] — righe 11–14
1.5.7.8. Elaborazione dell'azione [removepurchase]
Il cliente desidera rimuovere un acquisto dal proprio carrello:

Il codice è il seguente:
Commenti:
- Recupera il carrello dalla sessione - riga 4. Qui non verifichiamo che il recupero sia effettivamente avvenuto. Questo dovrebbe essere fatto perché la sessione potrebbe essere scaduta.
- Recupera l'ID [id] dell'articolo da rimuovere dalla query - riga 8.
- L'acquisto corrispondente viene rimosso dal carrello - riga 10
- Qui non è stata verificata la validità dell'ID dell'articolo acquistato. Se ha un tipo non valido, si verificherà un'eccezione che verrà gestita nelle righe 11-13. Se è valido ma non esiste, il metodo [cart.remove] (riga 10) non fa nulla.
- Viene visualizzato il nuovo carrello - riga 16
1.5.7.9. Elaborazione dell'azione [validateCart]
Il cliente desidera confermare il proprio carrello:

Il codice è il seguente:
Commenti:
- Recuperiamo il carrello dalla sessione - riga 6. Qui non verifichiamo se abbiamo effettivamente recuperato qualcosa. Dovremmo farlo perché la sessione potrebbe essere scaduta.
- Se la sessione è scaduta, avremo un puntatore [nothing] per il carrello e il metodo [buy] — riga 9 — genererà un'eccezione e verrà visualizzata la vista [ERRORS]. Tuttavia, il messaggio di errore non sarà chiaro per l'utente.
- Righe 8–16: Tentiamo di convalidare il carrello recuperato dalla sessione. Alcuni acquisti potrebbero non andare a buon fine se la quantità richiesta supera la disponibilità dell'articolo richiesto. Questi casi vengono memorizzati dal metodo [buy] in un elenco di errori, che viene recuperato alla riga 11.
- Se ci sono errori, viene inviata la vista [ERRORS] — righe 18–23
- altrimenti, viene inviata la vista [LIST] — righe 25–26
1.6. Conclusione
Qui abbiamo sviluppato un'applicazione utilizzando il modello MVC. Ad aprile 2005 non sembrano esserci framework di sviluppo MVC professionali per ASP.NET paragonabili a quelli disponibili per Java (Struts, Spring, ecc.). Il progetto [Spring.net] dovrebbe rilasciarne uno a breve. Fino ad allora, il metodo sopra descritto fornisce un approccio di sviluppo MVC valido per applicazioni di medie dimensioni.































