Skip to content

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.

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

Image

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:

  1. 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.
  1. The controller processes this request. To do so, it may need assistance from the business layer, known as the M in the MVC structure.
  2. 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
  3. 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.
  4. 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. business classes
  2. data access classes
  3. the database

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);
id
primary key uniquely identifying an item
name
item name
price
its price
current stock
current stock
minimum 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:

assembly
content
role
webarticles-dao
- [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
webarticles-domain
- [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:

Image

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:

  1. a constructor for setting the 5 pieces of information for an item: [id, name, price, currentStock, minimumStock]
  2. public properties for reading and writing the 5 pieces of information.
  3. a validation of the data entered for the item. If the data is invalid, an exception is thrown.
  4. 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:

getAllArticles
returns all items from the data source
clearAllArticles
clears the data source
getArticleById
returns the [Article] object identified by its primary key
addArticle
allows you to add an article to the data source
modifyArticle
allows you to modify an article in the data source
deleteItem
allows you to delete an item from the data source
updateItemStock
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:

Image

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

Image

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:

Imports NUnit.Framework

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:

Image

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:

Image

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:

Image

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
Function getAllArticles() As IList
returns the list of [Article] objects from the associated data source
Function getArticleById(ByVal idArticle As Integer) As Article
returns the [Article] object identified by [idArticle]
buy(ByVal cart As Cart)
processes the customer's cart by decrementing the stock of purchased items by the quantity purchased - may fail if stock is insufficient
ReadOnly Property errors() As ArrayList
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:
Public Property item() As item
the purchased item
Public Property qty() As Integer
the quantity purchased
Public ReadOnly Property purchaseTotal() As Double
the purchase amount
Public Overrides Function ToString() As String
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:
ReadOnly Property purchases() As ArrayList
the customer's shopping list - a list of objects of type [Purchase]
add(ByVal aPurchase As Purchase)
adds a purchase to the list of purchases
remove(ByVal purchaseId As Integer)
removes the purchase with idPurchase
ReadOnly Property totalBasket() As Double
the total amount of purchases in the cart
Function ToString() As String
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:
_articlesDao As IArticlesDao
the data access object
_errors As ArrayList
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:
Sub New(ByVal articlesDao As IArticlesDao)
  • 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:

Image

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:

Image

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:

M = Model
The business classes [domain], the data access classes [DAO], and the data source
V = Views
the ASPX pages
C = Controller
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
list.aspx
The views are located in the [views] folder of the application
INFO
info.aspx
CART
cart.aspx
EMPTY CART
empty-cart.aspx
ERRORS
errors.aspx

1.5.2. The Controllers

As mentioned, the controller will consist of two elements:

  1. [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
  2. [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
action=list
the client wants the list of
items
- requests the list of items from the layer
profession
- [LIST]
- [ERRORS]
action=info
The client requests
information about one of the
items displayed in the view
[LIST]
- requests the item from the business layer
- [INFO]
- [ERRORS]
action=purchase
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
action=remove purchase
the customer wants to remove an
item from their cart
- retrieve the cart from the session
and modifies it
- [CART]
- [EMPTY CART]
- [ERRORS]
action=cart
The customer wants to view their
cart
- retrieves the cart from the session
- [SHOPPING CART]
- [EMPTY CART]
- [ERRORS]
action=validate-cart
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:

  1. changes to the URLs of the various views
  2. changes to the classes implementing the [IArticlesDao] and [IArticlesDomain] interfaces
  3. 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:

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:

Image

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

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

tag is used to repeat an HTML template across the various items in a data source. Its various elements are as follows:

HeaderTemplate
the HTML template to display before the data source elements are displayed
ItemTemplate
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
FooterTemplate
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:
webarticles-dao.dll
contains the classes of the data access layer
webarticles-domain.dll
contains the classes of the business layer
Spring.Core.dll
contains the Spring classes that allow us to integrate the web, domain, and DAO layers
log4net.dll
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:

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

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:

Namespace istia.st.articles.web
    Public Class WebArticleHeader
        Inherits System.Web.UI.UserControl

        Protected WithEvents rptMenu As System.Web.UI.WebControls.Repeater

        Public WriteOnly Property actions() As Hashtable()
            Set(ByVal Value As Hashtable())
                ' associate the actions array with its component
                With rptMenu
                    .DataSource = Value
                    .DataBind()
                End With
            End Set
        End Property
    End Class
End Namespace

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:

<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx" %>
<%@ Page inherits="istia.st.articles.web.ErreursWebarticles" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h3>The following errors occurred:
        </h3>    

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:
    Public Class WebArticleErrors
        Inherits System.Web.UI.Page

        ' page components
...
        Protected WithEvents header As New WebArticleHeader

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
            ' Bind the menu options to rptmenu
            header.actions = CType(context.Items("options"), Hashtable())
...
        End Sub
    End Class

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:

actions
Hashtable() object - the array of menu options
listarticles
ArrayList of objects of type [Item]
message
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
1
user component
header
display header
2
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
6
label
lblMessage
display a message

Let’s review how to set these properties:

  • In Visual Studio, select the [DataGrid] to access its properties sheet:

Image

  • 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]
<%@ Page codebehind="liste.aspx.vb" inherits="istia.st.articles.web.ListeWebarticles" autoeventwireup="false" Language="vb" %>
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx"%>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h2>List of articles</h2>
        <P>
            <asp:DataGrid id="DataGridArticles" runat="server" ForeColor="Black" BackColor="LightGoldenrodYellow"
                BorderColor="Tan" CellPadding="2" BorderWidth="1px" GridLines="None" AutoGenerateColumns="False">
                <SelectedItemStyle ForeColor="GhostWhite" BackColor="DarkSlateBlue"></SelectedItemStyle>
                <AlternatingItemStyle BackColor="PaleGoldenrod"></AlternatingItemStyle>
                <HeaderStyle Font-Bold="True" BackColor="Tan"></HeaderStyle>
                <FooterStyle BackColor="Tan"></FooterStyle>
                <Columns>
                    <asp:BoundColumn DataField="name" HeaderText="Name"></asp:BoundColumn>
                    <asp:BoundColumn DataField="price" HeaderText="Price" DataFormatString="{0:C}"></asp:BoundColumn>
                    <asp:HyperLinkColumn Text="Info" DataNavigateUrlField="id" DataNavigateUrlFormatString="/webarticles/main.aspx?action=info&amp;id={0}"></asp:HyperLinkColumn>
                </Columns>
                <PagerStyle HorizontalAlign="Center" ForeColor="DarkSlateBlue" BackColor="PaleGoldenrod"></PagerStyle>
            </asp:DataGrid></P>
        <P>
            <asp:Label id="lblMessage" runat="server" BackColor="#FFC080"></asp:Label></P>
    </body>
</HTML>

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]
Imports System
Imports System.Collections
Imports System.Data
Imports istia.st.articles.dao

Namespace istia.st.articles.web

    ' manages the page displaying the list of articles
    Public Class WebArticleList
        Inherits System.Web.UI.Page

        ' page components
        Protected WithEvents lblMessage As System.Web.UI.WebControls.Label
        Protected WithEvents DataGridArticles As System.Web.UI.WebControls.DataGrid
        Protected WithEvents header As New WebArticleHeader

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            ' Prepare the [list] view based on context information
            ' bind the menu options to rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
            ' retrieve the articles into a DataTable
            Dim items As ArrayList = CType(context.Items("items"), ArrayList)
            ' bind them to the page's [DataGrid] component
            With DataGridArticles
                .DataSource = items
                .DataBind()
            End With
            ' display the message
            lblMessage.Text = context.Items("message").ToString
        End Sub
    End Class
End Namespace

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:

Image

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:

actions
Hashtable() object - the array of menu options
item
object of type [Article] - item to display
msg
String object - message to display in case of an error with the quantity
qty
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:

Image

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
1
user component
header
display header
2
literal
litID
display item number
3 to 6
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
7
HTML Submit
 
Submit the form
8
HTML Input
runat=server
txtQty
Enter the quantity purchased
9
label
lblMsgQte
any error message
1.5.6.3.3. The presentation code [infos.aspx]
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx" %>
<%@ Page codebehind="infos.aspx.vb" inherits="istia.st.articles.web.InfosWebarticles" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h2>Article with id [<asp:Literal id="litId" runat="server"></asp:Literal>]</h2>
        <P>
            <asp:DataGrid id="DataGridArticle" runat="server" BackColor="White" BorderColor="#E7E7FF" CellPadding="3"
                BorderWidth="1px" BorderStyle="None" GridLines="Horizontal" AutoGenerateColumns="False">
                <SelectedItemStyle Font-Bold="True" ForeColor="#F7F7F7" BackColor="#738A9C"></SelectedItemStyle>
                <AlternatingItemStyle BackColor="#F7F7F7"></AlternatingItemStyle>
                <ItemStyle HorizontalAlign="Center" ForeColor="#4A3C8C" BackColor="#E7E7FF"></ItemStyle>
                <HeaderStyle Font-Bold="True" HorizontalAlign="Center" ForeColor="#F7F7F7" BackColor="#4A3C8C"></HeaderStyle>
                <FooterStyle ForeColor="#4A3C8C" BackColor="#B5C7DE"></FooterStyle>
                <Columns>
                    <asp:BoundColumn DataField="name" HeaderText="Name">
                        <HeaderStyle HorizontalAlign="Center"></HeaderStyle>
                    </asp:BoundColumn>
                    <asp:BoundColumn DataField="price" HeaderText="Price" DataFormatString="{0:C}">
                        <HeaderStyle HorizontalAlign="Center"></HeaderStyle>
                    </asp:BoundColumn>
                    <asp:BoundColumn DataField="currentStock" HeaderText="Current Stock">
                        <HeaderStyle HorizontalAlign="Center"></HeaderStyle>
                    </asp:BoundColumn>
                    <asp:BoundColumn DataField="minStock" HeaderText="Minimum Stock">
                        <HeaderStyle HorizontalAlign="Center"></HeaderStyle>
                    </asp:BoundColumn>
                </Columns>
                <PagerStyle HorizontalAlign="Right" ForeColor="#4A3C8C" BackColor="#E7E7FF" Mode="NumericPages"></PagerStyle>
            </asp:DataGrid></P>
        <HR width="100%" SIZE="1">
        <form method="post" action="<%=strAction%>">
            <table>
                <tr>
                    <td><input type="submit" value="Buy"></td>
                    <td>Qty</td>
                    <td><INPUT type="text" maxLength="3" size="3" id="txtQte" runat="server"></td>
                    <td><asp:Label id="lblMsgQte" runat="server" />
                    </td>
                </tr>
            </table>
        </form>
    </body>
</HTML>

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]
Imports istia.st.articles.dao
Imports System
Imports System.Collections

Namespace istia.st.articles.web

    ' manages the information page for an article
    Public Class WebArticleInfo
        Inherits System.Web.UI.Page
        Protected WithEvents lblMsgQte As System.Web.UI.WebControls.Label
        Protected WithEvents litId As System.Web.UI.WebControls.Literal
        Protected WithEvents txtQte As System.Web.UI.HtmlControls.HtmlInputText
        Protected WithEvents DataGridArticle As System.Web.UI.WebControls.DataGrid
        Protected WithEvents entete As New EnteteWebArticles

        ' The URL where the form will be posted
        Private _strAction As String
        Public Property strAction() As String
            Get
                Return _strAction
            End Get
            Set(ByVal Value As String)
                _strAction = Value
            End Set
        End Property

        ' page display
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            ' retrieve query data
            Dim anArticle As Article = CType(Session.Item("article"), Article)
            ' bind the menu options to rptmenu
            header.actions = CType(context.Items("options"), Hashtable())
            ' bind the article to the [DataGrid]
            Dim articles As New ArrayList
            articles.Add(anArticle)
            With DataGridArticle
                .DataSource = articles
                .DataBind()
            End With
            ' the id label
            litId.Text = unArticle.id.ToString
            ' the error message
            lblMsgQte.Text = context.Items("msg").ToString
            ' the previous quantity
            txtQte.Value = context.Items("qte").ToString
            ' action URL
            strAction = "?action=purchase&id=" + unArticle.id.ToString
        End Sub
End Namespace

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:
        <form method="post" action="<%=strAction%>">
....
        </form>

1.5.6.4. The [panier.aspx] view

1.5.6.4.1. Introduction

This view displays the contents of the shopping cart:

Image

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:

actions
object Hashtable() - the array of menu options
cart
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
1
user component
header
display header
2
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
8
label
lblTotal
display the amount due
1.5.6.4.3. The presentation code [panier.aspx]
<%@ Page codebehind="panier.aspx.vb" inherits="istia.st.articles.web.PanierWebarticles" autoeventwireup="false" Language="vb" %>
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h2>Contents of your shopping cart</h2>
        <P>
            <asp:DataGrid id="DataGridAchats" runat="server" BorderWidth="1px" GridLines="Vertical" CellPadding="4"
                BackColor="White" BorderStyle="None" BorderColor="#DEDFDE" ForeColor="Black" AutoGenerateColumns="False">
                <SelectedItemStyle Font-Bold="True" ForeColor="White" BackColor="#CE5D5A"></SelectedItemStyle>
                <AlternatingItemStyle BackColor="White"></AlternatingItemStyle>
                <ItemStyle BackColor="#F7F7DE"></ItemStyle>
                <HeaderStyle Font-Bold="True" ForeColor="White" BackColor="#6B696B"></HeaderStyle>
                <FooterStyle BackColor="#CCCC99"></FooterStyle>
                <Columns>
                    <asp:BoundColumn DataField="name" HeaderText="Item"></asp:BoundColumn>
                    <asp:BoundColumn DataField="qty" HeaderText="Qty"></asp:BoundColumn>
                    <asp:BoundColumn DataField="price" HeaderText="Price"></asp:BoundColumn>
                    <asp:BoundColumn DataField="totalAchat" HeaderText="Total" DataFormatString="{0:C}"></asp:BoundColumn>
                    <asp:HyperLinkColumn Text="Remove" DataNavigateUrlField="id" DataNavigateUrlFormatString="/webarticles/main.aspx?action=retirerachat&amp;id={0}"></asp:HyperLinkColumn>
                </Columns>
                <PagerStyle HorizontalAlign="Right" ForeColor="Black" BackColor="#F7F7DE" Mode="NumericPages"></PagerStyle>
            </asp:DataGrid></P>
        <P>Order Total:
            <asp:Label id="lblTotal" runat="server"></asp:Label>&nbsp;euros</P>
    </body>
</HTML>

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]
Imports System
Imports System.Collections
Imports System.Data
Imports istia.st.articles.dao
Imports istia.st.articles.domain

