Visual Studio .NET 2005:
Using RAD Tools for N-Tier Development
Andy Beaulieu, MCT, MCSD.NET
09/06/2005
DOWNLOAD
THE SAMPLE SOLUTION
Summary
Visual Studio .NET 2005 provides great advances in its RAD tools and designers,
making it possible to create functional applications without writing much of any
code. However, many of the new tools and designers are targeted towards creating
2-Tier applications where presentation, business, and data access logic are bundled
into a single tier. This approach is bad for many reasons including scalability
and reusability of business logic components. This article describes how the new
RAD tools in Visual Studio 2005 can be used to develop N-Tier applications. It provides
step-by-step instructions with screen shots and explores the powerful code generation
that VS.NET 2005 provides.
Contents
Drag-and-Drop
Applications?
What's Wrong with This Picture?
A Simple N-Tier Architecture
The Data Access
Layer
The Business Logic Layer
The Presentation
Layer - Web UI
Adding
Validation to the Web UI
The Presentation Layer - Windows UI
Adding Validation to the Windows UI
Summary
Drag-and-Drop Applications?
It is quite simple in VS.NET 2005 to create an application that allows
editing of data in a tabular view. Just follow these steps:
(1) Start VS.NET 2005 and select File/New/Web Site... and enter "http://localhost/2TierDemo"
in the Location field.
(2) Switch Default.aspx to Design view and expand the Server Explorer.
(3) Right-click the Data Connections node in Server Explorer and select "Add Connection..."
Enter the connection info below to connect to the Northwind database:
(4) Expand the new Data Connection you added to Server Explorer until you reach
the Tables Node, and then drag/drop the Customers table onto the Default.aspx form.
VS.NET will create a new GridView control for you.
(5) At this point, you can run the web application and view the Customers data in
a table.
(6) Let's make the table editable and dress it up a bit. Locate the Smart Tag for
the GridView object - to do so, float the mouse over the GridView in Design View
and look in the upper-right corner of the GridView object. You will see a little
arrow with a box around it pointing to the right. Click this Smart Tag arrow and
you will see a properties window like that below. Check all of the checkbox options
to allow paging, sorting, editing, deleting, and selection. Also, select the Auto
Format option and select the Colorful format.

