This article assumes a familiarity with Visual Studio 2008 and ASP.NET. In this
article I will show you how to create a simple multi-tier (otherwise known as
n-tier) application using ASP.NET. We are going to be using several
ASP.NET controls. What coding
there is will be in the Business Logic Layer, and will be done in C#.
There's not too much, and if you only
know VB.NET, you should still be able to follow along with no problem.
Multi-tier applications exist for a
variety of reasons. This article won't go into too much depth on when and why to
use multi-tier architecture, however a quick overview is in order:
What Is Multi-Tier Architecture, And Why Do We Care?
Multi-tier (or n-tier) architecture refers to the practice of separating an
application into layers. Doing so makes it easier for you and other
developers to re-use your code. Typically, a multi-tier application will be
broken into three layers:
- Data Access Layer (DAL): This layer contains all the code that interacts with the data
store used by the application. For example, if your data store is a
typical RDBMS like Sql Server or Oracle, the data access layer will contain the
code that calls the stored procedures in your database to perform reads and
writes (or if you don't use stored procedures, the data access layer may contain
your SQL code).
- Business Logic Layer (BLL): This layer contains all the code that
implements your application's business rules. It makes calls to the DAL,
usually on behalf of the Presentation Layer (see below). The BLL typically
contains code that operates on the data received from the DAL prior to passing
it back to the Presentation Layer for display to the user. For example,
the BLL may perform calculations based on data retrieved from the DAL to be
displayed along with the actual data.
- Presentation Layer: This layer typically reads and writes to the BLL.
The Presentation Layer is responsible for taking the data it receives from the
BLL, formatting it, and displaying it to the user. It is also responsible
for collecting user-entered data and conveying it to the BLL for further
processing. In the .NET world, the Presentation Layer is typically created
with either ASP.NET Web Forms, Windows Forms, or more recently WPF (Windows Presentation Foundation).
Dividing an application's code in this manner allows us to take advantage of distributed
deployment. For
example, you could deploy your Data Access Layer to a data server, your Business
Logic Layer to an application server, and your Presentation Layer to a web
server (assuming it was a web-based GUI). Later, should you decide to create a
Windows GUI client that consumes the same Business Logic Layer component, you
could deploy that client to your users' desktops and reference the Business
component on the application server. In addition, a distributed,
multi-tiered application is much more likely to scale for future growth.
How We'll Go About It
We will be creating three Visual Studio projects, all within the same solution
for simplicity's sake. The application we create will read and write to
database tables which contain information on car manufacturer's, makes, and
models. Figure 1 shows the structure and relationships of the three
tables involved. You should create these tables for yourself, and when
doing so, don't forget to specify Primary Keys for each table as indicated in
the diagram:
Figure 1
I've pre-populated each table as shown in Figure 2, Figure 3, and
Figure 4:
Figure 2:
Manufacturer table
Figure 3:
Make table
Figure 4: Model table
You will need to set these tables up in your database in order to play along
with us during the creation of the application. The data doesn't have to
match exactly, but the structures of each table does. Also, when entering
data, keep in mind the relationships between the tables as shown in Figure 1
above.
As mentioned earlier, our goal is to create a multi-tier application consisting of a Data Access
Layer, a Business Logic Layer, and a Presentation Layer. There will be
absolutely no direct communication between the Presentation Layer and the Data
Access Layer. Furthermore, the Data Access Layer will be totally unaware
of the Business Logic Layer, and the Business Logic Layer will be totally
unaware of the Presentation Layer. In this way (as it should always be in
multi-tier architecture), each of the layers should be able to support any .NET
client. For example, although our Presentation Layer will be web-based, once
this application is finished, you will be able to create a new Presentation
Layer in Windows Forms (or WPF, for that matter) that can consume the Business Logic
Layer we will be building (which in turn will consume the Data Access Layer).
When we are finished building our application, it will allow us to perform maintenance on the three tables
above. It will also contain a reporting function which will enable a user
to display a list of all models in the system, including the Manufacturer Name,
Make Name, Model Name, and Model Year.
Let's Do It
OK, so start off by creating a new solution in Visual Studio, and call it
"Cars". (As I mentioned at the top of the article, I'm assuming you know
your way around Visual Studio, so I'm not going to take you through creating
solutions and projects within Visual Studio. If you don't know how to do
that already, please take advantage of the documentation and videos on
Microsoft's web site to familiarize yourself with these procedures.)
Developing the Data Access Layer
Within the Cars solution, create a new Class Library project and call it
"CarsDAL". This project is where we will develop our Data Access Layer.
Once the project is created, delete the Class1.cs file that gets created by
default as we won't be needing it. (I told you there wasn't going to be
much coding at the beginning, see?) Then, create a new folder named
App_Code in the project.
Right click on the App_Code folder, add a new DataSet, and name it
"CarsDataSet". Once the DataSet is
added, a Table Adapter Design Window will open. Right click anywhere
within the TableAdapter Design Window, the click on Add, then TableAdapter from
the popup menu. This will launch the TableAdapter Configuration Wizard.
We will now create our first TableAdapter, the Manufacturer TableAdapter, by
taking the following steps:
-
On the first page of the TableAdapter Configuration Wizard, choose your data
connection, which should point to the "Cars" database schema you created in
preparation earlier, then click Next.
-
The next page asks you to choose a Command Type. For the purposes of this
article we will choose the option that says, "Create New Stored Procedures".
Then click Next.
-
On the next screen, click the Query Builder button, which will load the Query
Builder window. The Add Table dialog box is displayed. Select
Manufacturer, then click the Add button, and then close the Add Table dialog
box. In the Manufacturer table now displayed in the top frame of the Query
Builder window, check each of the fields. Leave the "*(All Columns)"
checkbox unchecked. Then click OK in the Query Builder window, and click
Next to advance the TableAdapter Configuration Wizard.
-
In the next screen you are asked to name the four stored procedures that will be
created for you for the Manufacturer TableAdapter. (That's right, you
don't need to create them yourself, Visual Studio does it for you.) Change
the default names so that the four stored procedures are named Manufacturer_s,
Manufacturer_i, Manufacturer_u, and Manufacturer_d, for Select, Insert, Update,
and Delete respectively. Click Next.
-
The next screen is the Choose Methods screen. We're going to uncheck the
"Fill A DataTable" checkbox, and leave "Return A DataTable" and
"GenerateDBDirectMethods" checkboxes checked. Also, in the text field
under the "Return A DataTable" checkbox, change the method name to
GetManufacturerData. Click Next.
-
The Wizard Results screen will list all of the procedures and methods that were
generated for you. Look these over, then click Finish.
-
When the TableAdapter Configuration Wizard closes, you should see the
Manufacturer TableAdapter represented in the TableAdapter Design Window.
It should contain two sections; the Manufacturer section which lists the fields,
and the ManufacturerTableAdapter section which lists the queries available in
the TableAdapter. If you recall, we only created one query for this
TableAdapter, called GetManufacturerData(), which returns all of the rows in the
Manufacturer table. It's a good idea to create one more query which we
will call GetManufacturerDataById(), so that we can load a particular
Manufacturer record if necessary. This query is going to accept the MfrId
as a parameter. We'll create it as follows:
-
Right click on the bottom half of the Manufacturer TableAdapter, anywhere in the
ManufacturerTableAdapter section. Choose Add Query from the popup menu,
and you will be presented with the TableAdapter Query ConfigurationWizard.
Choose "Create New Stored Procedure", then click Next.
-
On the Query Type screen choose the first option, "SELECT which returns rows",
then click Next.
-
In the Generate Stored Procedure screen, you can use the Query Builder, or you
can manually type in your query. It should end up looking like this:
SELECT MfrId, MfrName, Created_Date FROM Manufacturer WHERE (MfrId = @MfrId)
-
Then click Next. On the Create Stored Procedure screen, name the stored
procedure "Manufacturer_By_Id_s", then click Next.
-
On the Choose Methods screen, uncheck the "Fill A DataTable" checkbox and leave
the "Return A DataTable" checkbox checked. Change the name of the method
to "GetManufacturerDataById", then click Next.
-
The Wizard Results screen will list all of the procedures and methods that were
generated for you. Look these over, then click Finish.
-
You should now see the Manufacturer TableAdapter represented in the TableAdapter
Design Window with the new method included.
Now, repeat each of those steps for the Make table, and then again for the Model
table. Be sure to create "ById" queries and methods for each table,
passing the appropriate field (MakeId and ModelId) and naming each query
correctly depending on the table you're dealing with. When you're done, there is
just one more step and we'll be done with the Data Access Layer.
OK, now we need to create just one more TableAdapter in the DAL. As
before, right click on any empty area within the TableAdapter Design Window, and
click Add, and then TableAdapter in the popup menu. This will load the
TableAdapter Configuration Wizard one more time:
-
Choose your Cars
connection string, then click Next.
-
Choose Create New Stored Procedures, then click Next.
-
In the Query Builder window, either click the Query Builder button or manually
type your SQL query. Either way, it should look pretty much like this when
you're done:
SELECT Manufacturer.MfrName, Make.MakeName, Model.ModelName, Model.ModelYear
FROM Model INNER JOIN Make ON Model.MakeId = Make.MakeId INNER JOIN Manufacturer
ON Make.MfrId = Manufacturer.MfrId
-
Then click Next. In the Create Stored Procedures screen, change the name
of the Select query to "ModelReport_s". The rest of the queries won't
matter since you can't insert, update, or delete when joining tables as we did
with this query. Visual Studio is smart enough to recognize this and
doesn't actually create these queries regardless of what you name them.
(Why it wasn't smart enough to just disable them is beyond me.) Anyway,
click Next when that's done.
-
Again you will see the Choose Methods screen. As before, uncheck the "Fill
A DataTable" checkbox and leave the "Return A DataTable" checkbox checked.
Change the method name to "GetModelReportData". Click Next.
-
The Wizard Results screen will list all of the procedures and methods that were
generated for you. Look these over, then click Finish.
The Data Access Layer is now complete. Next, we'll take a look at the
Business Logic Layer.
Business Logic Layer
Create a new Class Library project in the Cars solution, and call it CarsBLL.
Next, add a reference to the CarsDLL project. Then rename the Class1.cs
file created by default to CarsBLL.cs, add the
[System.ComponentModel.DataObject] attribute to the CarsBLL class, and add four fields, one
for each adapter created in the DAL. The CarsBLL should look like this at this point:
The [System.ComponentModel.DataObject]
attribute, along with the others we will be adding to the methods we create next
in this class, will make the BLL and its methods visible to the controls we will
be using later in the Presentation Layer.
Next, we'll add a property for each adapter, methods to Add, Update, Delete, and
Get data for the Manufacturer, Make, and Model tables, and a Get method for the
Report adapter. Note that each of these methods will make calls into the
DAL, which as we saw earlier, makes calls to the database. Once these
properties and methods are created, your CarsBLL class should look something
like this:
Note the attributes added to each method: [System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Select, true)]. Again, this ensures that
they are visible to the ObjectDataSource control which will be used in the Presentation Layer.
Now, you may have noticed that the BLL Delete methods all accept one parameter,
which represents the primary key for the corresponding table. We're going
to have to go back to the DAL and adjust the Delete methods there and in the
Delete stored procedures that were generated by Visual Studio. For some
reason, Visual Studio's TableAdapter Configuration Wizard generates these
methods and procedures so that it's necessary to pass a variable for every field
into them. That's just ridiculous. Visual Studio should be able to
recognize when a table has a primary key, and it should generate the Delete
methods and procedures accordingly. Well, it doesn't, so we're going to do
that. (We could leave it the way it is and adjust our BLL to deal with
this stupidity, but I have a low tolerance for stupidity. Besides, it's
almost as easy to do it correctly, and certainly worth the extra effort.)
So, let's go back to our CarsDAL project and take a look at the
CarsDataSet.Designer.cs file that was generated by the TableAdapter
Configuration Wizard. Within that file there should be three different
methods called InitAdapter(), one for each table in our schema. Within
each of these methods you can see how each command object is created. You
want to look for where the parameters are being added to the command object.
Look for code that looks something like this:
The above code adds four parameters to the DeleteCommand object of the
Manufacturer TableAdapter. We only need the first two; the first parameter
represents the return value of the stored procedure, and the second one
represents the primary key value of the record to be deleted. That's all
we really need, so delete the third and fourth parameters. Once you've
done that, repeat the process for both the Make TableAdapter and the Model
TableAdapter.
Now we need to adjust the three stored procedures that perform the Deletes in
the respective tables. You will need to adjust the parameter list within
each stored proc so that it only expects the first "input" parameter as mentioned
above. In addition, you will need to adjust the WHERE clause in each of
these stored procs as well, so that they only include the primary key field.
Here's what the Manufacturer_d stored procedure should look like when you're
done:
You will need to do this for both the Make_d and Model_d stored procedures as
well.
We have now completed both the Data Access Layer and the Business Logic Layer.
Presentation Layer
The Presentation Layer is the final piece to our puzzle. Keep in mind that
although I'll be using ASP.NET in order to create a web GUI for the purposes of
this article, you could just as easily create a Windows Forms or a WPF-based
presentation layer for your app, and utilize the exact same Business Logic Layer
and Data Access Layer components we created previously. After all, that is
the point of multi-tier architecture.
Within our Cars solution, create a new ASP.NET Web Application project, and name
it CarsWeb. VS will create a Default.aspx page within the project which we
will look at soon. You can leave it alone for now.
Before we start coding, we want to add a reference to the CarsBLL project.
Just right-click on the CarsWeb project, choose Add Reference from the menu,
then choose the CarsBLL project from the Project tab. After that's done, rebuild
the solution. This will ensure that CarsBLL is visible from within
CarsWeb.
Our presentation layer is going to consist of a menu of options which will be
contained in Default.aspx. It will also contain a Maintenance page for
each of our tables where a user can view, edit, or delete records. In
addition, there will be a Reports page which will list each model, along with
the names of its Manufacturer and Make. VS already created our
Default.aspx page, so just add four new pages, naming them mfrmaint.aspx,
makemaint.aspx, modelmaint.aspx, and report.aspx. You
should have five aspx files in total, including Default.aspx. (Our GUI is
not going to be pretty or fancy, but it will be functional. You can add
pretty and fancy at your leisure.)
We'll start by adding an ObjectDataSource to the Manufacturer maintenance page:
- Open up the
mfrmaint.aspx page in design mode.
- Add an ObjectDataSource control to
the page by dragging it in from the Data section of the VS toolbox. It
will be named ObjectDataSource1 by default.
- Right-click on the ObjectDataSource
control and choose Configure Data Source from the context menu.
- In the
configuration window, choose CarsBLL.CarsBLL from the business objects list,
then click Next.
- The next screen contains four tabs; SELECT, UPDATE,
INSERT, and DELETE. In each of these tabs, ensure that the appropriate
method is selected. The chosen methods should be GetManufacturerData(),
UpdateManufacturer(), AddManufacturer(), and DeleteManufacturer() respectively.
- Click Finish
- Switch to Source view, and change the OldValuesParameterFormatString attribute
from "original_{0}" to just "{0}" (due to a bug in the ObjectDataSource control)
And now, a GridView control:
-
Open up the
mfrmaint.aspx page in design mode again
- Add a GridView control to the
page by dragging it in from the Data section of the Toolbox
- In the
Properties window of the GridView control, assign ObjectDataSource1 to the
DataSourceID property
- In the GridView Tasks sheet, check the following
options: Enable Paging, Enable Sorting, Enable Editing, and Enable
Deleting
- Set the ReadOnly property to True for the MfrId
and Created_Date fields
- Set the DataKeyNames attribute of the GridView
control to "MfrId", so that the control knows which is the Primary Key field
Now, repeat the above steps in both makemaint.aspx and modelmaint.aspx for Make
Maintenance and Model Maintenance. When that's all done, meet me at the
next paragraph.
Ok, now that the table maintenance pages are done, we'll do the Report page.
The report will consist simply of a call to the GetAllModels() method of our
BLL. It's just like the table maintenance pages we just completed, only
easier. Open up report.aspx in Design mode and add an ObjectDataSource
just like we did on the table maintenance pages. Right-click on it and
choose Configure Data Source from the menu. In the configuration window,
choose CarsBLL.CarsBLL from the business objects list, then click Next. On
the next screen we'll only be concerned with the SELECT tab this time. In
the SELECT tab, choose the GetAllModels() method and click Finish. Next,
add a GridView control, and assign ObjectDataSource1 as its DataSourceID
property. Then go to the GridView control's GridView Tasks sheet, check
Enable Paging and Enable Sorting.
Lastly, open up Default.aspx and create a simple menu with links to each of the
other four pages. Something like this:
And that's it! Build your solution, and you should have pages that look as
follows:

Default.aspx

mfrmaint.aspx

makemaint.aspx

modelmaint.aspx

report.aspx
You can see that I wasn't lying when I said the GUI wouldn't be pretty, but
hopefully I've shed some light on the subject of building a multi-tier
application in C#.
Dave Verschleiser
Murray Hill Technologies