Namespace istia.st.articles.web
    ' manages the shopping cart display page
    Public Class WebShoppingCart
        Inherits System.Web.UI.Page
        Protected WithEvents DataGridAchats As System.Web.UI.WebControls.DataGrid
        Protected WithEvents lblTotal As System.Web.UI.WebControls.Label
        Protected WithEvents header As New WebItemHeader

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            ' Bind the menu options to rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
            ' retrieve the shopping cart
            Dim shoppingCart As ShoppingCart = CType(Session.Item("shoppingCart"), ShoppingCart)
            ' transfer the purchases to an array of purchase lines
            Dim purchases(unPanier.purchases.Count - 1) As PurchaseLine
            ' change the type of the ArrayList elements
            For i As Integer = 0 To purchases.Length - 1
                purchases(i) = New PurchaseLine(CType(aBasket.purchases(i), Purchase))
            Next
            ' Bind the data to the [DataGrid] components on the page
            With DataGridPurchases
                .DataSource = purchases
                .DataBind()
            End With
            ' Display the total amount due
            lblTotal.Text = unPanier.totalPanier.ToString
        End Sub

        ' Purchase line built from a Purchase object
        Private Class PurchaseLine
            Inherits Purchase

            ' constructor receives a Purchase
            Public Sub New(ByVal purchaseAs Purchase)
                Me.item = purchase.item
                Me.qty = purchase.qty
            End Sub

            ' id: returns the ID of the purchased item
            Public ReadOnly Property id() As Integer
                Get
                    Return item.id
                End Get
            End Property

            ' name: name of the purchased item
            Public ReadOnly Property name() As String
                Get
                    Return article.name
                End Get
            End Property

            ' price of the purchased item
            Public ReadOnly Property price() As Double
                Get
                    Return item.price
                End Get
            End Property

        End Class
    End Class