(7) Run the application.
What's Wrong with this Picture?
Being able to whip together an editable grid in 30 seconds without writing
a single line of code is quite impressive. But let's look at what the designer created
for us. Open the code-behind file Default.aspx.vb and you will notice it is quite
empty! This is because a lot of code generated by the VS.NET 2005 designers is declarative,
and needs no source code to execute. Now take a look at Default.aspx in "Source
View." The two main components we will see are a GridView control (used to bind
to a datasource and display a tabular UI), and a SqlDataSource component (a database
agnostic component used to connect to any relational database, and perform retreival
and update functions).
<asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString1 %>"
DeleteCommand="DELETE FROM [Customers] WHERE [CustomerID] = @original_CustomerID"
InsertCommand="INSERT INTO [Customers] ([CustomerID], [CompanyName], [ContactName], [ContactTitle], [Address], [City],...)
VALUES (@CustomerID, @CompanyName, @ContactName, @ContactTitle, @Address, @City, @Region, @PostalCode, ...)"
ProviderName="<%$ ConnectionStrings:NorthwindConnectionString1.ProviderName %>"
SelectCommand="SELECT [CustomerID], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], ..."
UpdateCommand="UPDATE [Customers] SET [CompanyName] = @CompanyName, [ContactName] = @ContactName, [ContactTitle] = @ContactTitle, ...D">
<InsertParameters>
<asp:Parameter Name="CustomerID" Type="String" />
<asp:Parameter Name="CompanyName" Type="String" />
<asp:Parameter Name="ContactName" Type="String" />
<asp:Parameter Name="ContactTitle" Type="String" />
<asp:Parameter Name="Address" Type="String" />
<asp:Parameter Name="City" Type="String" />
<asp:Parameter Name="Region" Type="String" />
<asp:Parameter Name="PostalCode" Type="String" />
<asp:Parameter Name="Country" Type="String" />
<asp:Parameter Name="Phone" Type="String" />
<asp:Parameter Name="Fax" Type="String" />
</InsertParameters>
<UpdateParameters>
<asp:Parameter Name="CompanyName" Type="String" />
<asp:Parameter Name="ContactName" Type="String" />
<asp:Parameter Name="ContactTitle" Type="String" />
<asp:Parameter Name="Address" Type="String" />
<asp:Parameter Name="City" Type="String" />
<asp:Parameter Name="Region" Type="String" />
<asp:Parameter Name="PostalCode" Type="String" />
<asp:Parameter Name="Country" Type="String" />
<asp:Parameter Name="Phone" Type="String" />
<asp:Parameter Name="Fax" Type="String" />
<asp:Parameter Name="original_CustomerID" Type="String" />
</UpdateParameters>
<DeleteParameters>
<asp:Parameter Name="original_CustomerID" Type="String" />
</DeleteParameters>
</asp:SqlDataSource>
As you can see, the designer has created a 2-Tier application. The User Interface
and Data Access logic all live within our ASP.NET page. If we were to enhance
our page to add validation or other logic, we would be piling Business Logic into
this same tier as well. This is less than optimal for reusability, maintainability
and scalabilty of the components.
A Simple N-Tier Architecture
In the simplest of N-Tier models, we have a Presentation (User Interface)
Layer, a Business Logic Layer, and a Data Access Layer. Below is a quick summary
of how these different layers interact, and what .NET components we'll use to create
them. Keep in mind that one of the goals of this architecture is to be able to use
the RAD designers that VS2005 provides us (there are of course other architetures
worth exploring using templated code generation and other methods).
|
Tier
|
Description
|
What we'll create
|
|
Presentation Layer
|
Providers the User Interface to the user
|
ASP.NET pages and WindowsForms application |
|
Business Logic Layer
|
Provides validation and workflow.
|
TableAdapter Extension Class (inherited from TableAdapter in Data Access Layer) |
|
Data Access Layer
|
Provides a "programmer friendly" representation of the Database entities
|
TableAdapter Objects
|
|
Database
|
Acts as a persistent store for application data.
|
SQL Server (Northwind Sample Database) |
The Data Access Layer
The purpose of the Data Access Layer (DAL) is to create a "programmer friendly"
library for retrieving and updating data. By programmer friendly, I mean that the
developer is freed from the mundane and error prone tasks of creating ADO.NET objects,
and can instead concentrate on the more important Business Logic and Presentation
layers. In several of my past projects, I made use of code generation to create
a DAL based on the schema of the database. The idea is, there are always basic operations
that you need to perform on almost every table. So instead of coding these by hand,
why not have a code generator do it for you? Today there are several popular code
generation tools including
CodeSmith that do such code generation.
The.NET Framework 2.0 introduces a new component, called the TableAdapter, which
is ideal for creating DAL Components. You can think of the TableAdapter component
as a generated DAL component. It exists within the context of a Typed DataSet, and
connects to the database to fill and update the Typed DataSet.
Let's see how we can use the designers in VS.NET 2005 to quickly generate a DAL
component for the Products entity in the Northwind database.
(1) Create a new, blank solution file by selecting File/New/Project, then selecting
Other Project Types/Visual Studio Solutions/Blank Solution. Name the solution "NTierDemo"
(2) Add a new Class Library project called DataAccessLayer by right-clicking the
solution and select Add/New Project/Class Library.
(3) Delete the default generated Class1.vb, we won't be needing it.
(4) Add a new DataSet item called "DSProducts.xsd" to your project by right-clicking
the project and selecting Add/New Item/DataSet.
(5) Right now we have a blank DataSet item in our project, to which we will add
the Products schema and TableAdapter to. With the DSProducts.xsd open in the Data
Designer, select Data/Add/TableAdapter from the main VS.NET menu. This launches
the TableAdapter Query Configuration Wizard
(6) On the first page of the Wizard, you can select an existing Data Connection
or choose an existing one. In this case, we'll take the existing NorthwindConnectionString.
(7) The next step of the Wizard lets you select the source of the TableAdapter's
command objects. In this case, we will select "Use SQL statements." (Note that stored
procedures, in general, outperform embedded SQL statements since they have an optimized
plan already determined and stored).

(8) In the next step of the wizard, we specify the SQL statement to use as the source
of data. You can type in SQL in the provided textbox, but let's click the "Query
Builder..." button at the bottom of the screen. Then, select the Products table
from the list of available tables.

