1. Part 1
The PDF of the document is available |HERE|.
The examples in the document are available |HERE|.
1.1. Introduction
Objectives of this article:
- Write a three-tier web application [user interface, business logic, data access]
- configure the application with Spring IOC
- Write different versions by changing the implementation of one or more of the three layers.
Tools used:
- Visual Studio.NET for development—see Appendix, Section 3.1;
- Cassini web server for execution—see Appendix, Section 3.2;
- Nunit for unit testing – see Appendix, Section 3.4;
- Spring for the integration and configuration of the web application layers—see Appendix, Section 3.3;
On a beginner-intermediate-advanced scale, this document falls into the [intermediate-advanced] category. Understanding it requires various prerequisites. Some of these can be found in documents I have written. In such cases, I cite them. It goes without saying that this is merely a suggestion and that the reader is free to use their preferred resources.
- VB.NET language: [Introduction to VB.NET through Examples];
- Web programming in VB.NET: [Web Development with ASP.NET 1.1];
- Using Spring's IoC aspect: [Spring IoC for .NET];
- Spring.NET documentation: [Spring.NET | Homepage]
This document follows the same structure as a document written for Java [3-Layer Architectures and MVC Architectures with Struts, Spring, and Java]. We are building the three-tier MVC web application written in Java using VB.NET. The point we want to make here is that the Java and .NET development platforms are sufficiently similar that skills acquired in one of these two domains can be reused in the other.
There does not appear to be a widely recognized ASP.NET MVC development solution. The following solution adopts the method introduced in the document [Web Development with ASP.NET 1.1]. While this method has the merit of using concepts common in J2EE development, it should nevertheless be taken for what it is: one of many MVC development methods. As soon as an MVC development method in ASP.NET becomes widely accepted, it should be adopted. The .NET version of Spring, currently under development, could well be a first solution.
1.2. The webarticles application
Here we present the components of a simplified e-commerce web application. This application will allow web users to:
- view a list of items from a database
- add some of them to an online shopping cart
- to confirm the cart. This confirmation will simply update the inventory of the purchased items in the database.
The different views presented to the user will be as follows:
- the "LIST" view, which displays a list of items for sale ![]() | - the [INFO] view, which provides additional information about a product: ![]() |
- the [CART] and [EMPTY CART] views, which display the contents of the customer’s cart
![]() | ![]() |
- the [ERRORS] view, which reports any application errors