End Namespace

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:

Image

It is displayed following a request to /main?action=panier or /main?action=retirerachat&id=ID. The controller request parameters are as follows:

actions
Hashtable() object - the menu options array
1.5.6.5.2. Page components
No.
type
name
role
1
user component
header
display header
1.5.6.5.3. The presentation code [emptycart.aspx]
<%@ Register TagPrefix="WA" TagName="header" Src="header.ascx"%>
<%@ Page codebehind="paniervide.aspx.vb" inherits="istia.st.articles.web.PaniervideWebarticles" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h2>Contents of your shopping cart</h2>
        <P>Your cart is empty</P>
    </body>
</HTML>

Comments:

  • The header is included on line 9
1.5.6.5.4. The control code [paniervide.aspx.vb]
Namespace istia.st.articles.web
    ' manages the empty cart display page
    Public Class EmptyCartWebArticles
        Inherits System.Web.UI.Page
        Protected WithEvents header As New WebArticlesHeader

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            ' the [empty_cart] view is prepared based on the context information
            ' we bind the menu options to rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
        End Sub
    End Class
End Namespace

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:

Image

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:

actions
Hashtable() object - the menu options array
errors
ArrayList of [String] objects representing the error messages to display
1.5.6.6.2. Page components
No.
type
name
role
1
user component
header
display header
2
repeater
rptErrors
display the list of errors
1.5.6.6.3. The presentation code [errors.aspx]
<%@ Register TagPrefix="WA" TagName="header" Src="header.ascx" %>
<%@ Page codebehind="errors.aspx.vb" inherits="istia.st.articles.web.WebArticleErrors" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h3>The following errors occurred:
        </h3>
        <ul>
            <asp:Repeater id="rptErrors" runat="server">
                <ItemTemplate>
                    <li>
                        <%# Container.DataItem %>
                    </li>
                </ItemTemplate>
            </asp:Repeater></ul>
    </body>