(9) The Query Builder is a RAD tool to create SQL queries. It allows you to graphically
specify tables to join, columns to select, and sort order, among other parameters.
Let's select the "All Columns" checkbox in the Products table as shown below, then
select OK.

(10) Back on the main page of the Wizard, click the Advanced button. This will show
the Advanced Options Dialog. Uncheck the "Use Optimistic Concurrency" checkbox.
If this checkbox were left checked, the SQL statements generated by the wizard would
include criteria to enforce optimistic concurrency checks. But there are better
ways of handling this, for example through using a timestamp (rowversion) column.
In fact, at the time of this writing, the latest beta of VS.NET will automatically
take advantage of a timestamp column for optimistic concurrency checks!

(11) Click the Finish button to complete the wizard.
(12) At this point, our Data Access Component has several useful methods available:
Fill and GetData - retrieve all Product records into a DataTable
Insert - inserts a new Product record into the database
Update - updates an existing Product record
Delete - deletes a Product record
But it is easy to extend our Data Access Component with new methods. Let's add a
GetById method which will return a single Productbased on Id. Once again, right-click
the DataSet in the Designer and select Add/Query. This time, for the select query,
we want to select a single Product row based on ProductId.

(19) When choosing "Method to Generate" for the GetById methods, select both options
for filling a DataTable and returning a DataTable.