1.3. General Application Architecture
We want to build an application with the following three-tier structure:
![]() |
- The three layers are made independent through the use of interfaces
- The integration of the different layers is handled by Spring
- Each layer has its own namespace: web (UI layer), domain (business layer), and dao (data access layer).
The application will follow an MVC (Model-View-Controller) architecture. If we refer back to the layered diagram above, the MVC architecture fits into it as follows:
![]() |
Processing a client request follows these steps:
- The client makes a request to the controller. In this case, the controller is an .aspx page that plays a specific role. It handles all client requests. It is the application’s entry point. It is the C in MVC.
- The controller processes this request. To do so, it may need assistance from the business layer, known as the M in the MVC structure.
- The controller receives a response from the business layer. The client’s request has been processed. This can trigger several possible responses. A classic example is
- an error page if the request could not be processed correctly
- a confirmation page otherwise
- The controller chooses the response (= view) to send to the client. This is most often a page containing dynamic elements. The controller provides these to the view.
- The view is sent to the client. This is the V in MVC.
1.4. The Model
Here we examine the M in MVC. The model consists of the following elements:
1.4.1. The database
The database contains only one table named ARTICLES. This table was generated using the following SQL commands:
CREATE TABLE ARTICLES (
ID INTEGER NOT NULL,
NAME VARCHAR(20) NOT NULL,
PRICE NUMERIC(15,2) NOT NULL,
CURRENTSTOCK INTEGER NOT NULL,
MINIMUM_STOCK INTEGER NOT NULL
);
/* constraints */
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_ID check (ID>0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_PRICE check (PRICE >= 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_NAME check (NAME <> '');
ALTER TABLE ARTICLES ADD CONSTRAINT UNQ_NAME UNIQUE (NAME);
/* primary key */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
primary key uniquely identifying an item | |
item name | |
its price | |
current stock | |
the stock level below which a reorder must be placed |
1.4.2. The model's namespaces
Model M is provided here in the form of two namespaces:
- istia.st.articles.dao: contains the data access classes of the [dao] layer
- istia.st.articles.domain: contains the business classes of the [domain] layer
Each of these namespaces will be generated within its own "assembly" file:
content | role | |
- [IArticlesDao]: the interface for accessing the [dao] layer. This is the only interface visible to the [domain] layer. It sees no others. - [Article]: class defining an article - [ArticlesDaoArrayList]: implementation class of the [IArticlesDao] interface with an [ArrayList] | data access layer - is entirely within the [DAO] layer of the 3-tier architecture of the web application | |
- [IArticlesDomain]: the interface for accessing the [domain] layer. This is the only interface visible to the web layer. It sees no others. - [AchatsArticles]: a class implementing [IArticlesDomain] - [Purchase]: a class representing a customer's purchase - [Cart]: a class representing a customer's total purchases | represents the model of web purchases web - resides entirely in the [domain] layer of the 3-tier architecture of the web application |
1.4.3. The [DAO] layer
The [DAO] layer contains the following elements:
-
[IArticlesDao]: the interface for accessing the [dao] layer
-
[Article]: class defining an article
-
[ArticlesDaoArrayList]: implementation class for the [IArticlesDao] interface using an [ArrayList] class
The [Visual Studio] project structure for the [dao] layer is as follows:

Comments:
- The [dao] project is of type [class library]
- The classes have been placed in a tree structure starting from the [istia] folder. They are all in the [istia.st.articles.dao] namespace.
1.4.3.1. The [Article] class
The class defining an article is as follows:
Imports System
Namespace istia.st.articles.dao
Public Class Article
' private fields
Private _id As Integer
Private _name As String
Private _price As Double
Private _currentStock As Integer
Private _minStock As Integer
' Item ID
Public Property id() As Integer
Get
Return _id
End Get
Set(ByVal Value As Integer)
If Value <= 0 Then
Throw New Exception("The id field [" + Value.ToString + "] is invalid")
End If
Me._id = Value
End Set
End Property
' item name
Public Property name() As String
Get
Return _name
End Get
Set(ByVal Value As String)
If Value Is Nothing OrElse Value.Trim.Equals("") Then
Throw New Exception("The name field [" + Value + "] is invalid")
End If
Me._name = Value
End Set
End Property
' item price
Public Property price() As Double
Get
Return _price
End Get
Set(ByVal Value As Double)
If Value < 0 Then
Throw New Exception("The price field [" + Value.ToString + "] is invalid")
End If
Me._price = Value
End Set
End Property
' current item stock
Public Property currentStock() As Integer
Get
Return _currentStock
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New Exception("The currentStock field [" + Value.ToString + "] is invalid")
End If
Me._currentStock = Value
End Set
End Property
' minimum stock for item
Public Property stockminimum() As Integer
Get
Return _stockminimum
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New Exception("The stockMinimum field [" + Value.ToString + "] is invalid")
End If
Me._stockminimum = Value
End Set
End Property
' default constructor
Public Sub New()
End Sub
' constructor with properties
Public Sub New(ByVal id As Integer, ByVal name As String, ByVal price As Double, ByVal currentStock As Integer, ByVal minimumStock As Integer)
Me.id = id
Me.name = name
Me.price = price
Me.currentStock = currentStock
Me.minStock = minStock
End Sub
' item identification method
Public Overrides Function ToString() As String
Return "[" + id.ToString + "," + name + "," + price.ToString + "," + currentStock.ToString + "," + minimumStock.ToString + "]"
End Function
End Class
End Namespace
This class provides:
- a constructor for setting the 5 pieces of information for an item: [id, name, price, currentStock, minimumStock]
- public properties for reading and writing the 5 pieces of information.
- a validation of the data entered for the item. If the data is invalid, an exception is thrown.
- a toString method that returns the value of an item as a string. This is often useful for debugging an application.
1.4.3.2. The [IArticlesDao] interface
The [IArticlesDao] interface is defined as follows:
Imports System
Imports System.Collections
Namespace istia.st.articles.dao
Public Interface IArticlesDao
' list of all articles
Function getAllArticles() As IList
' adds an article
Function addArticle(ByVal anArticle As Article) As Integer
' deletes an article
Function deleteArticle(ByVal articleId As Integer) As Integer
' modifies an article
Function modifyArticle(ByVal anArticle As Article) As Integer
' retrieves an item
Function getArticleById(ByVal idArticle As Integer) As Article
' deletes all articles
Sub clearAllArticles()
' changes the stock of an item
Function changeArticleStock(ByVal articleId As Integer, ByVal movement As Integer) As Integer
End Interface
End Namespace
The roles of the various methods in the interface are as follows:
returns all items from the data source | |
clears the data source | |
returns the [Article] object identified by its primary key | |
allows you to add an article to the data source | |
allows you to modify an article in the data source | |
allows you to delete an item from the data source | |
allows you to modify the stock of an item in the data source |
The interface provides client programs with a number of methods defined solely by their signatures. It does not concern itself with how these methods will actually be implemented. This brings flexibility to an application. The client program makes calls to an interface rather than to a specific implementation of that interface.
![]() |
The choice of a specific implementation will be made via a Spring configuration file. To demonstrate that, for testing the web application, only the data access interface matters—not its implementation class—we will first implement the data source using a simple [ArrayList] object. Later, we will present a database-based solution.
1.4.3.3. The implementation class [ArticlesDaoArrayList]
The implementation class [ArticlesDaoArrayList] is defined as follows:
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 constructor
Public Sub New()
' create some articles
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 articles
Public Function getAllArticles() As IList Implements IArticlesDao.getAllArticles
' returns the list of articles
SyncLock Me
Return articles
End SyncLock
End Function
' Delete all items
Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
' Clear the list of items
SyncLock Me
articles.Clear()
End SyncLock
End Sub
' Get an article 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 addItem(ByVal anItem As Item) As Integer Implements IArticlesDao.addItem
' Add the item to the list of items
SyncLock Me
' Check that it does not already exist
Dim ipos As Integer = posArticle(articles, unArticle.id)
If ipos <> -1 Then
Throw New Exception("The item with ID [" + unArticle.id.ToString + "] already exists")
End If
' Add the item
articles.Add(anItem)
' return the result
Return 1
End SyncLock
End Function
' Modify an item
Public Function modifyItem(ByVal newItem As Item) As Integer Implements IArticlesDao.modifyItem
' Modify an item
SyncLock Me
' Check if it exists
Dim pos As Integer = posArticle(articles, newArticle.id)
' if it does not exist
If ipos = -1 Then Return 0
' it exists - update it
articles(ipos) = newArticle
' return the result
Return 1
End SyncLock
End Function
' delete an item identified by its key
Public Function deleteItem(ByVal itemId As Integer) As Integer Implements IArticlesDao.deleteItem
' Delete an item
SyncLock Me
' Check if it exists
Dim ipos As Integer = posArticle(articles, idArticle)
' if it does not exist
If ipos = -1 Then Return 0
' it exists - delete it
articles.RemoveAt(ipos)
' return the result
Return 1
End SyncLock
End Function
' Change the stock of an item identified by its key
Public Function changeItemStock(ByVal itemId As Integer, ByVal movement As Integer) As Integer Implements IArticlesDao.changeItemStock
' Change the stock of an item
SyncLock Me
' Check that it exists
Dim ipos As Integer = posArticle(articles, idArticle)
' if it does not exist
If ipos = -1 Then Return 0
' it exists - update its stock if possible
Dim anArticle As Article = CType(articles(ipos), Article)
' we only change the stock if it is sufficient
If unArticle.currentStock + movement >= 0 Then
unArticle.currentStock += movement
Return 1
Else
Return 0
End If
End SyncLock
End Function
' Search for an item identified by its key
Private Function itemPosition(ByVal itemList As ArrayList, ByVal itemId As Integer) As Integer
' returns the position of the item [idArticle] in the list or -1 if not found
Dim anItem As Item
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
Comments:
- The data source is simulated by the private field [articles] of type [ArrayList]
- The class constructor creates 4 items in the data source by default.
- All data access methods have been synchronized to prevent concurrent access issues to the data source. At any given time, only one thread has access to a given method.
- The [posArticle] method returns the position [0..N] in the [ArrayList] data source of an article identified by its number. If the article does not exist, the method returns the position -1. This method is used repeatedly by the other methods.
- The [addArticle] method adds an article to the list of articles. It returns the number of articles inserted: 1. If the article already existed, an exception is thrown.
- The [modifyItem] method allows you to modify an existing item. It returns the number of items modified: 1 if the item existed, 0 otherwise.
- The [deleteArticle] method deletes an existing article. It returns the number of articles deleted: 1 if the article existed, 0 otherwise.
- The [getAllArticles] method returns a list of all articles
- The [getArticleById] method retrieves an article identified by its ID. It returns the value [nothing] if the article does not exist.
- The code does not present any real difficulty. We leave it to the reader to review and understand it.
1.4.3.4. Generating the [dao] layer assembly
The Visual Studio project is configured to generate the [webarticles-dao.dll] assembly. This is generated in the project’s [bin] folder:
![]() | ![]() |
1.4.3.5. NUnit tests for the [dao] layer
In Java, classes are tested using the [JUnit] framework. In .NET, the NUnit framework offers the same unit testing capabilities:

The structure of the Visual Studio test project is as follows:

Comments:
- The [tests] project is a [class library]
- [NUnit] tests require a reference to the [nunit.framework.dll] assembly
- the [NUnit] test class retrieves an instance of the object under test via Spring. Therefore,
- in the [bin] folder, the Spring class files
- in [References], a reference to the [Spring-Core.dll] assembly from the [bin] folder
- in [bin], a configuration file for Spring
- The test class requires the [webarticles-dao.dll] assembly from the [dao] layer. This has been placed in the [bin] folder and its reference added to the project references.
A [NUnit] test class requires access to classes in the [NUnit.Framework] namespace. Therefore, the following import statement is included:
The [NUnit.Framework] namespace is located in the [nunit.framework.dll] assembly, which must be added to the project references:
![]() | ![]() |
The assembly [nunit.framework.dll] should be in the list provided if [NUnit] has been installed. Simply double-click the assembly to add it to the project:

The [NUnit] test class for the [DAO] layer could look like this:
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 object under test
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' Get an instance of the Spring object factory
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
' Request instantiation of the articlesdao object
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
<Test()> _
Public Sub testGetAllArticles()
' visual verification
listArticles()
End Sub
<Test()> _
Public Sub testClearAllArticles()
' Delete all articles
articlesDao.clearAllArticles()
' retrieve all articles
Dim articles As IList = articlesDao.getAllArticles
' verification: there should be 0
Assert.AreEqual(0, articles.Count)
End Sub
<Test()> _
Public Sub testAddArticle()
' Delete all items
articlesDao.clearAllArticles()
' Check: the articles table must be empty
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' add two articles
articlesDao.addArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.addArticle(New Article(4, "article4", 40, 40, 4))
' verification: there should be two articles
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' visual verification
listArticles()
End Sub
<Test()> _
Public Sub testDeleteItem()
' Delete all articles
articlesDao.clearAllArticles()
' Check: the articles table must be empty
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' add two articles
articlesDao.addArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.addArticle(New Article(4, "article4", 40, 40, 4))
' verification: there should be 2 articles
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' we delete article 4
articlesDao.deleteArticle(4)
' Check: there should be 1 article left
articles = articlesDao.getAllArticles
Assert.AreEqual(1, articles.Count)
' visual check
listArticles()
End Sub
<Test()> _
Public Sub testModifyItem()
' Delete all articles
articlesDao.clearAllArticles()
' verification
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' Add 2 articles
articlesDao.addArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.addArticle(New Article(4, "article4", 40, 40, 4))
' verification
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' search for article 3
Dim anArticle As Article = articlesDao.getArticleById(3)
' verification
Assert.AreEqual(anArticle.name, "article3")
' search for item 4
anArticle = articlesDao.getArticleById(4)
' verification
Assert.AreEqual(anArticle.name, "article4")
' modify article 4
articlesDao.modifyArticle(New Article(4, "article4", 44, 44, 44))
' verification
anArticle = articlesDao.getArticleById(4)
Assert.AreEqual(anArticle.price, 44, 0.000001)
' visual verification
listArticles()
End Sub
<Test()> _
Public Sub testGetArticleById()
' Delete articles
articlesDao.clearAllArticles()
' verification
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' adding 2 articles
articlesDao.addArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.addArticle(New Article(4, "article4", 40, 40, 4))
' verification
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' search for article 3
Dim anArticle As Article = articlesDao.getArticleById(3)
' verification
Assert.AreEqual(anItem.name, "item3")
' search for article 4
anArticle = articlesDao.getArticleById(4)
' verification
Assert.AreEqual(anArticle.name, "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 testMissingItem()
' Delete all items
articlesDao.clearAllArticles()
' search for article 1
Dim article As article = articlesDao.getArticleById(1)
' check
Assert.IsNull(article)
' Modify a non-existent article
Dim i As Integer = articlesDao.modifyArticle(New article(1, "1", 1, 1, 1))
' should have modified no rows
Assert.AreEqual(i, 0)
' Deleting a non-existent item
i = articlesDao.deleteArticle(1)
' should have deleted no rows
Assert.AreEqual(0, i)
End Sub
<Test()> _
Public Sub testChangeItemStock()
' Delete all items
articlesDao.clearAllArticles()
' Add an item
Dim nbArticles As Integer = articlesDao.addArticle(New Article(3, "article3", 30, 101, 3))
Assert.AreEqual(nbArticles, 1)
' Add an article
nbArticles = articlesDao.addArticle(New Article(4, "article4", 40, 40, 4))
Assert.AreEqual(nbArticles, 1)
' Create 100 threads
Dim tasks(99) As Thread
For i As Integer = 0 To tasks.Length - 1
' create thread i
tasks(i) = New Thread(New ThreadStart(AddressOf decrement))
' Set the thread name
tasks(i).Name = "task_" & i
' Start thread i
tasks(i).Start()
Next
' waiting for all threads to finish
For i As Integer = 0 To tasks.Length - 1
taches(i).Join()
Next
' Checks - Item 3 must have a stock of 1
Dim anArticle As Article = articlesDao.getArticleById(3)
Assert.AreEqual(unArticle.name, "article3")
Assert.AreEqual(1, unArticle.currentStock)
' Decrease the stock of item 4
Dim error As Boolean = False
Dim nbLines As Integer = articlesDao.changeItemStock(4, -100)
' verification: its stock should not have changed
Assert.AreEqual(0, nbLines)
' visual verification
listArticles()
End Sub
Public Sub decrement()
' thread launched
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " launched")
' thread decrements stock
articlesDao.changeItemStock(3, -1)
' thread finished
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " finished")
End Sub
End Class
End Namespace
Comments:
- We wanted to write a test program for the [IArticlesDao] interface that is independent of its implementation class. Therefore, we used Spring to hide the name of the implementation class from the test program.
- The <Setup()> method retrieves a reference to the [articlesdao] object to be tested from Spring. This is defined in the following [spring-config.xml] file:
<?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>
This file specifies the name of the implementation class [istia.st.articles.dao.ArticlesDaoArrayList] for the interface [IArticlesDao] and where to find it [webarticles-dao.dll]. Since instantiation does not require any parameters, none are defined here.
- Most of the tests are easy to understand. The reader is encouraged to read the comments.
- The [testChangerStockArticle] method requires some explanation. It creates 100 threads responsible for decrementing the stock of a given item.
<Test()> _
Public Sub testChangerStockArticle()
' Delete all items
articlesDao.clearAllArticles()
' Add an item
Dim nbArticles As Integer = articlesDao.addArticle(New Article(3, "article3", 30, 101, 3))
Assert.AreEqual(nbArticles, 1)
' Add an article
nbArticles = articlesDao.addArticle(New Article(4, "article4", 40, 40, 4))
Assert.AreEqual(nbArticles, 1)
' Create 100 threads
Dim tasks(99) As Thread
For i As Integer = 0 To tasks.Length - 1
' create thread i
tasks(i) = New Thread(New ThreadStart(AddressOf decrement))
' Set the thread name
tasks(i).Name = "task_" & i
' Start thread i
tasks(i).Start()
Next
' wait for all threads to finish
For i As Integer = 0 To tasks.Length - 1
tasks(i).Join()
Next
' Checks - Item 3 must have a stock of 1
Dim anArticle As Article = articlesDao.getArticleById(3)
Assert.AreEqual(unArticle.name, "article3")
Assert.AreEqual(1, unArticle.currentStock)
' Decrement the stock of item 4
Dim error As Boolean = False
Dim nbLines As Integer = articlesDao.changeItemStock(4, -100)
' verification: its stock should not have changed
Assert.AreEqual(0, nbLines)
' visual verification
listArticles()
End Sub
This is to test concurrent access to the data source. The method responsible for updating the stock is as follows:
Public Sub decrement()
' thread launched
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " launched")
' thread decrements stock
articlesDao.changeStockItem(3, -1)
' thread finished
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " finished")
End Sub
It decrements the stock of item #3 by one. If we refer to the code of the [testChangerStockArticle] method, we see that:
- the stock of item #3 is initialized to 101
- the 100 threads will each decrement this stock by one
- we should therefore have a stock of 1 at the end of all threads' execution
Furthermore, still within this method, we attempt to set the stock of item #4 to a negative value. This should fail.
To test the [dao] layer, we generate the DLL [tests-webarticles-dao.dll] in the [bin] folder of the [tests] project:
![]() | ![]() |
Then, using the [Nunit-Gui] application, we load this DLL and run the tests:

In the left window, we see the list of tested methods. The color of the dot preceding each method’s name indicates whether the method passed (green) or failed (red). Readers viewing this document on screen will see that all tests passed. We will therefore consider the [dao] layer to be operational.
1.4.4. The [domain] layer
The [domain] layer contains the following elements:
-
[IArticlesDomain]: the interface for accessing the [domain] layer
-
[Purchase]: class defining a purchase
-
[ShoppingCart]: class defining a shopping cart
-
[ProductPurchases]: the class implementing the [IArticlesDomain] interface
The structure of the [Visual Studio] solution for the [domain] layer is as follows:

Comments:
- The [domain] project is of type [class library]
- The classes have been placed in a tree structure starting from the [istia] folder. They are all in the [istia.st.articles.domain] namespace.
- The [dao] layer DLL has been placed in the [bin] folder of the new project. Additionally, this DLL has been added as a reference to the project.
1.4.4.1. The [IArticlesDomain] interface
The [IArticlesDomain] interface decouples the [business] layer from the [web] layer. The latter accesses the [business/domain] layer via this interface without concerning itself with the class that actually implements it. The interface defines the following actions for accessing the business layer:
Imports Article = istia.st.articles.dao.Article
Namespace istia.st.articles.domain
Public Interface IArticlesDomain
' methods
Sub buy(ByVal basket As Basket)
Function getAllArticles() As IList
Function getArticleById(ByVal idArticle As Integer) As Article
ReadOnly Property errors() As ArrayList
End Interface
End Namespace
returns the list of [Article] objects from the associated data source | |
returns the [Article] object identified by [idArticle] | |
processes the customer's cart by decrementing the stock of purchased items by the quantity purchased - may fail if stock is insufficient | |
returns the list of errors that occurred - empty if no errors |
1.4.4.2. The [Purchase] class
The [Purchase] class represents a customer purchase:
Imports istia.st.articles.dao
Namespace istia.st.articles.domain
Public Class Purchase
' private fields
Private _article As article
Private _qty As Integer
' default constructor
Public Sub New()
End Sub
' constructor with parameters
Public Sub New(ByVal anItem As Item, ByVal qty As Integer)
' Accessing properties
Me.article = anItem
Me.qty = qty
End Sub
' purchased item
Public Property item() As item
Get
Return _item
End Get
Set(ByVal Value As article)
_article = Value
End Set
End Property
' quantity purchased
Public Property qty() As Integer
Get
Return _qte
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New Exception("Invalid quantity [" + Value.ToString + "]")
End If
_qte = Value
End Set
End Property
' total purchase
Public ReadOnly Property totalPurchase() As Double
Get
Return _qty * _item.price
End Get
End Property
' identity
Public Overrides Function ToString() As String
Return "[" + _article.ToString + "," + _qte.ToString + "]"
End Function
End Class
End Namespace
Comments:
- The [Purchase] class has the following properties and methods:
the purchased item | |
the quantity purchased | |
the purchase amount | |
object's string representation |
- It has a constructor that initializes the [item, qty] properties that define a purchase.
1.4.4.3. The [Cart] class
The [Cart] class represents the customer's total purchases:
Namespace istia.st.articles.domain
Public Class ShoppingCart
' private fields
Private _purchases As New ArrayList
Private _cartTotal As Double = 0
' default constructor
Public Sub New()
End Sub
' list of purchases
Public ReadOnly Property purchases() As ArrayList
Get
Return _purchases
End Get
End Property
' total purchases
Public ReadOnly Property totalCart() As Double
Get
Return _totalCart
End Get
End Property
' methods
Public Sub add(ByVal aPurchase As Purchase)
' check if the purchase already exists
Dim iPurchase As Integer = FindPurchase(purchase.item.id)
If iPurchase <> -1 Then
' Found
Dim currentPurchase As Purchase = CType(_purchases(iPurchase), Purchase)
currentPurchase.qty += unPurchase.qty
Else
' not found
_purchases.Add(purchase)
End If
' increment the cart total
_cartTotal += purchase.total
End Sub
' remove a purchase
Public Sub remove(ByVal purchaseId As Integer)
' search for the purchase
Dim purchaseAs Integer = findPurchase(purchaseId)
' if found, remove
If purchaseId <> -1 Then
Dim currentPurchase As Purchase = CType(_purchases(purchaseID), Purchase)
' remove from cart
_purchases.RemoveAt(purchaseID)
' Decrement the cart total
_cartTotal -= currentPurchase.totalPurchase
End If
End Sub
Private Function posAchat(ByVal idArticle As Integer) As Integer
' searches for a purchase in the list of purchases
' returns its position in the list or -1 if not found
Dim currentPurchase As Purchase
Dim found As Boolean = False
Dim i As Integer = 0
While Not found AndAlso i < _purchases.Count
' current purchase
CurrentPurchase = CType(_purchases(i), Purchase)
' comparison with the searched item
If currentPurchase.item.id = itemId 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 _purchases.ToString
End Function
End Class
End Namespace
Comments:
- The [ShoppingCart] class has the following properties and methods:
the customer's shopping list - a list of objects of type [Purchase] | |
adds a purchase to the list of purchases | |
removes the purchase with idPurchase | |
the total amount of purchases in the cart | |
returns the string representing the shopping cart |
- The [posAchat] method is a utility method that returns the position in the purchase list of a purchase identified by the item number. The shopping list is managed such that an item purchased multiple times occupies only one position in the list. Thus, a purchase can be identified by the item number. The [posAchat] method returns -1 if the purchase being searched for does not exist.
- The [add] method adds a new purchase to the purchase list. This either adds a new entry to the purchase list if the purchased item did not already exist in the list, or increments the quantity purchased if it already existed.
- The [remove] method allows you to remove a purchase identified by a number from the list of purchases. If the purchase does not exist, the method does nothing.
- The total purchase amount [totalPanier] is updated as items are added and removed.
1.4.4.4. The [PurchaseItems] class
The [IArticlesDomain] interface will be implemented by the following [PurchaseItems] class:
Imports istia.st.articles.dao
Namespace istia.st.articles.domain
Public Class ItemPurchases
Implements IArticlesDomain
'private fields
Private _articlesDao As IArticlesDao
Private _errors As ArrayList
' constructor
Public Sub New(ByVal articlesDao As IArticlesDao)
_articlesDao = articlesDao
End Sub
' list of errors
Public ReadOnly Property errors() As ArrayList Implements IArticlesDomain.errors
Get
Return _errors
End Get
End Property
' list of items
Public Function getAllArticles() As IList Implements IArticlesDomain.getAllArticles
' list of all articles
Try
Return _articlesDao.getAllArticles
Catch ex As Exception
_errors = New ArrayList
_errors.Add("Data access error: " + ex.Message)
End Try
End Function
' Retrieve an article identified by its ID
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDomain.getArticleById
' a specific item
Try
Return _articlesDao.getArticleById(idArticle)
Catch ex As Exception
_errors = New ArrayList
_errors.Add("Data access error: " + ex.Message)
End Try
End Function
' Buy a shopping cart
Public Sub buy(ByVal basket As Basket) Implements IArticlesDomain.buy
' Purchase a shopping cart - stock levels for purchased items must be decremented
_errors = New ArrayList
Dim purchase As purchase
Dim purchases As ArrayList = basket.purchases
For i As Integer = purchases.Count - 1 To 0 Step -1
' Decrement stock of item i
purchase = CType(purchases(i), purchase)
Try
If _articlesDao.changeItemStock(purchase.item.id, -purchase.qty) = 0 Then
' The operation could not be performed
_errors.Add("The purchase " + purchase.ToString + " could not be completed - Check inventory")
Else
' The operation was successful - remove the purchase from the cart
cart.remove(purchase.item.id)
End If
Catch ex As Exception
_errors = New ArrayList
_errors.Add("Data access error: " + ex.Message)
End Try
Next
End Sub
End Class
End Namespace
Comments:
- This class implements the four methods of the [IArticlesDomain] interface. It has two private fields:
the data access object | |
the list of possible errors. It can be accessed via the public property [errors] |
- To create an instance of the class, you must provide the object that allows access to the data:
- The [getAllArticles] and [getArticleById] methods rely on the methods of the same name in the [dao] layer
- The [buy] method validates the purchase of a shopping cart. This validation simply involves decrementing the stock of the purchased items. An item can only be purchased if there is sufficient stock. If this is not the case, the purchase is rejected: the item remains in the shopping cart and an error is reported in the [errors] list. A validated purchase is removed from the cart, and the stock of the corresponding item is decremented by the quantity purchased.
1.4.4.5. Generating the [domain] layer assembly
The Visual Studio project is configured to generate the [webarticles-domain.dll] assembly. This is generated in the project’s [bin] folder:
![]() | ![]() |
1.4.4.6. NUnit tests for the [domain] layer
The structure of the Visual Studio test project is as follows:

Comments:
- The [tests] project is of type [class library]
- the [NUnit] tests require a reference to the [nunit.framework.dll] assembly
- The [NUnit] test class retrieves an instance of the object under test via Spring. Therefore,
- in the [bin] folder, the Spring class files
- in [References], a reference to the [Spring-Core.dll] assembly from the [bin] folder
- in [bin], a configuration file for Spring
- The test class requires the [webarticles-dao.dll] assembly from the [dao] layer and the [webarticles-domain.dll] assembly from the [domain] layer. These have been placed in the [bin] folder and their references added to the project references.
A NUnit test class for the [domain] layer might look like this:
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 object under test
Private articlesDomain As IArticlesDomain
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' Get an instance of the Spring object factory
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
' Request instantiation of the ArticlesDao object
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
' then the articlesdomain object
articlesDomain = CType(factory.GetObject("articlesdomain"), IArticlesDomain)
End Sub
<Test()> _
Public Sub getAllArticles()
' visual verification
listArticles()
End Sub
<Test()> _
Public Sub getArticleById()
' Delete articles
articlesDao.clearAllArticles()
' verification
Dim articles As IList = articlesDomain.getAllArticles
Assert.AreEqual(0, articles.Count)
' adding 2 articles
articlesDao.addArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.addArticle(New Article(4, "article4", 40, 40, 4))
' verification
articles = articlesDomain.getAllArticles
Assert.AreEqual(2, articles.Count)
' search for article 3
Dim anArticle As Article = articlesDomain.getArticleById(3)
' verification
Assert.AreEqual(anItem.name, "item3")
' search for article 4
anArticle = articlesDao.getArticleById(4)
' verification
Assert.AreEqual(anArticle.name, "article4")
End Sub
<Test()> _
Public Sub buyCart()
' Delete items
articlesDao.clearAllArticles()
' verification
Dim items As IList = itemsDomain.getAllItems
Assert.AreEqual(0, articles.Count)
' adding 2 articles
articlesDao.addArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.addArticle(New Article(4, "article4", 40, 40, 4))
' verification
articles = articlesDomain.getAllArticles
Assert.AreEqual(2, articles.Count)
' create a shopping cart with two items
Dim shoppingCart As New shoppingCart
cart.add(New Purchase(New Item(3, "item3", 30, 30, 3), 10))
cart.add(New Purchase(New Item(4, "item4", 40, 40, 4), 10))
' Checks
Assert.AreEqual(700, cart.cartTotal, 0.000001)
Assert.AreEqual(2, cart.purchases.Count)
' validate cart
itemsDomain.buy(cart)
' checks
Assert.AreEqual(0, itemsDomain.errors.Count)
Assert.AreEqual(0, cart.purchases.Count)
' search for item 3
Dim anItem As Item = itemsDomain.getItemById(3)
' check
Assert.AreEqual(unArticle.currentStock, 20)
' search for item 4
anArticle = articlesDao.getArticleById(4)
' verification
Assert.AreEqual(anItem.currentStock, 30)
' new shopping cart
cart.add(New Purchase(New Item(3, "item3", 30, 30, 3), 100))
' validate cart
itemsDomain.buy(cart)
' validations
Assert.AreEqual(1, articlesDomain.errors.Count)
' search for item 3
anItem = itemsDomain.getItemById(3)
' verification
Assert.AreEqual(anItem.currentStock, 20)
End Sub
<Test()> _
Public Sub testRemovePurchases()
' Delete the 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.addArticle(article3)
Dim article4 As New Article(4, "article4", 40, 40, 4)
articlesDao.AddArticle(article4)
' reads the ARTICLES table
articles = articlesDomain.getAllArticles()
Assert.AreEqual(2, articles.Count)
' creates a shopping cart with two items
Dim myCart As New Cart
myCart.add(New Purchase(item3, 10))
myCart.add(New Purchase(item4, 10))
' Checks
Assert.AreEqual(700.0, myCart.cartTotal, 0.000001)
Assert.AreEqual(2, myCart.purchases.Count)
' add an item already purchased
myCart.add(New Purchase(item3, 10))
' Checks
' the total must be updated to 1000
Assert.AreEqual(1000.0, myCart.cartTotal, 0.000001)
' still 2 items in the cart
Assert.AreEqual(2, myCart.purchases.Count)
' Quantity of item 3 must have changed to 20
Dim aPurchase As Purchase = CType(myCart.purchases(0), Purchase)
Assert.AreEqual(20, myPurchase.qty)
' Remove item 3 from the cart
myCart.remove(3)
' Checks
' the total must be updated to 400
Assert.AreEqual(400.0, myCart.cartTotal, 0.000001)
' Only 1 item in the cart
Assert.AreEqual(1, myCart.items.Count)
' It must be item #4
Assert.AreEqual(4, CType(myCart.purchases(0), Purchase).item.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
Comments:
- We wanted to write a test program for the [IArticlesDomain] interface that is independent of its implementation class. Therefore, we used Spring to hide the name of the implementation class from the test program.
- The <Setup()> attribute method retrieves a reference to the [articlesdomain] and [articlesdao] objects to be tested from Spring. These are defined in the following [spring-config.xml] file:
<?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>
This file indicates:
- (continued)
- for the singleton [articlesdao], the name of the implementation class [istia.st.articles.dao.ArticlesDaoArrayList] and where to find it [webarticles-dao.dll]. Since instantiation does not require any parameters, none are defined here.
- for the singleton [articlesdomain], the name of the implementation class [istia.st.articles.domain.AchatsArticles] and where to find it [webarticles-domain.dll]. The class [AchatsArticles] has a constructor with one parameter: the singleton managing access to the [dao] layer. Here, this is defined as the singleton [articlesdao] defined previously.
- The test class obtains an instance of the class under test [articlesdomain] as well as an instance of the data access class [articlesdao]. This last point is controversial. The test class should theoretically not need access to the [dao] layer, which it is not even supposed to know about. Here, we have disregarded this "ethic," which, if followed, would have required us to create new methods in our [IArticlesDomain] interface.
To test the [domain] layer, we generate the DLL [tests-webarticles-domain.dll] in the [bin] folder of the [tests] project:
![]() | ![]() |
Then, using the [Nunit-Gui] application, we load this DLL and run the tests:

Readers viewing this document on screen will see that all tests were successful. We will now consider the [domain] layer to be operational.
1.4.5. Conclusion
Remember that we want to build the following three-tier web application:
![]() |
The M model of our MVC application is now written and tested. It is provided to us in two DLLs [webarticles-dao.dll, webarticles-domain.dll]. We can move on to the last layer, the [web] layer, which contains the C controller and the V views. We will first consider a method presented in the document [Web Development with ASP.NET 1.1]
- The C controller is implemented by two files [global.asax, main.aspx]
- The V views are handled by aspx pages
1.5. The [web] layer
The MVC architecture of the web application will be as follows:
![]() |
The business classes [domain], the data access classes [DAO], and the data source | |
the ASPX pages | |
All HTTP client requests pass through the following two controllers: global.asax: handles events related to the initial launch of the application main.aspx: processes each client's request individually |
1.5.1. The views
The views correspond to those presented at the beginning of this document:
list.aspx | The views are located in the [views] folder of the application ![]() | |
info.aspx | ||
cart.aspx | ||
empty-cart.aspx | ||
errors.aspx |
1.5.2. The Controllers
As mentioned, the controller will consist of two elements:
- [global.asax, global.asax.vb]: used primarily to initialize the application and set up the context for all data to be shared among different clients
- [main.aspx, main.aspx.vb]: the actual controller, which handles HTTP requests from clients.
The various client requests will be sent to the [main.aspx] controller and will contain a parameter called [action] specifying the action requested by the client:
request | meaning | controller action | possible responses |
the client wants the list of items | - requests the list of items from the layer profession | - [LIST] - [ERRORS] | |
The client requests information about one of the items displayed in the view [LIST] | - requests the item from the business layer | - [INFO] - [ERRORS] | |
The customer purchases an item | - requests the item from the business layer and adds it to the customer's cart | - [INFO] if quantity error - [LIST] if no error | |
the customer wants to remove an item from their cart | - retrieve the cart from the session and modifies it | - [CART] - [EMPTY CART] - [ERRORS] | |
The customer wants to view their cart | - retrieves the cart from the session | - [SHOPPING CART] - [EMPTY CART] - [ERRORS] | |
The customer has finished shopping and proceeds to checkout | - Updates the database with the stock levels of the purchased items - empties the customer's cart of items whose purchase has been confirmed | - [LIST] - [ERRORS] |
1.5.3. Application configuration
We will aim to configure the application to make it as flexible as possible with regard to changes such as:
- changes to the URLs of the various views
- changes to the classes implementing the [IArticlesDao] and [IArticlesDomain] interfaces
- changes to the DBMS, the database, or the articles table
1.5.3.1. URL changes
The names of the view URLs will be placed in the application's [web.config] configuration file along with a few other parameters:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
..
<appSettings>
<add key="urlMain" value="/webarticles/main.aspx"/>
<add key="urlInfos" value="views/infos.aspx"/>
<add key="urlErrors" value="views/errors.aspx"/>
<add key="urlList" value="views/list.aspx"/>
<add key="urlCart" value="views/cart.aspx"/>
<add key="urlEmptyCart" value="views/emptycart.aspx"/>
</appSettings>
</configuration>
1.5.3.2. Changing the classes that implement the interfaces
In the spirit of three-tier architectures, the layers must be isolated from one another. This isolation is achieved as follows:
- layers communicate with each other via interfaces, not concrete classes
- the code of one layer never instantiates the class of another layer itself in order to use it. It simply requests an instance of the interface implementation from an external tool—in this case, Spring—for the layer it wishes to use. To do this, we know that it does not need to know the name of the implementation class, but only the name of the Spring singleton for which it wants a reference.
In our application, Spring will be configured in the web application’s [web.config] file as follows:
<?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="views/infos.aspx"/>
<add key="urlErrors" value="views/errors.aspx"/>
<add key="urlList" value="views/list.aspx"/>
<add key="urlCart" value="views/cart.aspx"/>
<add key="urlEmptyCart" value="views/emptycart.aspx"/>
</appSettings>
</configuration>
To access the [business] layer, a class in the [web] layer can request the [articlesDomain] singleton. Spring will then instantiate an object of type [istia.st.articles.domain.AchatsArticles]. For this instantiation, it needs an object of type [articlesDao], i.e., an object of type [istia.st.articles.dao.ArticlesDaoArrayList]. Spring will then instantiate such an object. At the end of the operation, the [web] layer that requested the [articlesDomain] singleton has the entire chain connecting it to the data source:
![]() |
1.5.3.3. Changes related to the DBMS or the database
This point will be ignored here since we are working in a test application without a DBMS. We will address the implementation of a [dao] layer based on a DBMS in a later section.
1.5.4. The <asp:> tag library
Consider the [ERRORS] view, which displays a list of errors:

The [ERRORS] view is responsible for displaying a list of errors that the [main.aspx] controller has placed in the request context under the name [context.Items("errors")]. There are several ways to write such a page. Here, we are only interested in the error display portion.
Remember that an ASPX page has an HTML presentation layer and a .NET code layer that prepares the data the presentation layer must display. These two parts can be in the same [aspx] file (WebMatrix solution) or in two files: [aspx] for the presentation, [aspx.vb] for the code. The latter solution is the one used by Visual Studio. To complicate matters, the HTML presentation layer can also contain .NET code, which tends to blur the separation between the [controller] and [presentation] layers of the view. This approach is generally strongly discouraged. Removing all code from the [presentation] section required the creation of tag libraries. These "hide" the code under the guise of tags similar to HTML tags. We present two possible solutions for the [ERRORS] page.
Our first solution uses .NET code in the [presentation] section of the page. The ASPX page retrieves the list of errors present in the request in its controller section [errors.aspx.vb]:
Protected errors As ArrayList
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
' retrieve the errors
errors = CType(context.Items("errors"), ArrayList)
End Sub
then display them in the [presentation, errors.aspx] section:
<h2>The following errors occurred:</h2>
<ul>
<%
for i as integer=0 to errors.count-1
response.write("<li>" & errors(i).ToString & "</li>")
next
%>
</ul>
The second solution uses the <asp:repeater> tag from the ASP.NET <asp:> tag library. If you build an ASPX page graphically, this tag is available as a server control that you drag onto the design form. If you write the ASPX code manually, you can refer to the tag library.
With the <asp:> tag library, the ASPX code for the previous [ERRORS] view becomes the following:
<asp:Repeater id="rptErrors" runat="server">
<HeaderTemplate>
<h3>The following errors occurred:
</h3>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<%# Container.DataItem %>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
The
tag is used to repeat an HTML template across the various items in a data source. Its various elements are as follows:
the HTML template to display before the data source elements are displayed | |
the HTML template to repeat for each item in the data source. The expression [<%# Container.DataItem %>] is used to display the value of the current item in the data source | |
the HTML template to display after the data source items have been displayed |
The data source is bound to the tag, typically in the [controller] section of the page:
Protected WithEvents rptErrors As System.Web.UI.WebControls.Repeater
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
..
' bind errors to rptErrors
With rptErrors
.DataSource = context.Items("errors")
.DataBind()
End With
End Sub
This binding can also be done during page design if the data source is already known, such as an existing database.
In our views, we will use another tag: <asp:datagrid>, which allows us to display a data source as a table.
1.5.5. Structure of the Visual Studio solution for the [webarticles] application
A web application is a puzzle with many pieces. Giving it an MVC architecture generally increases the number of these pieces. The structure of the [webarticles] application in [Visual Studio] is as follows:
![]() | ![]() | ![]() |
![]() |
Comments:
- The [web] project is of the [Class Library] type and not of the [ASP.NET Web Application] type, as one might logically expect. The [ASP.NET Web Application] type requires the presence of the IIS web server on the development machine or on a remote machine. The IIS server is not included by default on Windows XP Home Edition machines. However, many PCs are sold with this version. To allow readers using Windows XP to implement the application under study, we will use the Cassini Web server (see appendix), available for free from Microsoft, and we will replace the [ASP.NET Web Application] project with a [Class Library] project. This entails a few drawbacks, which are explained in the appendices.
- The DLLs used by the application are as follows:
contains the classes of the data access layer | |
contains the classes of the business layer | |
contains the Spring classes that allow us to integrate the web, domain, and DAO layers | |
logging classes—used by Spring |
These DLLs are placed in the [bin] folder and added to the project references.
1.5.6. The ASPX views
As previously recommended, we will use the <asp:> tag library in our ASPX views.
1.5.6.1. The user component [entete.ascx]
To ensure consistency across the different views, they will share the same header, which displays the application name along with the menu:
![]() | ![]() |
The menu is dynamic and set by the controller. The controller includes an "actions" key attribute in the request sent to the ASPX page, with an associated value of an array of Hashtable() elements. Each element in this array is a dictionary intended to generate a header menu option. Each dictionary has two keys:
- href: the URL associated with the menu option
- link: the menu text
We will turn the header into a user control. A user control encapsulates a section of a page (layout and associated code) into a component that can then be reused in other pages. Here, we want to reuse the [entete] component in other views of the application. The presentation code will be in [entete.ascx] and the associated control code in [entete.ascx.vb]. The presentation code will use an <asp:repeater> component to display the menu options table:
![]() |
No. | type | name | role |
1 | repeater | rptMenu data source: an array of dictionaries with two keys: href, link | display menu options |
The page presentation code will be as follows:
Comments:
- The [repeater] component is defined on lines 6–14
- Each element in the data source associated with the repeater is a dictionary with two keys: href (line 9) and link (line 10)
The associated control code will be as follows:
Comments:
- The [EnteteWebArticles] component has a public, write-only [actions] property - line 7
- This property allows the <asp:repeater> component named [rptMenu]—line 10—to be bound to the array of options calculated by the application controller—lines 11–12.
The other views in the application will use the header defined by [entete.ascx]. The [erreurs.aspx] page, for example, will include the header using the following code:
Comments:
- Line 1 specifies that the <WA:entete> tag must be associated with the component defined by the [entete.ascx] file. The [TagPrefix] and [TagName] attributes are optional.
- Once this is done, the component is inserted into the page's presentation code using line 9. At runtime, this tag will include the code from the [entete.ascx] page into the ASPX page that contains it. The control code [erreurs.aspx.vb] will handle initializing this component. It can do so as follows:
Comments:
- Line 6 creates an object of type [EnteteWebArticles], which is the type of the component being created
- Line 11 initializes the [actions] property of this object
1.5.6.2. The [liste.aspx] view
1.5.6.2.1. Introduction
This view displays the list of items available for sale:
![]() | ![]() |
It is displayed following a request to /main?action=list or /main?action=cartvalidation. The elements of the controller request are as follows:
Hashtable() object - the array of menu options | |
ArrayList of objects of type [Item] | |
String object - message to display at the bottom of the page |
Each [Info] link in the HTML table of articles has a URL in the form [?action=info&id=ID], where ID is the id field of the displayed article.
1.5.6.2.2. Page components
![]() |
No. | type | name | role |
user component | header | display header | |
DataGrid | DataGridArticles 3 - related column: header: Name, field: name 4 - related column: header: Price, field: price 5 - hypertext column: text: Info, URL field: id, URL format: /webarticles/main.aspx?action=info&id={0} | display items for sale | |
label | lblMessage | display a message |
Let’s review how to set these properties:
- In Visual Studio, select the [DataGrid] to access its properties sheet:

- Use the [AutoFormat] link above to manage the layout of the displayed grid
- and the [Property Generator] link to manage its content
1.5.6.2.3. Presentation code [liste.aspx]
Comments:
- Line 9 defines the page header
- Lines 12–24 define the properties of the [DataGrid]
- Line 26 defines the [lblMessage] label
1.5.6.2.4. Controller code [liste.aspx.vb]
Comments:
- The page components appear on lines 13–15. Note that we needed to create an [EnteteWebArticles] object using the [new] operator, whereas this was not necessary with the other components. Without this explicit creation, we encountered a runtime error indicating that the [entete] object did not reference anything. This point warrants further investigation. It has not been investigated.
- The table of header menu options is retrieved from the context to initialize the page’s [entete] component—line 20
- The list of articles is taken from the context—line 22
- to initialize the [DataGridArticles] component—lines 24–27
- The [lblMessage] component is initialized with a message placed in the context—line 29
1.5.6.3. The [infos.aspx] view
1.5.6.3.1. Introduction
This view displays information about an item and also allows it to be purchased:

It is displayed following a request /main?action=infos&id=ID or a request /main?action=achat&id=ID when the quantity purchased is incorrect. The controller request parameters are as follows:
Hashtable() object - the array of menu options | |
object of type [Article] - item to display | |
String object - message to display in case of an error with the quantity | |
String object - value to display in the [Qty] input field |
The [msg] and [qte] fields are used in case of an input error regarding the quantity:

This page contains a form that is submitted via the [Buy] button. The target URL for the POST request is [?action=purchase&id=ID], where ID is the ID of the purchased item.
1.5.6.3.2. Page components
![]() |
No. | type | name | role |
user component | header | display header | |
literal | litID | display item number | |
DataGrid | DataGridArticle 3 - related column: header: Name, field: name 4 - related column: header: Price, field: price 5 - related column: header: Current Stock, field: currentStock 6 - related column: header: Minimum Stock, field: stockMinimum | display an item | |
HTML Submit | Submit the form | ||
HTML Input runat=server | txtQty | Enter the quantity purchased | |
label | lblMsgQte | any error message |
1.5.6.3.3. The presentation code [infos.aspx]
Comments:
- the header is included in the page - line 9
- The literal [litId] is defined on line 10
- The DataGrid [DataGridArticles] is defined on lines 12–34
- The form is defined on lines 36–46. It is of type POST.
- The POST target is provided by a variable [strAction] - line 36. This variable must be defined by the controller.
- The input field for the quantity purchased is defined on line 41. It is a server-side HTML component (runat=server). On the code side, it is accessed via an object.
- Line 42 defines the label [lblMsgQte], which will contain any error messages regarding the entered quantity
1.5.6.3.4. The control code [infos.aspx.vb]
Comments:
- The page components are defined in lines 10–14
- The class defines a public property [strAction] used to define the form's POST target - lines 17-25
- The article to be displayed is retrieved from the application context - line 30
- The array of header menu options is retrieved from the context to initialize the page's [entete] component—line 32
- lines 33–39: the [DataGridArticle] component is bound to a data source of type [ArrayList] containing only the article retrieved in line 30
- the [lblMsgQte, txtQte] components are initialized with information taken from the context - lines 42-45
- the [straction] property is also initialized with information taken from the context - line 47. This variable is used to generate the [action] attribute of the HTML form present on the page:
1.5.6.4. The [panier.aspx] view
1.5.6.4.1. Introduction
This view displays the contents of the shopping cart:

It is displayed in response to a request such as /main?action=cart or /main?action=cancelpurchase&id=ID. The controller's request parameters are as follows:
object Hashtable() - the array of menu options | |
object of type [Cart] - the cart to display |
Each [Remove] link in the HTML table of shopping cart items has a URL in the form [?action=removeitem&id=ID], where ID is the [id] field of the item to be removed from the cart.
1.5.6.4.2. Page components
![]() |
No. | type | name | role |
user component | header | display header | |
DataGrid | DataGridPurchases 3 - related column - header: Item, field: name 4 - related column - header: Qty, field: qty 5 - related column - header: Price, field: price 6 - related column - header: Total, field: total, formatting {0:C} 7 - hypertext column - Text: Remove, URL: id, URL format: /webarticles/main.aspx?action=retirerachat&id={0} | display the list of purchased items | |
label | lblTotal | display the amount due |
1.5.6.4.3. The presentation code [panier.aspx]
Comments
- Line 9 includes the header
- Lines 12–27 define the [DataGridAchats] component
- Line 29: the [lblTotal] component is defined
1.5.6.4.4. The control code [panier.aspx.vb]
Comments:
- The page components are declared on lines 11–13
- The initialization of the [header] component is identical to that found in the pages already studied—line 17
- The shopping cart to be displayed is retrieved from the session—line 19
- Displaying this shopping cart using the [DataGridAchats] component poses problems. The difficulty stems from the component’s initialization. Let’s review its columns:
- [Article] column associated with the [name] field in the data source
- [Qty] column associated with the [qty] field in the data source
- [Price] column associated with the [price] field in the data source
- [Total] column associated with the [total] field in the data source
The data source we have is the shopping cart and its shopping list. The latter will serve as the data source for the [DataGrid]. However, the [Purchase] objects that will populate the rows of the [DataGrid] do not have the [name, qty, price, total] properties expected by the [DataGrid]. Therefore, we create here, specifically for the [DataGrid], a data source whose elements have the characteristics expected by the [DataGrid]. These elements will be of type [PurchaseLine], a class created for this purpose and derived from the [Purchase] class—lines 36–66
- Once the [PurchaseLine] class is defined, the data source for [DataGridPurchases] is built from the shopping cart found in the session—lines 20–30
- The total purchase amount is displayed using the [totalPanier] property of the [Panier] class—line 32
1.5.6.5. The [emptyCart.aspx] view
1.5.6.5.1. Introduction
This view displays information indicating that the shopping cart is empty:

It is displayed following a request to /main?action=panier or /main?action=retirerachat&id=ID. The controller request parameters are as follows:
Hashtable() object - the menu options array |
1.5.6.5.2. Page components
![]() |
No. | type | name | role |
user component | header | display header |
1.5.6.5.3. The presentation code [emptycart.aspx]
Comments:
- The header is included on line 9
1.5.6.5.4. The control code [paniervide.aspx.vb]
Comments:
- We simply initialize the page's only dynamic component - line 10
1.5.6.6. The [errors.aspx] view
1.5.6.6.1. Introduction
This view is displayed in the event of errors:

It is displayed following any request that results in an error, except for the purchase action with an incorrect quantity, which is handled by the [INFOS] view. The elements of the controller request are as follows:
Hashtable() object - the menu options array | |
ArrayList of [String] objects representing the error messages to display |
1.5.6.6.2. Page components
![]() |
No. | type | name | role |
user component | header | display header | |
repeater | rptErrors | display the list of errors |
1.5.6.6.3. The presentation code [errors.aspx]
Comments:
- The header is defined on line 9
- The [rptErrors] component is defined on lines 13–19. Its content comes from a data source of type [ArrayList] containing [String] objects.
1.5.6.6.4. The control code [errors.aspx.vb]
Comments:
- The [header] component is initialized as usual, lines 9 and 14
- The [rptErrors] component is initialized with the [ArrayList] error list found in the context—lines 16–19
1.5.7. The global.asax and main.aspx controllers
We still need to write the core of our web application: the controller. Its role is to:
- retrieve the client’s request,
- process the action requested by the client using the business classes,
- send the appropriate view in response.
1.5.7.1. The [global.asax.vb] controller
When the application receives its very first request, the [Application_Start] procedure in the [global.asax.vb] file is executed. This will happen only once. The purpose of the [Application_Start] procedure is to initialize the objects required by the web application, which will be shared in read-only mode by all client threads. These shared objects can be placed in two locations:
- the controller’s private fields
- the application's execution context (Application)
The [Application_Start] method in the [global.asax.vb] file will perform the following actions:
- check the [web.config] file for the parameters necessary for the application to function properly. These were described in section 1.5.3.
- it will place a list of any errors in the application context in the form of an [ArrayList errors] object. This list will be empty if there are no errors but will exist nonetheless.
- If there were errors, the [Application_Start] method stops there. Otherwise, it requests a reference to a singleton of type [IArticlesDomain], which will be the business object that the controller will use for its needs. As explained in 1.5.3.2, the controller will request this singleton from the Spring framework. This instantiation operation may result in various errors. If so, these errors will again be stored in the [errors] object of the application context.
The [global.asax.vb] controller has a [Session_Start] procedure that runs every time a new client arrives. In this procedure, we will create an empty shopping cart for the client. This shopping cart will be maintained throughout this particular client’s requests. The code could be as follows:
Comments:
- The expected parameters in [web.config] are defined in an array - line 18
- They are searched for in [web.config]. If they are present, they are stored in the application context; otherwise, an error is logged in the [errors] error list - lines 21-33
- If there are no errors, Spring is asked for a reference to the [articlesDomain] singleton, which manages access to the application's [domain] layer - lines 35-47. Any errors are logged in [errors].
- Errors are logged in the application context - line 49
- The procedure exits if there were any errors - line 51
- We create an array of three dictionaries. Each has two keys: href and link. This array represents the three possible menu options—lines 52–71
- This array is stored in the application context - line 73
- For each new customer, the [Session_Start] procedure is executed. An empty shopping cart is created in the customer's session—lines 78–81
1.5.7.2. The [main.aspx.vb] controller
The [main.aspx.vb] controller handles all client requests. These requests all follow the format [/webarticles/main.aspx?action=XX]. A request is processed as follows:
- The [errors] object in the application context is checked. If it is not empty, this means errors occurred during application initialization and the application cannot run. In response, the [ERRORS] view is sent.
- The [action] parameter of the request will be retrieved and checked. If it does not correspond to a known action, the [ERRORS] view is sent with an appropriate error message.
- If the [action] parameter is valid, the client's request is passed to a procedure specific to the action for processing:
method | request | processing | possible responses |
GET /main?action=list | - request the list of items from the business class - display it | [LIST] or [ERRORS] | |
GET /main?action=info&id=ID | - retrieve the item with id=ID from the business class - display it | [INFO] or [ERRORS] | |
POST /main?action=purchase&id=ID - The quantity purchased is included in the posted parameters | - request the item with id=ID from the business class - add it to the cart in the customer session | [LIST] or [INFO] or [ERRORS] | |
GET /main?action=removePurchase&id=ID | - Remove the item with id=ID from the shopping list in the customer's session | [CART] | |
GET /main?action=cart | - Display the client session | [CART] or [EMPTY_CART] | |
GET /main?action=cartvalidation | - Decrement the stock levels of all items in the customer's [LIST] or [ERRORS] | [LIST] or [ERRORS] |
The controller template [main.aspx.vb] could look like this:
Comments:
- The class has two private fields that will be shared among the methods—lines 15–16:
- articlesDomain: the singleton for accessing the [domain] layer
- options: the array of dictionaries containing menu options
- The [Page_Load] procedure:
- will initialize the class's two private fields
- retrieves the [action] parameter from the request and executes the method that handles that action.
1.5.7.3. The [Page_Load] method
This event is the first to occur on the page. The code is as follows:
Comments:
- Each time the page loads, we ensure that the application initialization performed by [global.asax] was successful.
- To do this, we retrieve the list of errors placed there by [global.asax] from the application context - line 4
- If this list is not empty, we display the [ERRORS] view—lines 6–10
- We retrieve the [articlesDomain] singleton placed by [global.asax] in the application context and store it in the private field [articlesDomain] so that it is available to the class’s various methods—line 12
- We perform a similar operation with the menu options array - line 14
- retrieve the [action] parameter from the request - line 16
- We execute the method corresponding to the requested action. An unspecified action is treated as the [list] action - lines 16-36
1.5.7.4. Handling the [list] action
This involves displaying the list of items:

The code is as follows:
Comments:
- Any errors are placed in an [ArrayList] - line 4
- The list of items is requested from the [articlesDomain] singleton - lines 5-12
- If there were errors, the [ERRORS] view is rendered - lines 13-19
- otherwise, the [LIST] view is returned - lines 20-24
1.5.7.5. Handling the [info] action
The client has requested information about a specific item:

The code is as follows:
Comments:
- Any errors are placed in an [ArrayList] - line 4
- The ID of the requested item is retrieved from the request - line 6
- This ID is validated. It must exist and must be an integer. If not, the [ERRORS] view is displayed with the appropriate error message - lines 7-27
- Once the ID is verified, the item is requested from the [articlesDomain] singleton. If an exception occurs, the [ERRORS] view is sent - lines 29-39
- If the item is not found, the [ERRORS] view is sent—lines 41–48
- If the item is found, it is placed in the user's session and then displayed in the [INFO] view - lines 50-56
1.5.7.6. Processing the [purchase] action
The customer has purchased the item displayed in the [INFO] view.

The code is as follows:
Comments:
- The item placed in the session is retrieved - line 5
- If it is not there (the session may have expired), the [LIST] view is displayed - lines 7-10
- The quantity purchased is retrieved from the query - line 12
- Its validity is checked - lines 13-29
- if invalid, depending on the case, the [LIST] view is displayed - line 16 or the [INFO] view - lines 24-28
- If everything is normal, the purchase is added to the cart - lines 31-32
- then the [LIST] view is sent - line 34
1.5.7.7. Processing the [cart] action
The customer has made several purchases and wants to view the shopping cart:

The code is as follows:
Comments:
- We retrieve the cart from the session - line 4. We do not check here to ensure that we actually retrieve something. We should do so because the session may have expired.
- If the cart is empty, we send the [EMPTY CART] view - lines 6-10
- Otherwise, we send the [SHOPPINGCART] view—lines 11–14
1.5.7.8. Processing the [removepurchase] action
The customer wants to remove a purchase from their cart:

The code is as follows:
Comments:
- Retrieve the shopping cart from the session - line 4. We do not check here to ensure that something is actually retrieved. This should be done because the session may have expired.
- Retrieve the ID [id] of the item to be removed from the query - line 8.
- The corresponding purchase is removed from the shopping cart - line 10
- The validity of the purchased item's ID has not been checked here. If it has an invalid type, an exception will occur and be handled in lines 11–13. If it is valid but does not exist, the [cart.remove] method—line 10—does nothing.
- The new cart is displayed - line 16
1.5.7.9. Processing the [validateCart] action
The customer wants to confirm their cart:

The code is as follows:
Comments:
- We retrieve the cart from the session - line 6. We do not check here to ensure that we actually retrieve something. We should do this because the session may have expired.
- If the session has expired, we will have a [nothing] pointer for the cart, and the [buy] method—line 9—will throw an exception, and the [ERRORS] view will be displayed. However, the error message will be unclear to the user.
- Lines 8–16: We attempt to validate the shopping cart retrieved from the session. Some purchases may fail if the requested quantity exceeds the stock of the requested item. These cases are stored by the [buy] method in an error list, which is retrieved on line 11.
- If there are errors, the [ERRORS] view is sent—lines 18–23
- otherwise, the [LIST] view is sent—lines 25–26
1.6. Conclusion
Here, we have developed an application using the MVC pattern. As of April 2005, there do not appear to be any professional MVC development frameworks for ASP.NET comparable to those available for Java (Struts, Spring, etc.). The [Spring.net] project is expected to release one soon. Until then, the method described above provides a viable MVC development approach for medium-sized applications.