</HTML>

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]
Namespace istia.st.articles.web

    ' manages the error page
    Public Class WebArticleErrors
        Inherits System.Web.UI.Page

        ' page components
        Protected WithEvents rptErrors As System.Web.UI.WebControls.Repeater
        Protected WithEvents header As New WebArticleHeader

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            ' prepare the [errors] view based on context information
            ' Bind the menu options to rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
            ' Errors are linked to rptErrors
            With rptErrors
                .DataSource = context.Items("errors")
                .DataBind()
            End With
        End Sub
    End Class

End Namespace

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:

Imports System
Imports System.Web
Imports System.Web.SessionState
Imports System.Configuration
Imports istia.st.articles.domain
Imports System.Collections
Imports Spring.Context

Namespace istia.st.articles.web

    Public Class GlobalWebArticles
        Inherits System.Web.HttpApplication

        ' Initialize application
        Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)

            ' local data
            Dim parameters() As String = {"mainUrl", "errorUrl", "infoUrl", "listUrl", "cartUrl", "emptyCartUrl"}
            Dim errors As New ArrayList

            ' retrieve the application's initialization parameters
            Dim param As String
            For i As Integer = 0 To parameters.Length - 1
                ' read from configuration file
                param = ConfigurationSettings.AppSettings(parameters(i))
                If param Is Nothing Then
                    ' Log the error
                    errors.Add("Parameter [" + parameters(i) + "] missing from the [web.config] file")
                Else
                    ' store the parameter in the application
                    Application.Item(parameters(i)) = param
                End If
            Next
            ' Any errors?
            If errors.Count = 0 Then
                ' Create an IArticlesDomain object to access the business layer
                Dim context As IApplicationContext = CType(ConfigurationSettings.GetConfig("spring/context"), IApplicationContext)
                Dim articlesDomain As IArticlesDomain
                Try
                    articlesDomain = CType(context.GetObject("articlesDomain"), IArticlesDomain)
                    ' Store the object in the application
                    Application.Item("articlesDomain") = articlesDomain
                Catch ex As Exception
                    ' store the error
                    errors.Add("Error while constructing the business layer access object [" + ex.ToString + "]")
                End Try
            End If
            ' errors are placed in the application
            Application.Item("errors") = errors
            ' it's over if there were errors
            If errors.Count <> 0 Then Return
            ' create an array of menu options
            Dim options As New Hashtable
            ' retrieve the controller's URL
            Dim urlMain As String = CType(Application.Item("urlMain"), String)
            Dim anOption As Hashtable
            ' List of items
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=list")
            uneOption.Add("link", "List of articles")
            options.Add("list", anOption)
            ' shopping cart
            anOption = New Hashtable
            anOption.Add("href", urlMain + "?action=cart")
            anOption.Add("link", "View Cart")
            options.Add("cart", anOption)
            ' validate cart
            anOption = New Hashtable
            anOption.Add("href", urlMain + "?action=cartvalidation")
            anOption.Add("link", "Validate Cart")
            options.Add("cartValidation", anOption)
            ' Add the menu options to the application
            Application.Item("options") = options
            Return
        End Sub

        ' initialize session
        Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
            ' create a shopping cart for the customer
            Session.Item("cart") = New Cart
        End Sub
    End Class