The Business Logic Layer
The purpose of the Business Logic Layer (BLL) is to provide validation and workflow
for the application. By separating the BLL into a separate tier, we can reuse a
single set of components from different clients (for example an ASP.NET client and
a WindowsForms client).
Before we begin creating our Business Logic Layer, let's take a closer look at the
ObjectDataSource control, as we will be using this component to data bind our UI
elements to our BLL components. Most other Data Source controls in .NET 2.0 are
designed for use in a 2-tier architecture, but the ObjectDataSource control is specifically
designed to bind UI elements such as the GridView control directly to a middle tier
Business Logic Component.
However, in order to use the ObjectDataSource control, our BLL component methods
must adhere to certain design patterns. The "read" methods used to retrieve records
from the database can look like any of the following:
' a method returning a generic DataSet
Public Function GetProducts() As DataSet
' a method returning a strongly typed DataSet
Public Function GetProducts() As ProductDataSet
' a method returning a list implementing IEnumerable, a Collection,
or an Array
Public Function GetProducts() As ProductCollection
... and the Update, Insert, and Delete methods must accept each field as individual
parameters, or accept a single object which contains all record properties as class
properties. For example...
' an Update method accepting each field as a separate parameter
Public Sub Update(ByVal ProductId As Integer, ByVal ProductName As String,
_
ByVal CategoryId As Integer, ByVal Description As String)
' an Update method accepting a single object
' (the object must expose individual fields as properties)
Public Sub Update(ByVal oProductsBLL As CProductsBLL)
As it turns out, the TableAdapter class we created in the previous exercise can
be used as a source for the ObjectDataSource component. It exposes a "Get" method
that returns a Products typed DataSet, and also Insert/Update/Delete statements
that accept fields as parameters. Because of this, we can simply inherit from the
TableAdapterClass to create our Business Logic Layer component, and add our validation
logic to this extension layer.
Another little trick we will use is that of a Custom Exception Class to store validation
errors. By using this method, we will be able to take advantage of events in our
Presentation Layer later on to easily raise and handle validation errors.
So let's first take care of the business of creating our Custom Exception Class:
(1) Right-click the solution in Solution Explorer and select Add/Add New Project/Class
Project. Name this project "Utilities"
(2) Add a new class to the Utilities project called CExceptionDetail. This will
store the details of a single validation error:
Public Class CExceptionDetail
Private m_iRowNum As Integer
Private m_sColumnName As String
Private m_sValidationError As String
Public Sub New()
m_iRowNum = 0
m_sColumnName = ""
m_sValidationError = ""
End Sub
Public Sub New(ByVal iRowNum As Integer, ByVal sColumnName As String, ByVal sValidationError As String)
m_iRowNum = iRowNum
m_sColumnName = sColumnName
m_sValidationError = sValidationError
End Sub
Public Property RowNum() As Integer
Get
Return m_iRowNum
End Get
Set(ByVal value As Integer)
m_iRowNum = value
End Set
End Property
Public Property ColumnName() As String
Get
Return m_sColumnName
End Get
Set(ByVal value As String)
m_sColumnName = value
End Set
End Property
Public Property ValidationError() As String
Get
Return m_sValidationError
End Get
Set(ByVal value As String)
m_sValidationError = value
End Set
End Property
End Class
(3) Add a new class to the Utilities project called CValidationException with code
as follows. It is a fairly simple class that takes advantage of Generics (another
new feature in .NET 2.0) to store a collection of CexceptionDetail.
Public Class CValidationException
Inherits ApplicationException
Public ErrorsCollection As New List(Of CExceptionDetail)
Public Sub AddException(ByVal iRowNum As Integer, ByVal sColumnName As String, ByVal sValidationError As String)
ErrorsCollection.Add(New CExceptionDetail(iRowNum, sColumnName, sValidationError))
End Sub
Public Overrides Function ToString() As String
Dim sRetVal As String = "The following validation errors occurred:" & vbCrLf
Dim oExceptionDetail As CExceptionDetail
For Each oExceptionDetail In ErrorsCollection
sRetVal &= "(Row " & oExceptionDetail.RowNum & ") " & oExceptionDetail.ColumnName & ": " & _
oExceptionDetail.ValidationError & vbCrLf
Next
Return sRetVal
End Function
End Class
With our Custom Exception Class created, let's go back to the business of creating
our BLL Component:
(1) Right-click the solution in Solution Explorer and select Add/Add New Project/Class
Project. Name this project "BusinessLogicLayer" You can delete the default "Class1.vb"
file from the project.
(2) Add a new reference to the DataAccessLayer and Utilities projects by right-clicking
the project and selecting "Add Reference"
(3) Add a new class called "ProductsTableAdapterEx" which will inherit from the
ProductsTableAdapter class and include validation logic. The key here is the CheckIsValid
method, which validates the input parameters and fills an Errors Collection with
any validation errors encountered. If any validation errors are found, then a custom
validation exception is raised (we'll see how to handle this later in the User Interface
layer) :
Imports DataAccessLayer.DSProductsTableAdapters
Imports Utilities
Public Class ProductsTableAdapterEx
Inherits ProductsTableAdapter
Public Overrides Function GetDataById(ByVal ProductId As Integer) As DataAccessLayer.DSProducts.ProductsDataTable
Return MyBase.GetDataById(ProductId)
End Function
Public Overrides Function Insert(ByVal ProductName As String, ByVal SupplierID As System.Nullable(Of Integer), _
ByVal CategoryID As System.Nullable(Of Integer), ByVal QuantityPerUnit As String, _
ByVal UnitPrice As System.Nullable(Of Decimal), ByVal UnitsInStock As System.Nullable(Of Short), _
ByVal UnitsOnOrder As System.Nullable(Of Short), ByVal ReorderLevel As System.Nullable(Of Short), _
ByVal Discontinued As Boolean) As Integer
Dim oValidationErrors As New CValidationException
CheckIsValid(oValidationErrors, 0, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, _
UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued)
If oValidationErrors.ErrorsCollection.Count > 0 Then Throw oValidationErrors
Return MyBase.Insert(ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, _
UnitsOnOrder, ReorderLevel, Discontinued)
End Function
Public Overrides Function Update(ByVal ProductName As String, ByVal SupplierID As System.Nullable(Of Integer), _
ByVal CategoryID As System.Nullable(Of Integer), ByVal QuantityPerUnit As String, ByVal UnitPrice As System.Nullable(Of Decimal), _
ByVal UnitsInStock As System.Nullable(Of Short), ByVal UnitsOnOrder As System.Nullable(Of Short), _
ByVal ReorderLevel As System.Nullable(Of Short), ByVal Discontinued As Boolean, ByVal Original_ProductID As Integer) As Integer
Dim oValidationErrors As New CValidationException
CheckIsValid(oValidationErrors, 0, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, _
ReorderLevel, Discontinued)
If oValidationErrors.ErrorsCollection.Count > 0 Then Throw oValidationErrors
Return MyBase.Update(ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, _
Discontinued, Original_ProductID)
End Function
Public Overrides Function Update(ByVal dataSet As DataAccessLayer.DSProducts) As Integer
Update(dataSet.Products)
End Function
Public Overrides Function Update(ByVal dataRow As System.Data.DataRow) As Integer
Update(dataRow("ProductID"), dataRow("ProductName"), dataRow("SupplierID"), dataRow("CategoryID"), _
dataRow("QuantityPerUnit"), dataRow("UnitPrice"), dataRow("UnitsInStock"), dataRow("UnitsOnOrder"),
dataRow("ReorderLevel"), dataRow("Discontinued"))
End Function
Public Overrides Function Update(ByVal dataTable As DataAccessLayer.DSProducts.ProductsDataTable) As Integer
Dim oValidationErrors As New CValidationException
Dim drProducts As DataAccessLayer.DSProducts.ProductsRow
Dim iRowNum As Integer
For Each drProducts In dataTable
If drProducts.RowState = DataRowState.Added Or drProducts.RowState = DataRowState.Modified Then
CheckIsValid(oValidationErrors, iRowNum, drProducts.ProductName, drProducts.SupplierID, drProducts.CategoryID, _
drProducts.QuantityPerUnit, drProducts.UnitPrice, drProducts.UnitsInStock, drProducts.UnitsOnOrder, _
drProducts.ReorderLevel, drProducts.Discontinued)
End If
iRowNum += 1
Next
If oValidationErrors.ErrorsCollection.Count > 0 Then Throw oValidationErrors
Return MyBase.Update(dataTable)
End Function
Public Sub CheckIsValid(ByRef oValidationException As CValidationException, ByVal iRowNum As Integer, ByVal ProductName As String, _
ByVal SupplierID As Integer, ByVal CategoryID As Integer, ByVal QuantityPerUnit As String, _
ByVal UnitPrice As Decimal, ByVal UnitsInStock As Short, ByVal UnitsOnOrder As Short, _
ByVal ReorderLevel As Short, ByVal Discontinued As Boolean)
If ProductName Is Nothing OrElse ProductName.Trim.Length = 0 Then
oValidationException.AddException(iRowNum, "ProductName", "ProductName is a required field.")
End If
End Sub
End Class
The Presentation Layer - Web UI
The purpose of the presentation layer is to present a user interface to
the user for input and validation. One great advantage you gain from tiering out
an application is that you can create different clients (for example ASP.NET and
WindowsForms), but still reuse the business logic and data access layers.
For this walk-thru, we will create an ASP.NET web client much like we did in the
initial "2-tier" demo at the beginning of the article. But in this case, we will
use our Customer BLL class as a data source, using the new ObjectDataSource component.
(1) Right-click the NTierDemo solution and select Add/New Web Site... In the Location
textbox, enter http://localhost/NTierDemoClient

