6. Examples
In this chapter, we will illustrate what we have seen previously through a series of examples.
6.1. Example 1
6.1.1. The Problem
This application must allow a user to calculate their taxes. We are considering the simplified case of a taxpayer who has only their salary to report (2004 figures for 2003 income):
- We calculate the number of tax brackets for the employee as nbParts = nbEnfants / 2 + 1 if they are unmarried, and nbEnfants / 2 + 2 if they are married, where nbEnfants is the number of children.
- If he has at least three children, he is entitled to an additional half-share
- His taxable income R is calculated as R = 0.72 * S, where S is his annual salary
- We calculate his family coefficient QF = R / nbParts
- We calculate his tax I. Consider the following table:
4262 | 0 | 0 |
8382 | 0.0683 | 291.09 |
14,753 | 0.1914 | 1,322.92 |
23,888 | 0.2826 | 2,668.39 |
38,868 | 0.3738 | 4,846.98 |
47,932 | 0.4262 | 6,883.66 |
0 | 0.4809 | 9505.54 |
Each row has 3 fields. To calculate tax I, find the first row where QF <= field1. For example, if QF = 5000, the row found will be
Tax I is then equal to 0.0683*R - 291.09*nbParts. If QF is such that the condition QF<=field1 is never met, then the coefficients from the last row are used. Here:
which gives tax I = 0.4809*R - 9505.54*nbParts.
6.1.2. The application's MVC structure
The application's MVC structure will be as follows:

The controller role will be handled by the [main.aspx] page. There will be three possible actions:
- init: corresponds to the client’s first request. The controller will display the [formulaire.aspx] view
- calcul: corresponds to the request to calculate the tax. If the data in the input form is correct, the tax is calculated using the business class [impots]. The controller returns the [form.aspx] view to the client as it was validated, along with the calculated tax. If the data in the input form is incorrect, the controller will return the [errors.aspx] view with a list of errors and a link to return to the form.
- return: corresponds to returning to the form after an error. The controller displays the [form.aspx] view as it was validated before the error.
The [main.aspx] controller knows nothing about tax calculations. It is simply responsible for managing the client-server dialogue and executing the actions requested by the client. For the [calculate] action, it will rely on the [tax] business class.
6.1.3. The business class
The **impot** class will be defined as follows:
' imported namespaces
Imports System
' class
Namespace st.istia.univangers.fr
Public Class impot
Private limits(), coeffR(), coeffN() As Decimal
' constructor
Public Sub New(ByRef source As impotsData)
' the data needed to calculate the tax
' come from an external source [source]
' we retrieve them - there may be an exception
Dim data() As Object = source.getData
limits = CType(data(0), Decimal())
coeffR = CType(data(1), Decimal())
coeffN = CType(data(2), Decimal())
End Sub
' tax calculation
Public Function calculate(ByVal married As Boolean, ByVal numChildren As Integer, ByVal salary As Long) As Long
' calculate the number of shares
Dim nbParts As Decimal
If married Then
nbParts = CDec(nbChildren) / 2 + 2
Else
nbParts = CDec(nbChildren) / 2 + 1
End If
If nbChildren >= 3 Then
nbParts += 0.5D
End If
' Calculate taxable income & Family Quotient
Dim income As Decimal = 0.72D * salary
Dim QF As Decimal = income / numParts
' tax calculation
limits((limits.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limits(i)
i += 1
End While
Return CLng(revenue * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
End Namespace
A tax object by providing its constructor with a data source of type [impotsData]. This class has a public method [getData] that retrieves the three data arrays needed to calculate the tax, as described earlier. This method can handle an exception if the data could not be retrieved or if it is found to be incorrect. Once the [tax] object is created, its calculate method can be called repeatedly to calculate the taxpayer’s tax based on their marital status (married or single), number of children, and annual salary.
6.1.4. The data access class
The [impotsData] class is the class that provides access to the data. It is an abstract class. A derived class must be created for each new possible data source (arrays, flat files, databases, console, etc.). Its definition is as follows:
Imports System.Collections
Namespace st.istia.univangers.fr
Public MustInherit Class impotsData
Protected limites() As Decimal
Protected coeffr() As Decimal
Protected coeffn() As Decimal
Protected checked As Boolean
Protected valid As Boolean
' data access method
Public MustOverride Function getData() As Object()
' data validation method
Protected Function checkData() As Integer
' checks the acquired data
' there must be data
valid = Not limits Is Nothing AndAlso Not coeffr Is Nothing AndAlso Not coeffn Is Nothing
If Not valid Then Return 1
' must have 3 arrays of the same size
If valid Then valid = limits.Length = coeffr.Length AndAlso limits.Length = coeffn.Length
If Not valid Then Return 2
' the arrays must not be empty
valid = limits.Length ≠ 0
If Not valid Then Return 3
' each array must contain elements >= 0 and in ascending order
valid = check(limits, limits.Length - 1) AndAlso check(coeffr, coeffr.Length) AndAlso check(coeffn, coeffn.Length)
If Not valid Then Return 4
' everything is fine
Return 0
End Function
' checks the validity of an array's contents
Protected Function check(ByRef array() As Decimal, ByVal n As Integer) As Boolean
' array must have its first n elements >= 0 and in strictly ascending order
If array(0) < 0 Then Return False
For i As Integer = 1 To n - 1
If array(i) <= array(i - 1) Then Return False
Next
' OK
Return True
End Function
End Class
End Namespace
The class has the following protected attributes:
array of tax bracket limits | |
array of coefficients applied to taxable income | |
table of coefficients applied to the number of shares | |
Boolean indicating whether the data (limits, coeffr, coeffn) has been verified | |
Boolean indicating whether the data (limits, coeffr, coeffn) is valid |
The class has no constructor. It has an abstract method [getData] that derived classes must implement. The purpose of this method is to:
- assign values to the three arrays limits, coeffr, coeffn
- throw an exception if the data could not be acquired or if it turned out to be invalid.
The class provides the protected methods [checkData] and [check] that verify the validity of the attributes (limits, coeffr, coeffn). This relieves derived classes of the need to implement them. They will simply need to use them.
The first derived class we will use is as follows:
Imports System.Collections
Imports System
Namespace st.istia.univangers.fr
Public Class impotsArray
Inherits impotsData
' constructor without arguments
Public Sub New()
' Initialize arrays with constants
limits = New Decimal() {4262D, 8382D, 14753D, 23888D, 38868D, 47932D, 0D}
coeffr = New Decimal() {0D, 0.0683D, 0.1914D, 0.2826D, 0.3738D, 0.4262D, 0.4809D}
coeffn = New Decimal() {0D, 291.09D, 1322.92D, 2668.39D, 4846.98D, 6883.66D, 9505.54D}
checked = True
valid = True
End Sub
' constructor with three input arrays
Public Sub New(ByRef limits() As Decimal, ByRef coeffr() As Decimal, ByRef coeffn() As Decimal)
' store the data
Me.limits = limits
Me.coeffr = coeffr
Me.coeffn = coeffn
checked = False
End Sub
Public Overrides Function getData() As Object()
' check the data if necessary
Dim error As Integer
If Not checked Then error = checkData() : checked = True
' if invalid, then throw an exception
If Not valid Then Throw New Exception("The tax bracket data is invalid (" + error.ToString + ")")
' otherwise return the three arrays
Return New Object() {limits, coeffr, coeffn}
End Function
End Class
End Namespace
This class, named [impotsArray], has two constructors:
- a constructor without arguments that initializes the attributes (limits, coeffr, coeffn) of the base class with hard-coded arrays
- a constructor that initializes the attributes (limits, coeffr, coeffn) of the base class with arrays passed to it as parameters
The [getData] method, which allows external classes to retrieve the arrays (limits, coeffr, coeffn), simply verifies the validity of the three arrays using the [checkData] method of the base class. It throws an exception if the data is invalid.
6.1.5. Testing business classes and data access classes
It is important to include only business and data access classes that have been verified as correct in a web application. This way, the web application’s debugging phase can focus on the controller and view layers. A test program might look like the following:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
Module test
Sub Main()
' Interactive tax calculation program
' The user enters three pieces of data via the keyboard: married, number of children, salary
' the program then displays the tax due
Const syntax As String = "syntax: married noChildren salary" + ControlChars.Lf + "married: o for married, n for unmarried" + ControlChars.Lf + "noChildren: number of children" + ControlChars.Lf + "salary: annual salary in F"
' Create a tax object
Dim taxObj As Tax = Nothing
Try
taxObj = New Tax(New TaxArray)
Catch ex As Exception
Console.Error.WriteLine(("The following error occurred: " + ex.Message))
Environment.Exit(1)
End Try
' infinite loop
Dim married As String
Dim numberOfChildren As Integer
Dim salary As Long
While True
' Request the parameters for the tax calculation
Console.Out.Write("Tax calculation parameters in the format married nbChildren salary or nothing to stop:")
Dim parameters As String = Console.In.ReadLine().Trim()
' anything to do?
If parameters Is Nothing OrElse parameters = "" Then
Exit While
End If
' Check the number of arguments in the entered line
Dim error As Boolean = False
Dim args As String() = parameters.Split(Nothing)
Dim numParameters As Integer = args.Length
If nbParameters <> 3 Then
Console.Error.WriteLine(syntax)
error = True
End If
' Checking parameter validity
If Not error Then
' married
married = args(0).ToLower()
If married <> "y" And married <> "n" Then
error = True
End If
' nbChildren
Try
nbChildren = Integer.Parse(args(1))
If nbChildren < 0 Then
Throw New Exception
End If
Catch
error = True
End Try
' salary
Try
salary = Integer.Parse(args(2))
If salary < 0 Then
Throw New Exception
End If
Catch
error = True
End Try
End If
' if the parameters are correct, calculate the tax
If Not error Then
Console.Out.WriteLine(("tax=" & objTax.calculate(married = "o", numChildren, salary) & " euro(s)"))
Else
Console.Error.WriteLine(syntax)
End If
End While
End Sub
End Module
End Namespace
The application prompts the user to enter the three pieces of information needed to calculate their tax:
- marital status: o for married, n for unmarried
- number of children
- their annual salary
The tax calculation is performed using an object of type [tax] created when the application launches:
' creation of a tax object
Dim objTax As Tax = Nothing
Try
objTax = New Tax(New TaxArray)
Catch ex As Exception
Console.Error.WriteLine(("The following error occurred: " + ex.Message))
Environment.Exit(1)
End Try
As the data source, we use an object of type [impotsArray]. The constructor without arguments for this class is used, providing the three arrays (limites, coeffr, coeffn) with hard-coded values. Creating an [impot] object can theoretically throw an exception because, to create itself, the object will request the data (limites, coeffr, coeffn) from its data source, which was passed to it as a parameter, and this data retrieval may trigger an exception. However, in this case, the method used to obtain the data (hard-coded values) cannot cause an exception. However, we have left the exception handling in place to draw the reader’s attention to the possibility that the [impot] object might be constructed incorrectly.
Here is an example of the previous program in action:
dos>dir
04/05/2004 1:28 PM 1,337 impots.vb
April 21, 2004 8:23 AM 1,311 impotsArray.vb
04/21/2004 08:26 1,634 impotsData.vb
04/21/2004 08:42 2,490 testimpots1.vb
We compile all the classes [impot, impotsData, impotsArray] into a single assembly [impot.dll]:
dos>vbc /t:library /out:impot.dll impotsData.vb impotsArray.vb impots.vb
Microsoft (R) Visual Basic .NET Compiler version 7.10.3052.4
dos>dir
04/05/2004 1:28 PM 1,337 impots.vb
04/21/2004 08:23 1,311 impotsArray.vb
04/21/2004 08:26 1,634 impotsData.vb
04/21/2004 08:42 2,490 testimpots1.vb
04/21/2004 09:21 5,632 impots.dll
We compile the test program:
dos>dir
04/05/2004 1:28 PM 1,337 impots.vb
04/21/2004 08:23 1,311 impotsArray.vb
04/21/2004 08:26 1,634 impotsData.vb
04/21/2004 08:42 2,490 testimpots1.vb
04/21/2004 09:21 5,632 impots.dll
04/21/2004 09:23 4,608 testimpots1.exe
We can run the tests:
dos>testimpots1
Tax calculation parameters in married format: number of children, salary, or nothing to stop :o 2 60000
tax=4,300 euro(s)
Tax calculation parameters for married couple format: number of children, salary or nothing to stop: n 2 60000
tax=6,872 euro(s)
Tax calculation parameters for married couples with n children, salary or nothing to stop at:
6.1.6. Web application views
The application will have two views: [form.aspx] and [errors.aspx]. Let’s illustrate how the application works using screenshots. The [form.aspx] view is displayed when the URL [main.aspx] is requested for the first time:

The user fills out the form:

and uses the [Calculate] button to get the following result:

They may enter incorrect data:

Clicking the [Calculate] button then returns a different response [errors.aspx]:

They can use the [Back to Form] link above to return to the [form.aspx] view as it was before the error:

6.1.7. The [form.aspx] view
The [form.aspx] page will be as follows:
<%@ page src="formulaire.aspx.vb" inherits="formulaire" AutoEventWireup="false"%>
<html>
<head>
<title>Tax</title>
</head>
<body>
<P>Calculate Your Tax</P>
<HR>
<form method="post" action="main.aspx?action=calcul">
<TABLE border="0">
<TR>
<TD>Are you married?</TD>
<TD>
<INPUT type="radio" value="yes" name="rdMarie" <%=rdouichecked%>>Yes
<INPUT type="radio" value="no" name="rdMarie" <%=rdnonchecked%>>No
</TD>
</TR>
<TR>
<TD>Number of children</TD>
<TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="<%=txtEnfants%>"></TD>
</TR>
<TR>
<TD>Annual salary (EUR)</TD>
<TD><INPUT type="text" maxLength="12" size="12" name="txtSalary" value="<%=txtSalary%>"></TD>
</TR>
<TR>
<TD>Tax due:
</TD>
<TD><%=txtTax%></TD>
</TR>
</TABLE>
<hr>
<P>
<INPUT type="submit" value="Calculate">
</P>
</form>
<form method="post" action="main.aspx?action=delete">
<INPUT type="submit" value="Clear">
</form>
</body>
</html>
The dynamic fields on this page are as follows:
"checked" if the [yes] checkbox should be checked, "" otherwise | |
same for the [no] checkbox | |
value to be placed in the [txtChildren] input field | |
value to be entered in the [txtSalary] input field | |
Value to enter in the [txtTax] input field |
The page has two forms, each with a [submit] button. The [Calculate] button is the [submit] button for the following form:
<form method="post" action="main.aspx?action=calcul">
...
<P>
<INPUT type="submit" value="Calculate">
</P>
</form>
We can see that the form parameters will be posted to the controller with [action=calcul]. The [Clear] button is the [submit] button for the following form:
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Clear">
</form>
We can see that the form parameters will be posted to the controller with [action=effacer]. Here, the form has no parameters. Only the action matters.
The fields in [formulaire.aspx] are calculated by [formulaire.aspx.vb]:
Imports System.Collections.Specialized
Public Class form
Inherits System.Web.UI.Page
' page fields
Protected rdouichecked As String
Protected rdnonchecked As String
Protected txtChildren As String
Protected txtSalary As String
Protected txtTax As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' retrieve the previous request from the context
Dim form As NameValueCollection = Context.Items("form")
' prepare the page to be displayed
' radio buttons
rdouichecked = ""
rdnonchecked = "checked"
If form("rdMarie").ToString = "yes" Then
rdouichecked = "checked"
rdnonchecked = ""
End If
' the rest
txtChildren = CType(form("txtChildren"), String)
txtSalary = CType(form("txtSalary"), String)
txtTax = CType(Context.Items("txtTax"), String)
End Sub
End Class
The fields in [main.aspx] are calculated based on two pieces of information placed by the controller in the page context:
- Context.Items("form"): a NameValueCollection dictionary containing the values of the HTML fields [rdmarie,txtEnfants,txtSalaire]
- Context.Items("txtImpot"): tax value
6.1.8. The [erreurs.aspx] view
The [erreurs.aspx] view displays any errors that may occur during the application's runtime. Its presentation code is as follows:
<%@ page src="erreurs.aspx.vb" inherits="erreurs" AutoEventWireup="false"%>
<HTML>
<HEAD>
<title>Tax</title>
</HEAD>
<body>
<P>The following errors occurred:</P>
<HR>
<%=HTMLErrors%>
<%=link%>
</body>
</HTML>
The page has three dynamic fields:
HTML code for a list of errors | |
URL of a link | |
link text |
These fields are calculated by the page's controller in [errors.aspx.vb]:
Imports System.Collections
Imports Microsoft.VisualBasic
Public Class errors
Inherits System.Web.UI.Page
' page parameter
Protected HTMLErrors As String = ""
Protected href As String
Protected link As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' retrieve the context items
Dim errors As ArrayList = CType(context.Items("errors"), ArrayList)
href = context.Items("href").ToString
link = context.Items("link").ToString
' Generate the HTML code for the list
Dim i As Integer
For i = 0 To errors.Count - 1
HTMLErrors += " " + Errors(i).ToString + "" + ControlChars.CrLf
Next
End Sub
End Class
The page controller retrieves information placed by the application controller in the page context:
ArrayList object containing the list of error messages to display | |
URL of a link | |
link text |
Now that we know what the application user sees, we can move on to writing the application's controller.
6.1.9. The controllers [global.asax, main.aspx]
Let’s review the MVC architecture of our application:

ClientApplication logic
The controller [main.aspx] must handle three actions:
- init: corresponds to the client's first request. The controller displays the [formulaire.aspx] view
- calcul: corresponds to the tax calculation request. If the data in the input form is correct, the tax is calculated using the business class [taxes]. The controller returns the [form.aspx] view to the client as it was validated, along with the calculated tax. If the data in the input form is incorrect, the controller returns the [errors.aspx] view with a list of errors and a link to return to the form.
- return: corresponds to returning to the form after an error. The controller displays the [form.aspx] view as it was validated before the error.
It is also known that every request to the application passes through the [global.asax] controller, if it exists. We therefore have, at the application’s entry point, a chain of two controllers:
- [global.asax], which, due to the ASP.NET architecture, receives all requests to the application
- [main.aspx], which, by the developer’s choice, also receives all requests to the application
The need for [main.aspx] stems from the fact that we will have a session to manage. We have seen that [global.asax] is not suitable as a controller in this case. We could do without [global.asax] entirely here. However, we will use it to execute code when the application starts. The MVC diagram above shows that we will need to create a [tax] object to calculate the tax. There is no need to create this object multiple times; once is sufficient. We will therefore create it at application startup during the [Application_Start] event handled by the [global.asax] controller. The code for this is as follows:
[global.asax]
[global.asax.vb]
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' Create an 'impot' object
Dim objTaxAs Tax
Try
objImpot = New impot(New impotsArray)
' Add the object to the application
Application("objImpot") = objImpot
' no error
Application("error") = False
Catch ex As Exception
'An error occurred; we note it in the application
Application("error") = True
End Try
End Sub
End Class
Once created, the [impot] object is stored in the application. This is where requests from different clients will retrieve it. Since the creation of the [impot] object may fail, we handle any exceptions and set an [error] key in the application to indicate whether or not an error occurred during the creation of the [impot] object.
The controller code [main.aspx, main.aspx.vb] will be as follows:
[main.aspx]
[main.aspx.vb]
Imports System
Imports System.Collections.Specialized
Imports System.Collections
Imports st.istia.univangers.fr
Public Class main
Inherits System.Web.UI.Page
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' First, we check if the application was able to initialize correctly
If CType(Application("error"), Boolean) Then
' we redirect to the error page
Dim errors As New ArrayList
errors.Add("Application temporarily unavailable...")
context.Items("errors") = errors
context.Items("link") = ""
context.Items("href") = ""
Server.Transfer("errors.aspx")
End If
' retrieve the action to be performed
Dim action As String
If Request.QueryString("action") Is Nothing Then
action = "init"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' execute the action
Select Case action
Case "init"
' initialize application
initAppli()
Case "calculation"
' calculate tax
calculateTax()
Case "return"
' return to form
returnToForm()
Case "clear"
' initialize application
initApp()
Case Else
' unknown action = init
initAppli()
End Select
End Sub
Private Sub initAppli()
' Display the pre-filled form
Context.Items("form") = initForm()
Context.Items("txtImpot") = ""
Server.Transfer("form.aspx", True)
End Sub
Private Function initForm() As NameValueCollection
' Initialize the form
Dim form As New NameValueCollection
form.Set("rdMarie", "no")
form.Set("txtChildren", "")
form.Set("txtSalary", "")
Return form
End Function
Private Sub calculateTax()
' Check the validity of the entered data
Dim errors As ArrayList = checkData()
' if there are errors, report them
If errors.Count <> 0 Then
' Save the entries
Session.Item("form") = Request.Form
' prepare the error page
context.Items("href") = "main.aspx?action=return"
context.Items("link") = "Back to form"
context.Items("errors") = errors
Server.Transfer("errors.aspx")
End If
' No errors here - calculating the tax
Dim tax As Long = CType(Application("objTax"), tax).calculate( _
Request.Form("rdMarie") = "yes", _
CType(Request.Form("txtChildren"), Integer), _
CType(Request.Form("txtSalary"), Long))
' display the results page
context.Items("txtImpot") = impot.ToString + " euro(s)"
context.Items("form") = Request.Form
Server.Transfer("formulaire.aspx", True)
End Sub
Private Sub returnForm()
' the form is displayed with values retrieved from the session
Context.Items("form") = Session.Item("form")
Context.Items("txtImpot") = ""
Server.Transfer("form.aspx", True)
End Sub
Private Function checkData() As ArrayList
' initially no errors
Dim errors As New ArrayList
Dim error As Boolean = False
' Married radio button
Try
Dim rdMarried As String = Request.Form("rdMarried").ToString
If rdMarried <> "yes" And rdMarried <> "no" Then
Throw New Exception
End If
Catch
errors.Add("You did not specify your marital status")
End Try
' number of children
Try
Dim txtChildren As String = Request.Form("txtChildren").ToString
Dim nbChildren As Integer = CType(txtChildren, Integer)
If nbChildren < 0 Then Throw New Exception
Catch
errors.Add("The number of children is incorrect")
End Try
' salary
Try
Dim txtSalary As String = Request.Form("txtSalary").ToString
Dim salary As Integer = CType(txtSalary, Long)
If salary < 0 Then Throw New Exception
Catch
errors.Add("The annual salary is incorrect")
End Try
' return the list of errors
Return errors
End Function
End Class
The controller starts by checking that the application has initialized correctly:
' First, we check if the application was able to initialize correctly
If CType(Application("error"), Boolean) Then
' we redirect to the error page
Dim errors As New ArrayList
errors.Add("Application temporarily unavailable...")
context.Items("errors") = errors
context.Items("link") = ""
context.Items("href") = ""
Server.Transfer("errors.aspx")
End If
If the controller detects that the application failed to initialize correctly (the [impot] object required for the calculation could not be created), it displays the error page with the appropriate parameters. Here, there is no need to place the return link on the form since the entire application is unavailable. A general error message is placed in [Context.Items("errors")] of type [ArrayList].
If the controller determines that the application is operational, it then analyzes the action it is asked to execute via the [action] parameter. We have encountered this mode of operation many times by now. The processing of each type of action is delegated to a function.
6.1.9.1. The init and delete actions
These two actions must display the empty input form. Recall that this form (see views) has two parameters:
- Context.Items("form"): a [NameValueCollection] dictionary containing the values of the HTML fields [rdmarie,txtEnfants,txtSalaire]
- Context.Items("txtImpot"): tax value
The [initAppli] function initializes these two parameters to display an empty form.
6.1.9.2. The calculation action
This action must calculate the tax due based on the data entered in the form and return the form pre-filled with the entered values and the calculated tax amount. The [calculImpot] function responsible for this task begins by verifying that the form data is correct:
- the [rdMarie] field must be present and have the value [yes] or [no]
- the [txtEnfants] field must be present and be an integer >=0
- the [txtSalaire] field must be present and be an integer >=0
If the entered data is invalid, the controller displays the [erreurs.aspx] view after first setting the expected values for it in the context:
- Error messages are placed in an [ArrayList] object, which is then added to the context [Context.Items("errors")]
- The URL of the return link and the text of that link are also placed in the context.
Before handing control over to the [erreurs.aspx] page, which will send the response to the client, the values entered in the form (Request.Form) are placed in the session, associated with the "form" key. This will allow a subsequent request to retrieve them.
One might wonder here whether it is useful to verify that the fields [rdMarie, txtEnfants, txtSalaire] are present in the request sent by the client. This is unnecessary if we are certain that our client is a browser that has received the view [formulaire.aspx] containing these fields. We can never be certain of this. We will show an example a little later where the client is the [curl] application we have already encountered. We will query the application without sending the fields it expects and see how it reacts. This is a rule that has been stated several times and which we reiterate here: an application must never make assumptions about the type of client querying it. To be safe, it must assume that it could be queried by a programmed application that might send it unexpected parameter strings. It must behave correctly in all cases.
In our case, we verified that the fields [rdMarie, txtEnfants, txtSalaire] were present in the request but did not check whether it could contain others. In this application, they would be ignored. Nevertheless, again as a security measure, it would be useful to log this type of request in a log file and send an alert to the application administrator so that they are aware the application is receiving "strange" requests. By analyzing these in the log file, they could detect a potential attack on the application and then take the necessary measures to protect it.
If the expected data is correct, the controller initiates the tax calculation using the [tax] object stored in the application. It then stores the two pieces of information expected by the [form.aspx] view in the context:
- Context.Items("formulaire"): a [NameValueCollection] dictionary containing the values of the HTML fields [rdmarie,txtEnfants,txtSalaire], here [Request.Form)], i.e., the values previously entered in the form
- Context.Items("txtImpot"): the tax value that has just been calculated
The attentive reader may have wondered while reading the above: since the [impot] object created at application startup is shared among all requests, could there be access conflicts leading to corruption of the [impot] object’s data? To answer this question, we need to return to the code of the [impot] class. The requests call the [impot].calculateTax method to obtain the tax due. So this is the code we need to examine:
Public Function calculate(ByVal married As Boolean, ByVal numChildren As Integer, ByVal salary As Long) As Long
' Calculate the number of shares
Dim nbParts As Decimal
If married Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbChildren) / 2 + 1
End If
If nbChildren >= 3 Then
nbParts += 0.5D
End If
' Calculate taxable income & Family Quotient
Dim income As Decimal = 0.72D * salary
Dim QF As Decimal = income / nbParts
' calculate tax
limits((limits.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limits(i)
i += 1
End While
Dim amount As Long = CLng(income * coeffR(i) - nbParts * coeffN(i))
Return impot
End Function
Suppose a thread is executing the previous method and is interrupted. Another thread then executes the method. What are the risks? To find out, we added the following code:
Dim impot As Long = CLng(revenue * coeffR(i) - nbParts * coeffN(i))
' wait 10 seconds
Thread.Sleep(10000)
Return impot
Thread 1, after calculating the value [impot1] of the local variable [impot], is interrupted. Thread 2 then executes and calculates a new value [impot2] for the same variable [impot] before being interrupted. Thread 1 regains control. What does it find in the local variable [impot]? Since this variable is local to a method, it is stored in a memory structure called the stack. This stack is part of the thread’s context, which is saved when the thread is suspended. When thread 2 starts up, its context is set up with a new stack and therefore a new local variable [impot]. When thread 2 is suspended in turn, its context will also be saved. When thread 1 is restarted, its context is restored, including its stack. It then retrieves its local variable [impot] and not that of thread 2. We are therefore in a situation where there are no access conflicts between requests. The tests conducted with the 10-second pause described above confirmed that simultaneous requests did indeed yield the expected result.
6.1.9.3. The return action
This action corresponds to clicking the [Back to Form] link on the [errors.aspx] view to return to the [form.aspx] view, which is pre-filled with the values previously entered and saved in the session. The [returnForm] function retrieves this information. The two parameters expected by the [form.aspx] view are initialized:
- Context.Items("form") with the values previously entered and saved in the session
- Context.Items("txtImpot") with the empty string
6.1.10. Testing the web application
All of the above files are placed in a folder named <application-path>.

In this folder, a [bin] subfolder is created, in which the [impot.dll] assembly—generated from the compilation of the business class files: [impots.vb, impotsData.vb, impotsArray.vb]—is placed. The required compilation command is shown below:
dos>vbc /t:library /out:impot.dll impotsData.vb impotsArray.vb impots.vb
Microsoft (R) Visual Basic .NET Compiler version 7.10.3052.4
dos>dir
04/05/2004 13:28 1,337 impots.vb
04/21/2004 08:23 1,311 impotsArray.vb
04/21/2004 08:26 1,634 impotsData.vb
04/21/2004 09:21 5,632 impots.dll
The [impot.dll] file above must be placed in <application-path>\bin so that the web application can access it. The Cassini server is launched with the parameters (<application-path>,/impots1). Using a browser, we request the URL [http://localhost/impots1/main.aspx]:

We fill out the form:

Then we start the tax calculation using the [Calculate] button. We get the following response:

Then we enter incorrect data:

Clicking the [Calculate] button yields the following response:

Clicking the [Back to Form] link takes us back to the form as it was when it was submitted:

Finally, clicking the [Clear] button resets the page:

6.1.11. Using the [curl] client
It is important to test web applications with clients other than browsers. If you send a browser a form with parameters to be posted when it is submitted, the browser will send the values of those parameters back to the server. Another client might not do this, and then the server would receive a request with missing parameters. It must know what to do in this case. Another example is client-side data validation. If the form contains data to be validated, this validation can be performed on the client side using scripts included in the document containing the form. The browser will only submit the form if all client-side validated data is valid. One might then be tempted, on the server side, to assume that we will receive validated data and not want to perform this validation a second time. That would be a mistake. Indeed, a client other than a browser could send invalid data to the server, and the web application might then behave unexpectedly. We will illustrate these points using the [curl] client.
First, we request the URL [http://localhost/impots1/main.aspx]:
dos>curl --include --url http://localhost/impots1/main.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:18:10 GMT
Set-Cookie: ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255; path=/
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 982
Connection: Close
<html>
<head>
<title>Tax</title>
</head>
<body>
<P>Calculate Your Tax</P>
<HR width="100%" SIZE="1">
<form method="post" action="main.aspx?action=calcul">
<TABLE border="0">
<TR>
<TD>Are you married?</TD>
<TD>
<INPUT type="radio" value="yes" name="rdMarie" >Yes <INPUT type="radio" value="no" name="rdMarie" checked>No</TD>
</TR>
<TR>
<TD>Number of children</TD>
<TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value=""></TD>
</TR>
<TR>
<TD>Annual salary (euros)</TD>
<TD><INPUT type="text" maxLength="12" size="12" name="txtSalary" value=""></TD>
</TR>
<TR>
<TD>Tax due:
</TD>
<TD></TD>
</TR>
</TABLE>
<hr>
<P>
<INPUT type="submit" value="Calculate">
</P>
</form>
<form method="post" action="main.aspx?action=clear">
<INPUT type="submit" value="Delete">
</form>
</body>
</html>
The server sent us the form's HTML code. In the HTTP headers, we have the session cookie. We will use it in subsequent requests to maintain the session. Let's request the [calcul] action without providing any parameters:
dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --url http://localhost/impots1/main.aspx?action=calcul
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:22:42 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 380
Connection: Close
<HTML>
<HEAD>
<title>Tax</title>
</HEAD>
<body>
<P>The following errors occurred:</P>
<HR>
You did not specify your marital status
The number of children is incorrect
The annual salary is incorrect
<a href="main.aspx?action=return">
Back to the form
</body>
</HTML>
We can see that the web application returned the [errors] view with three error messages for the three missing parameters. Now let's send some incorrect parameters:
dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --data rdMarie=xx --data txtEnfants=xx --data txtSalaire=xx --url http://localhost/impots1/main.aspx?action=calcul
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:25:50 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 380
Connection: Close
<HTML>
<HEAD>
<title>Tax</title>
</HEAD>
<body>
<P>The following errors occurred:</P>
<HR>
You did not specify your marital status
The number of children is incorrect
The annual salary is incorrect
<a href="main.aspx?action=return">
Back to the form
</body>
</HTML>
The three errors were correctly detected. Now let's send valid parameters:
dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --data rdMarie=yes --data txtChildren=2 --data txtSalary=60000 --url http://localhost/impots1/main.aspx?action=calcul
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:28:24 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 1000
Connection: Close
<html>
<head>
<title>Tax</title>
</head>
<body>
<P>Calculate Your Tax</P>
<HR width="100%" SIZE="1">
<form method="post" action="main.aspx?action=calcul">
<TABLE border="0">
<TR>
<TD>Are you married?</TD>
<TD>
<INPUT type="radio" value="yes" name="rdMarie" checked>Yes <INPUT type="radio" value="no" name="rdMarie" >No</TD>
</TR>
<TR>
<TD>Number of children</TD>
<TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="2"></TD>
</TR>
<TR>
<TD>Annual salary (euros)</TD>
<TD><INPUT type="text" maxLength="12" size="12" name="txtSalary" value="60000"></TD>
</TR>
<TR>
<TD>Tax due:
</TD>
<TD>4,300 euros</TD>
</TR>
</TABLE>
<hr>
<P>
<INPUT type="submit" value="Calculate">
</P>
</form>
<form method="post" action="main.aspx?action=clear">
<INPUT type="submit" value="Delete">
</form>
</body>
</html>
We have successfully retrieved the tax due: 4,300 euros. The lesson from this example is that we must not be misled by the fact that we are writing a web application intended for clients that are browsers. A web application is a TCP/IP service, and this network protocol does not reveal the nature of a service’s client application. Therefore, we cannot know whether a web application’s client is a browser or not. We therefore follow two rules:
- Upon receiving a request from a client, we make no assumptions about the client and verify that the expected parameters in the request are present and valid
- we construct a response intended for browsers, which generally consists of HTML documents
A web application can be built to serve different clients simultaneously, such as browsers and mobile phones. We can then include a new parameter in each request indicating the client type. Thus, a browser will request a tax calculation via a request to the URL http://machine/impots/main.aspx?client=navigateur&action=calcul, while a mobile phone will send a request to the URL http://machine/impots/main.aspx?client=mobile&action=calcul. The MVC structure facilitates the development of such an application. It takes the following form:

The [Business Classes, Data Access Classes] block remains unchanged. This is because it is a client-agnostic component. The [Controller] block changes only slightly but must account for a new parameter in the request: the [client] parameter, which indicates the type of client being handled. The [Views] block must generate views for each type of client. It might be worthwhile to account for the presence of the [client] parameter in the query right from the application’s design phase, even if the short- or medium-term goal is limited to browsers. If the application later needs to support a new type of client, only views tailored to that client need to be written.
6.2. Example 2
6.2.1. The Problem
Here, we propose to address the same problem as before but by modifying the data source for the [tax] object created by the web application. In the previous version, the data source provided values from arrays hard-coded into the code. This time, the new data source will retrieve them from an ODBC data source associated with a MySQL database.
6.2.2. The ODBC data source
The data will be located in a table named [IMPOTS] in a MySQL database named [dbimpots]. The contents of this table will be as follows:

The database owner is the user [admimpots] with password [mdpimpots]. We associate an ODBC data source with this database. Before doing so, let’s first review the different ways to access a database using the .NET platform.
There are many databases available for Windows platforms. To access them, applications use programs called drivers.

In the diagram above, the driver has two interfaces:
- the I1 interface presented to the application
- the I2 interface to the database
To prevent an application written for database B1 from having to be rewritten if migrating to a different database B2, standardization efforts have been made on interface I1. If databases using "standardized" drivers are employed, database B1 will be provided with driver P1, database B2 with driver P2, and the I1 interface of these two drivers will be identical. Thus, the application will not need to be rewritten. For example, you can migrate an Access database to a MySQL database without changing the application.
There are two types of standardized drivers:
- ODBC (Open DataBase Connectivity) drivers
- OLE DB (Object Linking and Embedding DataBase) drivers
ODBC drivers provide access to databases. The data sources for OLE DB drivers are more varied: databases, email systems, directories, etc. There are no limits. Any data source can be the subject of an OLE DB driver if a vendor decides to do so. The benefit is obviously significant: you have uniform access to a wide variety of data.
The .NET 1.1 platform comes with three types of data access classes:
- SQL Server.NET classes, for accessing Microsoft SQL Server databases
- the Ole Db.NET classes, for accessing databases from DBMSs that offer an OLE DB driver
- ODBC.NET classes, for accessing databases from DBMSs that offer an ODBC driver
The MySQL DBMS has long had an ODBC driver. This is the one we’ll use now. In Windows, we select [Start Menu/Control Panel/Administrative Tools/32-bit ODBC Sources]. Depending on the Windows version, this path may vary slightly. This opens the following application, which will allow us to create our ODBC source:

We will create a System data source, i.e., a data source that any user of the machine can use. Therefore, above, we select the [System Data Source] tab. The page displayed has an [Add] button, which we use to create a new ODBC data source:

The wizard asks you to select the ODBC driver to use. Windows comes with a number of pre-installed ODBC drivers. The MySQL ODBC driver is not included in this set. You must therefore install it first. You can find it online by typing the search terms "MySQL ODBC" or "MyODBC" into a search engine. Here, we have installed the [MySQL ODBC 3.51] driver. We select it and click [Finish]:

You will need to provide the following information:
the name that will identify the ODBC data source. Any Windows application will be able to access the source using this name | |
Any text describing the data source | |
The name of the machine hosting the MySQL DBMS. Here, it is the local machine. It could be a remote machine. This would allow a Windows application to access a remote database without any special coding. This is a major advantage of the ODBC source. | |
A MySQL DBMS can manage multiple databases. Here, we specify which one we want to manage: dbimpots | |
The name of a user registered within the MySQL database management system. Access to the data source will be performed under this user’s name. Here: admimpots | |
This user's password. Here: mdpimpots | |
MySQL DBMS working port. By default, this is port 3306. We have not changed it |
Once this is done, we test the validity of our connection settings using the [Test Data Source] button:

Once this is done, we are confident in our ODBC data source. We can now use it. We click [OK] as many times as necessary to exit the ODBC wizard.
If the reader does not have the MySQL DBMS, they can download it for free at [http://www.mysql.com]. Below, we outline the steps to create an ODBC source with Access. The first steps are identical to those described previously. We add a new system data source:

The selected driver will be [Microsoft Access Driver]. Click [Finish] to proceed to the ODBC source definition:

The information to be provided is as follows:
The name that will identify the ODBC data source. Any Windows application will be able to access the source using this name | |
Any text describing the data source | |
The full name of the Access file to be used |
6.2.3. A new data access class
Let’s revisit the MVC structure of our application:

In the diagram above, the [impotsData] class is responsible for retrieving data. In this case, it will retrieve data from the MySQL database [dbimpots]. As we learned in the previous version of this application, [impotsData] is an abstract class that must be derived whenever we want to adapt it to a new data source. Let’s review the structure of this abstract class:
Imports System.Collections
Namespace st.istia.univangers.fr
Public MustInherit Class impotsData
Protected limites() As Decimal
Protected coeffr() As Decimal
Protected coeffn() As Decimal
Protected checked As Boolean
Protected valid As Boolean
' data access method
Public MustOverride Function getData() As Object()
' data verification method
Protected Function checkData() As Integer
' checks the acquired data
...
End Function
' Checks the validity of an array's contents
Protected Function check(ByRef array() As Decimal, ByVal n As Integer) As Boolean
...
End Function
End Class
End Namespace
The class that derives from [impotsData] must implement two methods:
- a constructor if the default constructor of [impotsData] is not suitable
- the [getData] method, which returns the three arrays (limites, coeffr, coeffn)
We create the [impotsODBC] class, which will retrieve the data (limits,coeffr,coeffn) from an ODBC source whose name we will provide:
Imports System.Data.Odbc
Imports System.Data
Imports System.Collections
Imports System
Namespace st.istia.univangers.fr
Public Class impotsODBC
Inherits impotsData
' instance variables
Protected DSNimpots As String
' constructor
Public Sub New(ByVal DSNimpots As String)
' Note the three pieces of information
Me.DSNimpots = DSNimpots
End Sub
Public Overrides Function getdata() As Object()
' Initialize the three arrays limits, coeffr, and coeffn based on
' the contents of the [impots] table in the DSNimpots ODBC database
' limits, coeffr, and coeffn are the three columns of this table
' may throw various exceptions
Dim connectString As String = "DSN=" + DSNimpots + ";" ' connection string to the database
Dim impotsConn As OdbcConnection = Nothing ' the connection
Dim sqlCommand As OdbcCommand = Nothing ' the SQL command
' the SELECT query
Dim selectCommand As String = "select limits,coeffr,coeffn from taxes"
' arrays to retrieve the data
Dim aLimits As New ArrayList
Dim aCoeffR As New ArrayList
Dim aCoeffN As New ArrayList
Try
' Attempt to access the database
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' create a command object
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' Execute the query
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
' Process the retrieved table
While myReader.Read()
' the data from the current row is placed in the arrays
aLimites.Add(myReader("limites"))
aCoeffR.Add(myReader("coeffr"))
aCoeffN.Add(myReader("coeffn"))
End While
' Release resources
myReader.Close()
taxConn.Close()
Catch e As Exception
Throw New Exception("Database access error (" + e.Message + ")")
End Try
' dynamic arrays are converted to static arrays
Me.limits = New Decimal(aLimits.Count - 1) {}
Me.coeffr = New Decimal(aLimites.Count - 1) {}
Me.coeffn = New Decimal(aLimites.Count - 1) {}
Dim i As Integer
For i = 0 To aLimites.Count - 1
limits(i) = Decimal.Parse(aLimits(i).ToString())
coeffR(i) = Decimal.Parse(aCoeffR(i).ToString())
coeffN(i) = Decimal.Parse(aCoeffN(i).ToString())
Next i
' Check the acquired data
Dim error As Integer = checkData()
' if data is invalid, throw an exception
If Not valid Then Throw New Exception("The tax bracket data is invalid (" + error.ToString + ")")
' otherwise, return the three arrays
Return New Object() {limits, coeffr, coeffn}
End Function
End Class
End Namespace
Let's take a look at the constructor:
' constructor
Public Sub New(ByVal DSNimpots As String)
' note the three pieces of information
Me.TaxDSN = TaxDSN
End Sub
It takes as a parameter the name of the ODBC data source containing the data to be retrieved. The constructor simply stores this name. The [getData] method is responsible for reading the data from the [impots] table and placing it into three arrays (limites, coeffr, coeffn). Let's comment on the code:
- the parameters for connecting to the ODBC data source are defined, but the connection is not open
' connection string to the database
Dim connectString As String = "DSN=" + DSNimpots + ";"
' create a database connection object - this connection is not open
Dim impotsConn As OdbcConnection = New OdbcConnection(connectString)
- Define three [ArrayList] objects to retrieve data from the [impots] table:
' arrays to retrieve the data
Dim aLimits As New ArrayList
Dim aCoeffR As New ArrayList
Dim aCoeffN As New ArrayList
- All database access code is enclosed in a try/catch block to handle any access errors. We open the connection to the database:
' we attempt to access the database
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
- We execute the [select] command on the open connection. We obtain an [OdbcDataReader] object that will allow us to iterate through the rows of the table resulting from the select:
' create a command object
Dim sqlCommand As OdbcCommand = New OdbcCommand(selectCommand, impotsConn)
' Execute the query
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
- We iterate through the result table, row by row. To do this, we use the [Read] method of the [OdbcDataReader] object obtained previously. This method does two things:
- It advances one row in the table. Initially, the cursor is positioned before the first row
- it returns the Boolean value [true] if it was able to advance, [false] otherwise, the latter case indicating that all rows have been processed.
The columns of the current row of the [OdbcDataReader] object are obtained via OdbcDataReader. This returns an object representing the value of the column. We iterate through the entire table to populate its contents into the three [ArrayList] objects:
' Processing the retrieved table
While myReader.Read()
' the data from the current row is placed in the arrays
aLimites.Add(myReader("limites"))
aCoeffR.Add(myReader("coeffr"))
aCoeffN.Add(myReader("coeffn"))
- Once this is done, we release the resources associated with the connection:
- The contents of the three [ArrayList] objects are transferred to three standard arrays:
' dynamic arrays are converted to static arrays
limits = New Decimal(aLimits.Count - 1) {}
coeffr = New Decimal(aLimites.Count - 1) {}
coeffn = New Decimal(aLimits.Count - 1) {}
Dim i As Integer
For i = 0 To aLimits.Count - 1
limits(i) = CType(aLimits(i), Decimal)
coeffR(i) = CType(aCoeffR(i), Decimal)
coeffN(i) = CType(aCoeffN(i), Decimal)
Next i
- Once the data from the [impots] table has been loaded into the three arrays, all that remains is to verify their contents using the [checkData] method of the base class [impotsData]:
' we check the acquired data
Dim error As Integer = checkData()
' if data is invalid, then throw an exception
If Not valid Then Throw New Exception("The tax bracket data is invalid (" + error.ToString + ")")
' otherwise, return the three arrays
Return New Object() {limits, coeffr, coeffn}
6.2.4. Tests for the data access class
A test program could look like this:
Option Explicit On
Option Strict On
' namespaces
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
' test page
Module testimports
Sub Main(ByVal arguments() As String)
' interactive tax calculation program
' The user enters three pieces of information via the keyboard: marital status, number of children, salary
' the program then displays the tax due
Const syntax1 As String = "pg DSNimpots"
Const syntax2 As String = "syntax: married noChildren salary" + ControlChars.Lf + "married: o for married, n for unmarried" + ControlChars.Lf + "noChildren: number of children" + ControlChars.Lf + "salary: annual salary in F"
' Check program parameters
If arguments.Length <> 1 Then
' error message
Console.Error.WriteLine(syntax1)
' end
Environment.Exit(1)
End If
' retrieve the arguments
Dim DSNimpots As String = arguments(0)
' create a tax object
Dim taxObj As Tax = Nothing
Try
objTax = New Tax(New TaxODBC(TaxDSN))
Catch ex As Exception
Console.Error.WriteLine(("The following error occurred: " + ex.Message))
Environment.Exit(2)
End Try
' infinite loop
While True
' initially no errors
Dim error As Boolean = False
' Requesting tax calculation parameters
Console.Out.Write("Tax calculation parameters in the format: married, number of children, salary, or 'nothing' to exit:")
Dim parameters As String = Console.In.ReadLine().Trim()
' anything to do?
If parameters Is Nothing Or parameters = "" Then
Exit While
End If
' Check the number of arguments in the entered line
Dim args As String() = parameters.Split(Nothing)
Dim nbParameters As Integer = args.Length
If nbParameters <> 3 Then
Console.Error.WriteLine(syntax2)
error = True
End If
Dim groom As String
Dim nbChildren As Integer
Dim salary As Integer
If Not error Then
' Checking the validity of the
' married
married = args(0).ToLower()
If married <> "o" And married <> "n" Then
Console.Error.WriteLine((syntax2 + ControlChars.Lf + "Invalid 'married' argument: enter 'y' or 'n'")
error = True
End If
' nbChildren
nbChildren = 0
Try
nbChildren = Integer.Parse(args(1))
If nbChildren < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntax2 + "\nInvalid numberOfChildren argument: enter a positive integer or zero")
error = True
End Try
' salary
salary = 0
Try
salary = Integer.Parse(args(2))
If salary < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntax2 + "\nInvalid salary argument: enter a positive integer or zero")
error = True
End Try
End If
If Not error Then
' parameters are correct - calculate the tax
Console.Out.WriteLine(("tax=" & objTax.calculate(married = "o", numChildren, salary).ToString + " euro(s)"))
End If
End While
End Sub
End Module
End Namespace
The application is launched with a parameter:
- DSNimpots: name of the ODBC data source to be used
The tax calculation is performed using an object of type [tax] created when the application is launched:
' creation of a tax object
Dim objTax As Tax = Nothing
Try
objTax = New Tax(New TaxODBC(DSNTaxes))
Catch ex As Exception
Console.Error.WriteLine(("The following error occurred: " + ex.Message))
Environment.Exit(1)
End Try
Once initialized, the application repeatedly prompts the user to enter the three pieces of information needed to calculate their tax:
- marital status: o for married, n for unmarried
- the number of children
- annual salary
All classes are compiled:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsODBC.vb
dos>dir
04/01/2004 7:34 PM 7,168 impots.dll
04/01/2004 7:31 PM 1,360 impots.vb
04/21/2004 08:23 1,311 impotsArray.vb
04/21/2004 08:26 1,634 impotsData.vb
04/01/2004 7:34 PM 2,735 impotsODBC.vb
04/01/2004 7:32 PM 3,210 testimpots.vb
The test program is then compiled:
dir>dir
04/01/2004 7:34 PM 7,168 impots.dll
04/01/2004 7:31 PM 1,360 impots.vb
04/21/2004 08:23 1,311 impotsArray.vb
04/21/2004 08:26 1,634 impotsData.vb
04/01/2004 7:34 PM 2,735 impotsODBC.vb
04/01/2004 7:34 PM 6,144 testimpots.exe
04/01/2004 7:32 PM 3,210 testimpots.vb
The test program is first run with the MySQL ODBC data source:
dos>testimpots odbc-mysql-dbimpots
Tax calculation parameters in married format: number of children, salary, or nothing to stop :o 2 60000
tax=4,300 euro(s)
We switch the ODBC source to an Access source:
dos>testimpots odbc-access-dbimpots
Tax calculation parameters in married format: number of children, salary or nothing to stop :o 2 60000
tax=4,300 F
6.2.5. Web application views
These are the same as in the previous application: formulaire.aspx and erreurs.aspx
6.2.6. Application controllers [global.asax, main.aspx]
Only the [global.asax] controller needs to be modified. It is responsible for creating the [impot] object when the application starts. The constructor of this object has a single parameter: the [impotsData] object, which is responsible for retrieving the data. This parameter therefore changes for each new type of data source. The [global.asax.vb] controller becomes the following:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' Create an 'impot' object
Dim objImpot As impot
Try
objImpot = New impot(New impotsODBC(ConfigurationSettings.AppSettings("DSNimpots")))
' Add the object to the application
Application("objImpot") = objImpot
' no error
Application("error") = False
Catch ex As Exception
'An error occurred; we note it in the application
Application("error") = True
Application("message") = ex.Message
End Try
End Sub
End Class
The data source for the [impot] object is now an [impotODBC] object. This object takes the DSN name of the ODBC data source to be used as a parameter. Rather than hard-coding this name in the code, we place it in the application's [web.config] configuration file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="DSNimpots" value="odbc-mysql-dbimpots" />
</appSettings>
</configuration>
We know that the value of a key C in the <appSettings> section of the [web.config] file is retrieved in the application code using [ConfigurationSettings.AppSettings(C)].
To determine the cause of the exception, we log the exception message in the application so that it remains available for queries. The [main.aspx.vb] control will include this message in its error list:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' First, we check if the application was able to initialize correctly
If CType(Application("error"), Boolean) Then
' we redirect to the error page
Dim errors As New ArrayList
errors.Add("Application temporarily unavailable...(" + Application("message").ToString + ")")
context.Items("errors") = errors
context.Items("link") = ""
context.Items("href") = ""
Server.Transfer("errors.aspx")
End If
' retrieve the action to be performed
...
6.2.7. Summary of changes
The application is ready to be tested. Let's list the changes made to the previous version:
- a new data access class has been created
- the controller [global.asax.vb] has been modified in two places: construction of the [impot] object and logging of the message related to any exception in the application
- The [main.aspx.vb] controller has been modified in one place to display the previous exception message
- A [web.config] file has been added
The modification work was done primarily in 1, i.e., outside the web application. This was made possible by the application’s MVC architecture, which separates the controller from the business classes. That is the whole point of this architecture. It could be shown that with an appropriate [web.config] file, any modification to the application controller could have been avoided. It is possible to specify in [web.config] the name of the data access class to be dynamically instantiated—such as —as well as the various parameters required for this instantiation. With this information, [global.asax] can instantiate the data access object. Changing the data source then amounts to:
- creating the data access class for that source if it does not yet exist
- modifying the [web.config] file to allow the dynamic creation of an instance of this class in [global.asax]
6.2.8. Testing the web application
All of the above files are placed in a folder named <application-path>.

In this folder, a [bin] subfolder is created, in which the [impot.dll] assembly—generated from the compilation of the business class files: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb]—is placed. The required compilation command is shown below:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsODBC.vb
dos>dir
04/01/2004 19:34 7,168 impots.dll
04/01/2004 7:31 PM 1,360 impots.vb
04/21/2004 08:23 1,311 impotsArray.vb
04/21/2004 08:26 1,634 impotsData.vb
04/01/2004 7:34 PM 2,735 impotsODBC.vb
04/01/2004 7:32 PM 3,210 testimpots.vb
The [impot.dll] file above must be placed in <application-path>\bin so that the web application can access it. The Cassini server is launched with the parameters (<application-path>,/impots2). The tests yield the same results as in the previous version, as the presence of the database is transparent to the user. To demonstrate this presence, however, we ensure that the ODBC source is unavailable by stopping the MySQL DBMS and request the URL [http://localhost/impots2/main.aspx]. We receive the following response:

6.3. Example 3
6.3.1. The Problem
Here, we propose to address the same problem by again modifying the data source of the [impot] object created by the web application. This time, the new data source will be an ACCESS database accessed via an OLEDB driver. Our goal is to demonstrate another way to access a database.
6.3.2. The OLEDB Data Source
The data will be located in a table named [IMPOTS] in an ACCESS database. The contents of this table will be as follows:

6.3.3. The Data Access Class
Let’s revisit the MVC structure of our application:

- In the diagram above, the [impotsData] class is responsible for retrieving the data. This time, it will need to do so from an OLEDB source.
We create the [impotsOLEDB] class, which will retrieve the data (limits, coeffr, coeffn) from an ODBC source that we will name:
Imports System.Data
Imports System.Collections
Imports System
Imports System.Xml
Imports System.Data.OleDb
Namespace st.istia.univangers.fr
Public Class impotsOLEDB
Inherits impotsData
' instance variables
Protected connectionString As String
' constructor
Public Sub New(ByVal connectionString As String)
' store the three pieces of information
Me.connectionString = connectionString
End Sub
Public Overrides Function getData() As Object()
' initializes the three arrays limits, coeffr, and coeffn based on
' the contents of the [impots] table in the OLEDB database [connectionString]
' limits, coeffr, and coeffn are the three columns of this table
' may throw various exceptions
' we create a DataAdapter object to read data from the OLEDB source
Dim adapter As New OleDbDataAdapter("select limits,coeffr,coeffn from taxes", connectionString)
' we create an in-memory representation of the result of the SELECT statement
Dim content As New DataTable("taxes")
Try
adapter.Fill(content)
Catch e As Exception
Throw New Exception("Database access error (" + e.Message + ")")
End Try
' Retrieve the contents of the tax table
Dim taxRows As DataRowCollection = content.Rows
' resize the destination arrays
Me.limits = New Decimal(taxRows.Count - 1) {}
Me.coeffr = New Decimal(taxRows.Count - 1) {}
Me.coeffn = New Decimal(taxRows.Count - 1) {}
' transfer the contents of the tax table to the arrays
Dim i As Integer
Dim row As DataRow
Try
For i = 0 To TaxLines.Count - 1
' row i of the table
row = taxRows.Item(i)
' retrieve the contents of the row
limits(i) = CType(row.Item(0), Decimal)
coeffr(i) = CType(row.Item(1), Decimal)
coeffn(i) = CType(row.Item(2), Decimal)
Next
Catch
Throw New Exception("The tax bracket data is not of the correct type")
End Try
' Check the acquired data
Dim error As Integer = checkData()
' if data is invalid, then throw an exception
If Not valid Then Throw New Exception("The tax bracket data is invalid (" + error.ToString + ")")
' otherwise, return the three arrays
Return New Object() {limits, coeffr, coeffn}
End Function
End Class
End Namespace
Let's take a look at the constructor:
' constructor
Public Sub New(ByVal connectionString As String)
' Note the following three pieces of information
Me.connectionString = connectionString
End Sub
It receives as a parameter the connection string for the OLEDB source containing the data to be retrieved. The constructor simply stores it. A connection string contains all the parameters required by the OLEDB driver to connect to the OLEDB source. It is generally quite complex. To find the connection string for ACCESS databases, you can use the [WebMatrix] tool. Launch this tool. It provides a window for connecting to a data source:
![]() ![]() | ![]() ![]() |
Using the icon indicated by the arrow above, you can create a connection to two types of Microsoft databases: SQL Server and ACCESS. Let’s choose ACCESS:

We used the [...] button to select the ACCESS database. We confirm the wizard. In the [Data] tab, icons represent the connection:

Now, let’s create a new .aspx file via [Files/New File]:

We get a blank page on which we can design our web interface:

Let’s drag the [impots] table from the [Data] tab onto the sheet above. We get the following result:

Right-click on the [AccessDataSourceControl] object below to access its properties:

The OLEDB connection string to the ACCESS database is provided by the [ConnectionString] property above:
Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\chap5\impots\3\impots.mdb
We can see that this string consists of a fixed part and a variable part, which is simply the name of the ACCESS file. We will use this fact to generate the connection string to our OLEDB data source.
Let’s now return to our [impotsOLEDB] class. The [getData] method is responsible for reading data from the [impots] table and placing it into three arrays (limites, coeffr, coeffn). Let’s comment on its code:
- We define the [DataAdapter] object, which will allow us to transfer the result of an SQL SELECT query into memory. To do this, we define the [select] query to be executed and associate it with the [DataAdapter] object. The constructor of the [DataAdapter] also requires the connection string it will use to connect to the OLEDB source
' We create a DataAdapter object to read data from the OLEDB source
Dim adapter As New OleDbDataAdapter("select limits,coeffr,coeffn from taxes", connectionString)
- We execute the [select] command using the [Fill] method of the [DataAdapter] object. The result of the [select] is loaded into a [DataTable] object created for this purpose. A [DataTable] object is the in-memory representation of a database table, i.e., a set of rows and columns. We handle an exception that may occur if, for example, the connection string is incorrect.
' We create an in-memory representation of the select result
Dim content As New DataTable("taxes")
Try
adapter.Fill(content)
Catch e As Exception
Throw New Exception("Database access error (" + e.Message + ")")
End Try
- In [content], we have the [taxes] table returned by the [select] statement. A [DataTable] object is a table, i.e., a set of rows. These are accessible via the [rows] property of [DataTable]:
' Retrieve the contents of the impots table
Dim taxRows As DataRowCollection = content.Rows
- Each element of the [taxRows] collection is a [DataRow] object representing a row in the table. This row has columns accessible via the [DataRow] object through its [Item] property. [DataRow].[Item(i)] is column number i of the [DataRow]. By iterating through the collection of rows (the DataRows collection of taxRows) and the collection of columns for each row, we can retrieve the entire table:
' we dimension the receiving arrays
Me.limits = New Decimal(taxLines.Count - 1) {}
Me.coeffr = New Decimal(taxLines.Count - 1) {}
Me.coeffn = New Decimal(taxLines.Count - 1) {}
' Transfer the contents of the 'impots' table to the arrays
Dim i As Integer
Dim row As DataRow
Try
For i = 0 To taxLines.Count - 1
' Row i of the table
row = taxRows.Item(i)
' retrieve the row's contents
limits(i) = CType(row.Item(0), Decimal)
coeffr(i) = CType(row.Item(1), Decimal)
coeffn(i) = CType(row.Item(2), Decimal)
Next
Catch
Throw New Exception("The tax bracket data is not of the correct type")
End Try
- Once the data from the [taxes] table has been loaded into the three arrays, all that remains is to verify their contents using the [checkData] method of the base class [taxData]:
' Check the acquired data
Dim error As Integer = checkData()
' if data is invalid, throw an exception
If Not Valid Then Throw New Exception("The tax bracket data is invalid (" + error.ToString + ")")
' otherwise, return the three arrays
Return New Object() {limits, coeffr, coeffn}
6.3.4. Tests for the data access class
A test program could look like this:
Option Explicit On
Option Strict On
' namespaces
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
' test page
Module testimports
Sub Main(ByVal arguments() As String)
' interactive tax calculation program
' the user enters three pieces of data via the keyboard: married, number of children, salary
' the program then displays the tax due
Const syntax1 As String = "pg bdACCESS"
Const syntax2 As String = "syntax: married nbChildren salary" + ControlChars.Lf + "married: o for married, n for unmarried" + ControlChars.Lf + "nbChildren: number of children" + ControlChars.Lf + "salary: annual salary in F"
' Checking program parameters
If arguments.Length <> 1 Then
' error message
Console.Error.WriteLine(syntax1)
' end
Environment.Exit(1)
End If
' retrieve the arguments
Dim path As String = arguments(0)
' prepare the connection string
Dim connectionString As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + path
' Create a tax object
Dim taxObject As TaxObject = Nothing
Try
objTax = New Tax(New TaxOLEDB(connectionString))
Catch ex As Exception
Console.Error.WriteLine(("The following error occurred: " + ex.Message))
Environment.Exit(2)
End Try
' infinite loop
While True
' initially no errors
Dim error As Boolean = False
' Requesting tax calculation parameters
Console.Out.Write("Tax calculation parameters in the format: married, number of children, salary, or 'nothing' to exit:")
Dim parameters As String = Console.In.ReadLine().Trim()
' anything to do?
If parameters Is Nothing Or parameters = "" Then
Exit While
End If
' Check the number of arguments in the entered line
Dim args As String() = parameters.Split(Nothing)
Dim numParams As Integer = args.Length
If nbParameters ≠ 3 Then
Console.Error.WriteLine(syntax2)
error = True
End If
Dim husband As String
Dim nbChildren As Integer
Dim salary As Integer
If Not error Then
' Checking the validity of the
' married
married = args(0).ToLower()
If married <> "o" And married <> "n" Then
Console.Error.WriteLine((syntax2 + ControlChars.Lf + "Invalid married argument: enter o or n"))
error = True
End If
' nbChildren
nbChildren = 0
Try
nbChildren = Integer.Parse(args(1))
If nbChildren < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntax2 + "\nInvalid numberOfChildren argument: enter a positive integer or zero")
error = True
End Try
' salary
salary = 0
Try
salary = Integer.Parse(args(2))
If salary < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntax2 + "\nInvalid salary argument: enter a positive integer or zero")
error = True
End Try
End If
If Not error Then
' the parameters are correct - calculate the tax
Console.Out.WriteLine(("tax=" & objTax.calculate(married = "o", numChildren, salary).ToString + " euro(s)"))
End If
End While
End Sub
End Module
End Namespace
The application is launched with a parameter:
- bdACCESS: name of the ACCESS file to be used
The tax calculation is performed using an object of type [tax] created when the application is launched:
' retrieve the arguments
Dim path As String = arguments(0)
' prepare the connection string
Dim connectionString As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + path
' create a tax object
Dim taxObject As TaxObject = Nothing
Try
objTax = New Tax(New TaxOLEDB(connectionString))
Catch ex As Exception
Console.Error.WriteLine(("The following error occurred: " + ex.Message))
Environment.Exit(2)
End Try
The connection string to the OLEDB source was constructed using information obtained with [WebMatrix].
Once initialized, the application repeatedly prompts the user to enter the three pieces of information needed to calculate their tax:
- marital status: o for married, n for unmarried
- number of children
- their annual salary
All classes are compiled:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsOLEDB.vb
The [impots.mdb] file is placed in the test application folder, and the application is launched as follows:
dos>testimpots impots.mdb
Tax calculation parameters in married format: number of children, salary, or nothing to stop :o 2 60000
tax=4,300 euro(s)
You can run the application with an incorrect ACCESS file:
dos>testimpots xx
The following error occurred: Database access error (File 'D:\data\serge\devel\aspnet\poly\chap5\impots\3\xx' not found.)
6.3.5. The web application views
These are the same as in the previous application: formulaire.aspx and erreurs.aspx
6.3.6. Application controllers [global.asax, main.aspx]
Only the [global.asax] controller needs to be modified. It is responsible for creating the [impot] object when the application starts. The constructor of this object has a single parameter: the [impotsData] object responsible for retrieving the data. This parameter therefore changes since we are switching data sources. The [global.asax.vb] controller becomes the following:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' Create an 'impot' object
Dim objImpot As impot
Try
objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("connectionString")))
' Add the object to the application
Application("objImpot") = objImpot
' no error
Application("error") = False
Catch ex As Exception
'An error occurred; we log it in the application
Application("error") = True
Application("message") = ex.Message
End Try
End Sub
End Class
The data source for the [impot] object is now an [impotOLEDB] object. This object takes as a parameter the connection string for the OLEDB data source to be used. This string is stored in the application's [web.config] configuration file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="connectionString"
value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\chap5\impots2\impots.mdb" />
</appSettings>
</configuration>
The [main.aspx] controller remains unchanged.
6.3.7. Summary of changes
The application is ready for testing. Let’s list the changes made to the previous version:
- a new data access class has been created
- the [global.asax.vb] controller has been modified in one place: construction of the [impot] object
- A [web.config] file has been added
6.3.8. Testing the web application
All of the above files are placed in a folder named <application-path>.

In this folder, a [bin] subfolder is created, in which the [impot.dll] assembly—generated from compiling the business class files: [impots.vb, impotsData.vb, impotsArray.vb, impotsOLEDB.vb]—is placed. The required compilation command is shown below:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsOLEDB.vb
The [impot.dll] file generated by this command must be placed in <application-path>\bin so that the web application can access it. The Cassini server is launched with the parameters (<application-path>,/impots3). The tests yield the same results as in the previous version.
6.4. Example 4
6.4.1. The Problem
We now propose to transform our application into a tax calculation simulation application. A user will be able to perform successive tax calculations, and these will be presented to them on a new view resembling this:

6.4.2. The application’s MVC structure
The application’s MVC structure becomes as follows:

A new view [simulations.aspx] appears, of which we have just provided a screenshot. The data access class will be the [impotsODBC] class from Example 2.
6.4.3. The web application views
The [erreurs.aspx] view remains unchanged. The [formulaire.aspx] view changes slightly. In fact, the tax amount no longer appears on this view. It is now on the [simulations.aspx] view. Thus, upon startup, the page presented to the user is as follows:

Additionally, the [form] view includes a JavaScript script that validates the entered data before sending it to the server, as shown in the following example:

The presentation code is as follows:
<%@ page src="formulaire.aspx.vb" inherits="formulaire" AutoEventWireup="false"%>
<html>
<head>
<title>Tax</title>
<script language="javascript">
function calculate(){
// Check parameters before sending them to the server
with(document.frmImpots){
//number of children
fields = /^\s*(\d+)\s*$/ .exec(txtChildren.value);
if(champs==null){
// the template is not validated
alert("The number of children was not provided or is incorrect");
txtChildren.focus();
return;
}//if
//salary
fields = /^\s*(\d+)\s*$/ .exec(txtSalary.value);
if(fields==null){
// the pattern does not match
alert("Salary was not provided or is incorrect");
txtSalary.focus();
return;
}//if
// OK—send the form to the server
submit();
}//with
}//calculate
</script>
</head>
<body>
<P>Calculate your tax</P>
<HR width="100%" SIZE="1">
<form name="frmImpots" method="post" action="main.aspx?action=calcul">
<TABLE border="0">
<TR>
<TD>Are you married?</TD>
<TD>
<INPUT type="radio" value="yes" name="rdMarie" <%=rdouichecked%>>Yes
<INPUT type="radio" value="no" name="rdMarie" <%=rdnonchecked%>>No</TD>
</TR>
<TR>
<TD>Number of children</TD>
<TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="<%=txtEnfants%>"></TD>
</TR>
<TR>
<TD>Annual salary (euros)</TD>
<TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value="<%=txtSalaire%>"></TD>
</TR>
</TABLE>
<hr>
<P>
<INPUT type="button" value="Calculate" onclick="calculate()">
</P>
</form>
<form method="post" action="main.aspx?action=clear">
<INPUT type="submit" value="Clear">
</form>
</body>
</html>
The dynamic fields on the page are the same as in previous versions. The dynamic field for the tax amount has been removed. The [Calculate] button is no longer a [submit] button. It is a [button], and when clicked, the JavaScript function [calculate()] is executed:
<INPUT type="button" value="Calculate" onclick="calculate()">
We have given the form the name [frmImpots] so that we can reference it in the [calculer] script:
<form name="frmImpots" method="post" action="main.aspx?action=calcul">
The JavaScript function [calculate] uses regular expressions to validate the fields in the [document.frmImpots.txtEnfants] and [document.frmImpots.txtSalaire] forms. If the entered values are correct, they are sent to the server via [document.frmImpots.submit()].
The presentation page obtains its dynamic fields from the following controller [formulaire.aspx.vb]:
Imports System.Collections.Specialized
Public Class form
Inherits System.Web.UI.Page
' page fields
Protected rdouichecked As String
Protected rdnonchecked As String
Protected txtChildren As String
Protected txtSalary As String
Protected txtTax As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' retrieve the previous request from the context
Dim form As NameValueCollection = Context.Items("form")
' prepare the page to be displayed
' radio buttons
rdouichecked = ""
rdnonchecked = "checked"
If form("rdMarie").ToString = "yes" Then
rdouichecked = "checked"
rdnonchecked = ""
End If
' the rest
txtChildren = CType(form("txtChildren"), String)
txtSalary = CType(form("txtSalary"), String)
End Sub
End Class
The controller [formulaire.aspx.vb] is identical to previous versions except that it no longer needs to retrieve the [txtImpot] field from the context, as this field has been removed from the page.
The [simulations.aspx] view appears as follows:

and corresponds to the following presentation code:
<%@ page src="simulations.aspx.vb" inherits="simulations" autoeventwireup="false" %>
<HTML>
<HEAD>
<title>Simulations</title>
</HEAD>
<body>
<P>Simulation Results</P>
<HR width="100%" SIZE="1">
Married
Children
Annual salary (euros)
Tax due (euros)
<%=simulationsHTML%>
<a href="<%=href%>">
<%=link%>
</body>
</HTML>
This code contains three dynamic fields:
HTML code for a list of simulations in the form of HTML table rows | |
URL of a link | |
link text |
They are generated by the controller [simulations.aspx.vb]:
Imports System.Collections
Imports Microsoft.VisualBasic
Public Class simulations
Inherits System.Web.UI.Page
Protected simulationsHTML As String = ""
Protected href As String
Protected link As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'Retrieve the simulations from the context
Dim simulations As ArrayList = CType(context.Items("simulations"), ArrayList)
' each simulation is an array of 4 string elements
Dim simulation() As String
Dim i, j As Integer
For i = 0 To simulations.Count - 1
simulation = CType(simulations(i), String())
simulationsHTML += ""
For j = 0 To simulation.Length - 1
simulationsHTML += "" + simulation(j) + ""
Next
simulationsHTML += "" + ControlChars.CrLf
Next
' retrieve the other elements of the context
href = context.Items("href").ToString
link = context.Items("link").ToString
End Sub
End Class
The page controller retrieves information placed by the application controller in the page context:
ArrayList object containing the list of simulations to display. Each element is an array of 4 strings representing the simulation's information (married, children, salary, tax). | |
URL of a link | |
Link text |
6.4.4. The controllers [global.asax, main.aspx]
Let’s review the MVC architecture of our application:

The controller [main.aspx] must handle three actions:
- init: corresponds to the client’s first request. The controller displays the view [form.aspx]
- calcul: corresponds to the tax calculation request. If the data in the input form is correct, the tax is calculated using the business class [impotsODBC]. The controller returns the [simulations.aspx] view to the client with the result of the current simulation plus all previous ones. If the data in the input form is incorrect, the controller returns the [erreurs.aspx] view with the list of errors and a link to return to the form.
- return: corresponds to returning to the form after an error. The controller displays the [form.aspx] view as it was validated before the error.
In this new version, only the [calcul] action has changed. Indeed, if the data is valid, it must lead to the [simulations.aspx] view, whereas previously it led to the [form.aspx] view. The [main.aspx.vb] controller becomes the following:
Imports System
...
Public Class main
Inherits System.Web.UI.Page
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' First, we check if the application was able to initialize correctly
...
' we execute the action
Select Case action
Case "init"
' initialize application
initAppli()
Case "calculation"
' calculate tax
calculateTax()
Case "return"
' return to form
returnToForm()
"Clear" checkbox
' initialize application
initAppli()
Else
' unknown action = init
initAppli()
End Select
End Sub
...
Private Sub calculateTax()
' Save the entries
Session.Item("form") = Request.Form
' Check the validity of the entered data
Dim errors As ArrayList = checkData()
' if there are errors, display a message
If errors.Count <> 0 Then
' prepare the error page
context.Items("href") = "main.aspx?action=return"
context.Items("link") = "Back to form"
context.Items("errors") = errors
Server.Transfer("errors.aspx")
End If
' No errors here - calculating the tax
Dim tax As Long = CType(Application("objTax"), tax).calculate( _
Request.Form("rdMarie") = "yes", _
CType(Request.Form("txtChildren"), Integer), _
CType(Request.Form("txtSalary"), Long))
' add the result to the existing simulations
Dim simulations As ArrayList
If Not Session.Item("simulations") Is Nothing Then
simulations = CType(Session.Item("simulations"), ArrayList)
Else
simulations = New ArrayList
End If
' Add the current simulation
Dim simulation() As String = New String() {Request.Form("rdMarie").ToString, _
Request.Form("txtEnfants").ToString, Request.Form("txtSalaire").ToString, _
tax.ToString}
simulations.Add(simulation)
' Add the simulations to the session and context
context.Items("simulations") = simulations
Session.Item("simulations") = simulations
' display the results page
context.Items("href") = "main.aspx?action=return"
context.Items("link") = "Back to form"
Server.Transfer("simulations.aspx", True)
End Sub
...
End Class
We have included only what is necessary above to understand the changes found exclusively in the [calculImpots] function:
- First, the function saves the form [Request.Form] to the session so that the form can be regenerated in the state in which it was validated. This must be done in all cases, since whether the operation results in the [erreurs.aspx] response or the [simulations.aspx] response, we return to the form via the [Back to form] link. To restore the form correctly, its values must have been saved in the session beforehand.
- If the entered data is correct, the function adds the current simulation (married, children, salary, tax) to the list of simulations. This list is found in the session associated with the "simulations" key.
- The list of simulations is stored in the session for future use. It is also placed in the current context because that is where the [simulations.aspx] view expects to find it
- The [simulations.aspx] view is displayed once the other information it requires has been placed in the context
6.4.5. Summary of Changes
The application is ready for testing. Let’s list the changes made to previous versions:
- A new view has been created
- The [main.aspx.vb] controller has been modified in one place: handling of the [calcul] action
6.4.6. Testing the web application
The reader is invited to perform the tests. Here is a reminder of the procedure. All application files are placed in a folder named <application-path>. Within this folder, a subfolder [bin] is created, containing the assembly [impot.dll] generated from the compilation of the business class files: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb]. The [impot.dll] file produced by this command must be placed in <application-path>\bin so that the web application can access it. The Cassini server is launched with the parameters (<application-path>,/impots4).
6.5. Conclusion
The previous examples have demonstrated, in a concrete scenario, mechanisms commonly used in web development. We have consistently used the MVC architecture for its educational value. We could have handled these same examples differently and perhaps more simply without this architecture. However, it offers significant advantages as soon as the application becomes somewhat complex with multiple pages.
We could continue our examples in various ways. Here are a few:
- The user might want to save their simulations over time. They could run simulations on day D and retrieve them on day D+3, for example. One possible solution to this problem is the use of cookies. We know that the session token between the server and a client is transmitted via this mechanism. We could also use this mechanism to transmit the simulations between the client and the server.
- At the same time the server sends the page containing the simulation results, it sends a cookie in its HTTP headers containing a string representing the simulations. Since these are in an [ArrayList] object, this object must be converted to a [String]. The server would set a lifespan for the cookie, for example 30 days.
- The client browser stores the received cookies in a file and sends them back each time it makes a request to a server that sent them, provided they are still valid (lifetime not exceeded). The server will receive a [String] character string for the simulations, which it must convert into an [ArrayList] object.
Cookies are managed by [Response.Cookies] when sent to the client and by [Request.Cookies] when received on the server.
- The mechanism described above can become quite resource-intensive if there are a large number of simulations. Furthermore, it is common for a user to periodically clear their cookies by deleting them all, even if they otherwise allow their browser to use them. So sooner or later, the simulation cookies will be lost. We may therefore want to store these on the server rather than on the client, in a database for example. To link simulations to a specific user, the application could start with an authentication phase requiring a username and password, which are themselves stored in a database or any other type of data repository.
- We might also want to secure the operation of our application. It currently makes two assumptions:
- the user always goes through the controller [main.aspx]
- and in that case, it always uses the actions available on the page that was sent to it
What happens, for example, if the user directly requests the URL [http://localhost/impots4/formulaire.aspx]? This scenario is unlikely since the user is unaware of this URL. However, it must be accounted for. It can be handled by the application controller [global.asax], which processes all requests made to the application. It can thus verify that the requested resource is indeed [main.aspx].
A more likely scenario is that a user does not use the actions available on the page the server sent them. For example, what happens if the user directly requests the URL [http://localhost/impots4/main.aspx?action=retour] without first filling out the form? Let’s try it. We get the following response:

The server crashes. This is normal. For the [return] action, the controller expects to find a [NameValueCollection] object in the session representing the form values it needs to display. It does not find them. The controller mechanism provides an elegant solution to this problem. For each request, the [main.aspx] controller can verify that the requested action is indeed one of the actions from the page previously sent to the user. We can use the following mechanism:
- Before sending its response to the client, the controller stores information identifying this page in the client’s session
- when it receives a new request from the client, it verifies that the requested action indeed belongs to the last page sent to that client
- The information linking pages and the actions allowed on those pages can be entered into the application’s [web.config] configuration file.
- Experience shows that application controllers share a broad common foundation and that it is possible to build a generic controller, with its specialization for a given application handled via a configuration file. This is the approach taken, for example, by the [Struts] tool in the field of Java web programming.