End Namespace

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
doList
GET /main?action=list
- request the list of items
from the business class
- display it
[LIST] or [ERRORS]
doInfo
GET /main?action=info&id=ID
- retrieve the item with id=ID from
the business class
- display it
[INFO] or [ERRORS]
doPurchase
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]
doRetirePurchase
GET /main?action=removePurchase&id=ID
- Remove the item with id=ID from the
shopping list in
the customer's session
[CART]
doCart
GET /main?action=cart
- Display the
client session
[CART] or [EMPTY_CART]
doCartValidation
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:

Imports System.Collections
Imports System
Imports System.Data

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

Namespace istia.st.articles.web

    ' Web application controller class
    Public Class MainWebArticles
        Inherits System.Web.UI.Page

        ' private fields
        Private articlesDomain As IArticlesDomain
        Private options As Hashtable

        ' page load
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
....
        End Sub

        ' methods for processing actions

        ' list of items
        Public Sub doList()
...
        End Sub

        ' information about an item
        Public Sub getInfo()
...
        End Sub

        ' purchase an item
        Public Sub doPurchase()
...
        End Sub

        ' Delete a purchase
        Public Sub doRemovePurchase()
...
        End Sub

        ' View shopping cart
        Public Sub viewCart()
...
        End Sub

        ' purchase cart
        Public Sub doValidateCart()