(2) Add a reference to the BusinessLogicLayer and Utilities projects by right-clicking
the NTierDemoClient project and selecting Add Reference.
(3) Go to Design View for Default.aspx and drag-drop a GridView component from the
toolbox onto the form. Click the Smart Tag for the GridView component and select
<New data source...> from the Choose Data Source dropdown.
(4) Select the Object Data Source Type.

(5) On the next screen of the wizard, select the ProductsTableAdapterEx class from
the BusinessLogicLayer project.

(6) There are four tabs on the "Define Data Methods" step of the wizard which can
be used to define what methods should be called for each read and update operation.
The wizard will automatically select the correct methods on our Business Logic Component..

(7) At this point, it is interesting to open the code-behind file, Default.aspx.vb,
and
note the generated ObjectDataSource control:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" DeleteMethod="Delete"
InsertMethod="Insert" SelectMethod="GetData" TypeName="BusinessLogicLayer.ProductsTableAdapterEx"
UpdateMethod="Update">
<DeleteParameters>
<asp:Parameter Name="Original_ProductID" Type="Int32" />
</DeleteParameters>
<UpdateParameters>
<asp:Parameter Name="ProductName" Type="String" />
<asp:Parameter Name="SupplierID" Type="Int32" />
<asp:Parameter Name="CategoryID" Type="Int32" />
<asp:Parameter Name="QuantityPerUnit" Type="String" />
<asp:Parameter Name="UnitPrice" Type="Decimal" />
<asp:Parameter Name="UnitsInStock" Type="Int16" />
<asp:Parameter Name="UnitsOnOrder" Type="Int16" />
<asp:Parameter Name="ReorderLevel" Type="Int16" />
<asp:Parameter Name="Discontinued" Type="Boolean" />
<asp:Parameter Name="Original_ProductID" Type="Int32" />
</UpdateParameters>
<InsertParameters>
<asp:Parameter Name="ProductName" Type="String" />
<asp:Parameter Name="SupplierID" Type="Int32" />
<asp:Parameter Name="CategoryID" Type="Int32" />
<asp:Parameter Name="QuantityPerUnit" Type="String" />
<asp:Parameter Name="UnitPrice" Type="Decimal" />
<asp:Parameter Name="UnitsInStock" Type="Int16" />
<asp:Parameter Name="UnitsOnOrder" Type="Int16" />
<asp:Parameter Name="ReorderLevel" Type="Int16" />
<asp:Parameter Name="Discontinued" Type="Boolean" />
</InsertParameters>
</asp:ObjectDataSource>
The TypeName property is the class name of our BLL Component. This
is the class that the ObjectDataSource control will create on Select and Update
of the records. The SelectMethod,
InsertMethod, UpdateMethod and DeleteMethod properties are the methods that will
be called on the BLL component to Read, Insert, Update, and Delete. The DataObjectTypeName
property is the class that will be used as a parameter to the update methods.
(8) Lastly, on the GridView smart tag, check all of the "Enable" checkboxes for
Paging, Sorting, Editing, Deleting, and Selection.
Go ahead and run the solution, and try editing and updating a row.
Adding Validation to the Web UI
One of the goals of an N-Tier architecture is to always place validation
in the middle-tier. It is very tempting to break this rule because of the vast array
of Validator controls provided by ASP.NET, and the fact that you can save round-trips
to the web server by taking advantage of client-side validation. But it's important
to appreciate the consitency and reusaility you gain by coding your validation into
the Business Logic Layer instead of the Presentation Layer.
We'll use CustomValidator controls to signal validation errors to our users. To
add a CustomValidator control to a column in the GridView, we will create a TemplateColumn.
(1) First, delete the existing ProductName bound column by clicking on the "ProductName"
column header (you will see the column highlight) and then choosing "Remove Column"
from the GridView's Smart Tag.
(2) Next we will add our TemplateField. From the GridView's SmartTag, select "Add
New Column" and choose "TemplateField." Enter "Prodcut Name" for the Header Text.
(3) To edit the new TemplateField, right-click anywhere on the GridView and select
"Edit Template" and then "Column[10] - Product Name"
(4) Drag a Label control into the ItemTemplate section of the designer and name
it lblProductName. Click the SmartTag for lblProductName and select "Edit DataBindings."
Bind the Text property of the Label control to the ProductName field as shown below.

(5) Drag a TextBox control and a CustomValidator control into the EditItemTemplate
section, and name them txtProductName and vldProductName, respectively. Using
the SmartTag of the TextBox control, set the databinding for the Text property to
field ProductName as you did in the previous step. Then set the Text property of
the CustomValidator to an asterisk (*). The Template layout should now look similar
to below. To exit the template designer, click the SmartTag and select "End
Template Editing."

(6) Finally, add a ValidationSummary control to the top of the Web Form.
Currently, our BLL component will raise our Custom Exception when it encounters
a validation error. This is done through the CheckIsValid function. All we need
to do is catch the exception and show the errors to the user. The GridView control
provides a RowUpdated event which allows us to trap any exceptions that are raised
during an Update. But before we handle the RowUpdated event, let's make a handy
utility function that we can use to automatically flag the CustomValidator controls
on our web page.
As long as we maintain a consistent name for our CustomValidator controls, we can
cycle through the controls on a web form and set the IsValid and ErrorMessage properties
with the following utility function:
Public Shared Sub ApplyValidationToWebControl(ByRef oControl As System.Web.UI.Control, ByRef lstExceptions As List(Of CExceptionDetail))
Dim oExceptionDetail As CExceptionDetail
For Each oExceptionDetail In lstExceptions
Dim oFindValidationControl As CustomValidator = CType(oControl.FindControl("vld" & oExceptionDetail.ColumnName), CustomValidator)
If Not oFindValidationControl Is Nothing Then
oFindValidationControl.IsValid = False
oFindValidationControl.ErrorMessage = oExceptionDetail.ValidationError
End If
Next
End Sub
Using the above utility function, we can easily apply the validation errors to the
user interface by handling the RowUpdated event as follows:
Protected Sub GridView1_RowUpdated(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewUpdatedEventArgs) _ Handles GridView1.RowUpdated
If Not e.Exception Is Nothing Then
' if an exception was caught, then we can get details from the InnerException.
If TypeOf (e.Exception.InnerException) Is CValidationException Then
Dim oExceptionDetails As List(Of CExceptionDetail)
oExceptionDetails = CType(e.Exception.InnerException, CValidationException).ErrorsCollection
' notify the control not to raise the error further
e.ExceptionHandled = True
' keep the grid in edit mode
e.KeepInEditMode = True
' apply the validation to any matching CustomValidators
CValidationUtil.ApplyValidationToWebControl(GridView1.Rows(GridView1.EditIndex), oExceptionDetails)
End If
End If
End Sub
The Presentation Layer - Windows UI
One of the advantages to using an N-Tier architecture is that you can reuse the
same Business Logic Layer components for multiple clients. In this section we will
create a WindowsForms client that uses the same BLL component that we created above.
(1) Add a new project called NTierDemoWinClient by right-clicking the solution and
selecting "Add/New Project" and then "Windows Application."
(2) Add a reference to the BusinessLogicLayer, DataAccessLayer, and Utilities projects
by right-clicking the project and selecting "Add Reference"
(3) Add a new Data Source by selecting Data/Add New Data Source. This starts the
Data Source Configuration Wizard. In the first step of the Wizard, select a data
source type of "Object"