...
        End Sub

    End Class

End Namespace

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:

        ' page load
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            ' check if the application started correctly
            Dim errors As ArrayList = CType(Application.Item("errors"), ArrayList)
            ' if there are errors, display the [errors] view
            If errors.Count <> 0 Then
                context.Items("errors") = errors
                context.Items("options") = New Hashtable() {}
                Server.Transfer(CType(Application("errorsUrl"), String))
            End If
            ' retrieve the access object to the business class
            articlesDomain = CType(Application.Item("articlesDomain"), IArticlesDomain)
            ' as well as the menu options
            options = CType(Application.Item("options"), Hashtable)
            ' retrieve the action to be performed
            Dim action As String = Request.QueryString("action")
            If action Is Nothing Then
                action = "list"
            End If
            ' Execute the action
            Select Case action
                Case "list"
                    doListe()
                "Info" box
                    doInfo()
                "Purchase" case
                    doPurchase()
                Case "cart"
                    doCart()
                Case "removePurchase"
                    doRemovePurchase()
                Case "validateCart"
                    doValidateCart()
                Case Else
                    doList()
            End Select
        End Sub

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:

Image

The code is as follows:

        ' list of items
        Public Sub doList()
            ' error handling
            Dim errors As New ArrayList
            ' request the list of items
            Dim items As IList
            Try
                articles = articlesDomain.getAllArticles
            Catch ex As Exception
                ' Log the error
                errors.Add(ex.ToString)
            End Try
            ' Any errors?
            If errors.Count <> 0 Then
                ' display [errors] view
                context.Items("errors") = errors
                context.Items("options") = New Hashtable() {CType(options("list"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErrors"), String))
            End If
            ' display [list] view
            context.Items("items") = items
            context.Items("options") = New Hashtable() {CType(options("cart"), Hashtable)}
            If context.Items("message") Is Nothing Then context.Items("message") = ""
            Server.Transfer(CType(Application.Item("urlListe"), String))
        End Sub

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:

Image

The code is as follows:

        ' information about an item
        Public Sub doInfos()
            ' error handling
            Dim errors As New ArrayList
            ' retrieve the ID of the requested item
            Dim strId As String = Request.QueryString("id")
            ' Is there anything?
            If strId Is Nothing Then
                ' Not normal—display the error page
                errors.Add("invalid action (action=info, id=none)")
                context.Items("errors") = errors
                context.Items("options") = New Hashtable() {CType(options("list"), Hashtable)}
                Server.Transfer(CType(Application.Item("errorUrl"), String))
                Exit Sub
            End If
            ' Is it an integer?
            Dim id As Integer
            Try
                id = Integer.Parse(strId)
            Catch ex As Exception
                ' Not normal - send the error page
                errors.Add("invalid action (action=info, invalid id[" + strId + "]")")
                context.Items("errors") = errors
                context.Items("options") = New Hashtable() {CType(options("list"), Hashtable)}
                Server.Transfer(CType(Application.Item("errorUrl"), String))
                Exit Sub
            End Try
            ' request the item with the key 'id'
            Dim anArticle As Article
            Try
                unArticle = articlesDomain.getArticleById(id)
            Catch ex As Exception
                ' Data access issue
                errors.Add("Data access problem (" + ex.ToString + ")")
                context.Items("errors") = errors
                context.Items("options") = New Hashtable() {CType(options("list"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErrors"), String))
                Exit Sub
            End Try
            'Has an article been retrieved?'
            If anArticle Is Nothing Then
                ' The article does not exist
                errors.Add("The item with id=" + id.ToString + " does not exist")
                context.Items("errors") = errors
                context.Items("options") = New Hashtable() {CType(options("list"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErrors"), String))
                Exit Sub
            End If
            ' we have the article - we put it in the current session
            Session.Item("article") = anArticle
            ' prepare its display
            context.Items("options") = New Hashtable() {CType(options("list"), Hashtable)}
            context.Items("msg") = ""
            context.Items("qty") = ""
            Server.Transfer(CType(Application.Item("urlInfos"), String))
        End Sub

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.

Image

The code is as follows:

        ' purchase of an item
        Public Sub doPurchase()
            ' purchase of an item
            ' retrieve the item that was in the session
            Dim anItem As Item = CType(Session.Item("item"), Item)
            ' Do we have anything?
            If unArticle Is Nothing Then
                ' Not normal—display the list of items
                doList()
            End If
            ' retrieve the quantity entered
            Dim strQte As String = Request.Form("txtQte")
            ' Is there anything?
            If strQte Is Nothing Then
                ' Not normal - send the list of items
                doListe()
            End If
            ' Do we have an integer?
            Dim qte As Integer
            Try
                qte = Integer.Parse(strQte)
                If (qte <= 0) Then Throw New Exception
            Catch ex As Exception
                ' Not normal - we send the info page along with the error message
                context.Items("options") = New Hashtable() {CType(options("list"), Hashtable)}
                context.Items("msg") = "Incorrect quantity"
                context.Items("qty") = strQty
                Server.Transfer(CType(Application.Item("urlInfos"), String))
            End Try
            ' everything is fine - we add the purchase to the customer's cart
            Dim aBasket As Basket = CType(Session.Item("basket"), Basket)
            unPanier.add(New Purchase(unArticle, qte))
            ' display the list of items
            doList()
        End Sub

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:

Image

The code is as follows:

        ' View shopping cart
        Public Sub doPanier()
            'retrieve the shopping cart from the session
            Dim aBasket As Basket = CType(Session.Item("basket"), Basket)
            ' Is the shopping cart empty?
            If unPanier.purchases.Count = 0 Then
                ' Display empty cart
                context.Items("options") = New Hashtable() {CType(options("list"), Hashtable)}
                Server.Transfer(CType(Application.Item("emptyCartUrl"), String))
            End If
            ' display non-empty cart
            context.Items("options") = New Hashtable() {CType(options("list"), Hashtable), CType(options("cartValidation"), Hashtable)}
            Server.Transfer(CType(Application.Item("cartUrl"), String))
        End Sub

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:

Image

The code is as follows:

        ' removing a purchase
        Public Sub doRetirerAchat()
            'retrieve the shopping cart from the session
            Dim shoppingCart As ShoppingCart = CType(Session.Item("shoppingCart"), ShoppingCart)
            ' remove the purchase
            Try
                ' Retrieve the ID of the removed item
                Dim itemId As Integer = Integer.Parse(Request.QueryString("id"))
                ' remove it from the cart
                unPanier.remove(itemId)
            Catch ex As Exception
                ' Display the list of items
                doList()
            End Try
            ' display the shopping cart
            doShoppingCart()
        End Sub

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:

Image

The code is as follows:

        ' shopping cart purchase
        Public Sub doValidationPanier()
            ' initially, no errors
            Dim errors As ArrayList
            ' retrieve the shopping cart from the session
            Dim aCart As Cart = CType(Session.Item("cart"), Cart)
            ' attempt to validate the cart
            Try
                articlesDomain.buy(aBasket)
                ' Log any purchase errors
                errors = itemsDomain.errors
            Catch ex As Exception
                ' record the error
                errors = New ArrayList
                errors.Add(String.Format("Error validating the cart [{0}]", ex.Message))
            End Try
            ' if errors then error page
            If errors.Count <> 0 Then
                context.Items("errors") = errors
                context.Items("options") = New Hashtable() {CType(options("list"), Hashtable), CType(options("cart"), Hashtable)}
                Server.Transfer(CType(Application.Item("errorUrl"), String))
                Exit Sub
            End If
            ' Everything is fine - display the list of items with a success message
            context.Items("message") = "Your cart has been validated"
            doList()
        End Sub

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.