(4) Select the DSProducts DataSet and select Next and then Finish.

(5) Modify the display mode of the Data Source to "Details" by expanding the Data
Source window and right-clicking on the Products DataTable.

(6) Drag-and-drop the Products DataTable from the Data Sources window onto Form1.
Note that a BindingSource and BindingNavigator are automatically added for you.

(7) On the ProductsBindingNavigator Toolstrip, select the "Save" icon named bindingNavigatorSaveItem
and set its Enabled property to True (note that if you were binding a SQLDataSource,
this step would not be necesary, but we need to do this with binding to an object).
(8) We can now add code to fill and update the Data using our BLL component:
Private m_taProducts As New ProductsTableAdapterEx
Private Sub frmProductsDataBind_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
ProductsBindingSource.DataSource = m_taProducts.GetData
End Sub
Private Sub bindingNavigatorSaveItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles bindingNavigatorSaveItem.Click
' this is an update
ProductsBindingSource.EndEdit()
m_taProducts.Update(ProductsBindingSource.DataSource)
End Sub
Adding Validation to the Windows UI
When we implemented validation above in our Web UI, we created a
utility function to loop through controls on a web form and automatically set the
Validator controls to an invalid state. We can create a similar method for a WindowsForms
control using the SetColumnError method. Using SetColumnError in conjunction with
the ErrorProvider control will make it very easy to flag validation errors to the
user.
Public Shared Sub ApplyValidationToDataTable(ByRef dtData As DataTable, _
ByRef lstExceptions As System.Collections.Generic.List(Of CExceptionDetail))
Dim oExceptionDetail As CExceptionDetail
For Each oExceptionDetail In lstExceptions
dtData.Rows(oExceptionDetail.RowNum).SetColumnError(oExceptionDetail.ColumnName, oExceptionDetail.ValidationError)
Next
End Sub
By using SetColumnError, we will see how the ErrorProvider Control will automatically
show the user Validation Errors as they are caught.
(1) Add an ErrorProvider control from the Toolbox onto your form.
(2) Set the ErrorProvider DataSource property to "ProductsBindingSource"
(3) Modify the Save event so that it catches our Custom Validation Exception and
uses the utility function to display the errors:
Private Sub bindingNavigatorSaveItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles bindingNavigatorSaveItem.Click
' this is an update
ProductsBindingSource.EndEdit()
Try
m_taProducts.Update(ProductsBindingSource.DataSource)
Catch ex As CValidationException
MessageBox.Show("Error occurred:" & ex.ToString())
CValidationUtil.ApplyValidationToDataTable(ProductsBindingSource.DataSource, ex.ErrorsCollection)
End Try
End Sub
Summary
VS2005 adds many new wizards and designers that speed the development of data applications.
But if you use these tools in their default manner, you may tie yourself into a
2-Tier architecture. In this article we have examined various methods we can use
to leverage the new designers and wizards to quickly create an N-Tier architecture.
|