Asp.Net
Building Layered Web Applications with Microsoft ASP.NET 2.0 – Part 2
Introduction
In part 1 you saw the design of the classes that make up the Contact Manager application. You saw the classes in the Business Objects layer, the Business Logic Layer and you saw a number of classes in the DAL (Data Access Layer).
In this installment, I’ll show you the code for these classes. You’ll see how to implement the public properties for each of the classes in the Business Objects layer, how to write the code for methods like GetItem and GetList, and you’ll see how to connect to the database in a safe, transactional manner for inserts and updates. But, before we dig into the code, let’s take a brief look at the class design again. With the class design in mind, it’s easier to make decisions for the layout of your project. Although you could simply drop all your classes in the App_Code folder or in a separate class library project all under the same namespace, it’s often easier to put them in separate folders and namespaces so the code is easier to find. The following figure shows the organization of the classes in the application:

Figure 1 – The Full Class Diagram for the Contact Person Manager Application
At the top of the image you see the business objects in the BO namespace. These classes are placed in the Spaanjaars.ContactManager.BO namespace, following a best practice in namespace names where each namespace is named in the format CompanyName.ProjectName.Layer. I also put the enumerations in that layer, as they are used directly as types for certain properties of the classes in the same layer.
All the *Manager classes are put in the Spaanjaars.ContactManager.Bll namespace and similarly, I put the *DB classes in the Spaanjaars.ContactManager.Dal namespace.
To continue this separation, I also used different folders inside the App_Code folder of the application. In a larger application you may want to use three separate class libraries instead. The Business Layer class library would then have a reference to the Business Objects and Data Access class libraries, the DAL would also have a reference to the Business objects layer, while the final web site would reference the BO and BLL layers. In the sample application for this article, I decided to keep things simple and store everything in separate folders under App_Code, but the exact same principles would apply if you’d be using class library projects.
So, after I created a brand new web site in Visual Studio 2005 (I am using the Professional Edition, but you can follow along if you have the Express Edition), I added four folders under the special ASP.NET App_Code folder, and put a number of empty class files in them, each one named after the class or enum from the diagram in Figure 1. I ended up with the following Solution Explorer:

Figure 2 – The Solution Explorer for the Contact Person Manager Application
Each class in the BusinessLogic folder is put in the Bll namespace, files in the BusinessObject folder fall under the BO namespace while the classes in the DataAccess folder are put in the Dal namespace. For example, the Address class in the BusinessObject folder looks like this:
namespace Spaanjaars.ContactManager.BO
{
public class Address
{
// Implementation here
}
}
Although the enumerations actually fall under the BO namespace, I added them in a separate folder so they are easier to find.
Now that you’ve seen the basic structure of the site, let’s take a look at the actual code for the classes.
Code Organization in the Contact Person Manager Application
Before I show you the specific implementation of the classes, let’s first take a look at the basic structure of the class files. To make it easier to locate and maintain code, I often wrap them in #region / #endregion constructs. I typically use at least four different regions although I don’t use all of them in every file. Figure 3 shows the collapsed code for a typical business object:

Figure 3 – The Collapsed Code for the Address Class Showing Two Code Regions
The Private Variables region contains all the backing variables for the public properties and optionally other private, class scoped variables for the business objects. They’re often assigned a default value here as well. For example:
private string street = String.Empty;
The Public Properties region contains all the properties that the class exposes, like Id, Street and HouseNumber in the case of the Address class. Most of these properties use the private fields from the Private Variables region.
Figure 4 shows the collapsed code for a typical class in the data access layer:

Figure 4 – The Collapsed Code for the AddressDB Class Showing Two Other Code Regions
Many classes in the sample application contain a region called Public Methods. This region contains the class’s public methods that are used to interact with the object and the underlying database. You’ll find methods like GetItem and GetList to retrieve the objects, and Save and Delete to propagate object changes back to the database. In addition, the class also contains a Private Methods region that contains methods that are only accessible from within the class that defines them.
Coding the Classes for the Contact Person Manager Application
With the code organization behind our back, it’s time to look at the actual implementation of the classes. Since the Address, EmailAddress and PhoneNumber classes are very similar, I’ll only look at one of them: the EmailAddress class. Of course, I’ll also show you the EmailAddressManager and EmailAddressDB classes as we progress through the code.
After you have seen the EmailAddress class, I’ll take a detailed look at the ContactPerson class. Since this class is a bit more complex than the other three, it helps to understand the other three classes before you look at ContactPerson.
The EmailAddress Class – Implementation
The first region in the class, called Private Variables contains the following code:
private int id = -1; private string email = String.Empty; private ContactType type = ContactType.NotSet; private int contactPersonId = -1;
The private field id is initially set to -1. This value is used later on to determine whether an object is entirely new (id is still -1), or that it already has an associated record in the database (id is greater than 0). Normally, this is considered a magic number, where a fixed number has a special meaning that cannot be derived easily by its value. Magic numbers are usually considered a bad programming practice. You can avoid magic numbers by using Nullable Types (new in .NET 2) where an int could be null as well to indicate it doesn’t have a value. In this case, I can live with the magic number. Legal values for the id and contactPersonId fields come from identity columns in the database. These values are always greater than zero, so it’s clear from the code that -1 means something special. If you don’t like this, then check out Nullable Types on the MSDN site.
The email field (which contains the actual e-mail address) defaults to String.Empty while the type defaults to ContactType.NotSet, a custom value of the ContactType enumeration to indicate it hasn’t been set to another value before. Think of this as a null value for the ContactType type. Finally, the contactPersonId is set to -1 as well.
In the Public Properties region of the code, you find one property for each of these fields. All of them look similar to this:
public string Email
{
get
{
return email;
}
set
{
email = value;
}
}
You can see that each of the private fields is used as a backing variable for the public property.
Earlier I said that the classes in the BO namespace, like EmailAddress, are “dumb” classes. They are not capable of performing any action; all they do is contain data. To work with instances of EmailAddress, you need to use the methods in the EmailAddressManager class.
Working with Instances of EmailAddress
The EmailAddressManager contains a number of methods that allow you to perform CRUD operations on an EmailAddress: Create, Read, Update and Delete. Let’s take a look at Read first.
First of all, the EmailAddressManager class has a method called GetItem. This method accepts the ID of the EmailAddress in the database. The implementation of this method is really simple: all it does is forward the call to the GetItem method in the data access layer:
public static EmailAddress GetItem(int id)
{
return EmailAddressDB.GetItem(id);
}
Although this seems like a lot of work for nothing (why not have the calling code access the DAL method directly?), it serves a distinct purpose. Although not implemented in GetItem for the EmailAddressManager class, the code in the business layer is an ideal place for things like security. Instead of directly calling EmailAddressDB.GetItem, you could perform a security check first to determine whether the current user has sufficient access right to perform the call. You could, for example, modify GetItem so it looks like this:
public static EmailAddress GetItem(int id, IPrincipal currentUser)
{
if (!currentUser.IsInRole("EmailAddressManagers"))
{
throw new NotSupportedException(@"You're not allowed to call
EmailAddressManager.GetItem when you're not in the
EmailAddressManagers role");
}
return EmailAddressDB.GetItem(id);
}
You would then call this method from an ASPX page like this:
EmailAddress myEmailAddress = EmailAddressManager.GetItem(10, Context.User);
And, instead or in addition of this, you could use this method to cache the object returned by GetItem for some time, so you don’t have to hit the database every time you need to work with the object. Another valid activity for methods in the business layer is validation. You’ll see how this works when the Save method is discussed.
Since these kind of requirements – security, validation, caching and so on – are often application specific, it’s important to implement them in the business layer. If you implement them in the data access layer, you can’t reuse that layer in an application that has different requirements or demands.
The GetItem method is static which means you can call it without an object instance. With this method, calling code can get an instance of EmailAddress with a single line of code:
EmailAddress myEmailAddress = EmailAddressManager.GetItem(id);
where id is the ID of a valid EmailAddress in the database.
As you saw, GetItem in the business layer simply forwards the call to GetItem in the EmailAddressDB class. That method looks like this:
public static EmailAddress GetItem(int id)
{
EmailAddress myEmailAddress = null;
using (SqlConnection myConnection =
new SqlConnection(AppConfiguration.ConnectionString))
{
SqlCommand myCommand = new SqlCommand(
"sprocEmailAddressSelectSingleItem", myConnection);
myCommand.CommandType = CommandType.StoredProcedure;
myCommand.Parameters.AddWithValue("@id", id);
myConnection.Open();
using (SqlDataReader myReader = myCommand.ExecuteReader())
{
if (myReader.Read())
{
myEmailAddress = FillDataRecord(myReader);
}
myReader.Close();
}
myConnection.Close();
}
return myEmailAddress;
}
First, this method creates a new SqlConnection by passing in the connection string (retrieved from the web.config file through the static property ConnectionString on the AppConfiguration class). It then creates and sets up a new SqlCommand. The command is configured to call a stored procedure called sprocEmailAddressSelectSingleItem which returns a single EmailAddress by its ID. This ID is passed to the stored procedure by adding a parameter object to the command using AddWithValue. The stored procedure itself is as straight forward as it can be:
CREATE PROCEDURE sprocEmailAddressSelectSingleItem @id int AS SELECT Id, Email, EmailType, ContactPersonId FROM EmailAddress WHERE Id = @id
This procedure simple retrieves all the fields from the EmailAddress table for the specified ID.
The GetItem method in the EmailAddressDB class finally opens a connection to the database and then calls ExecuteReader to get an open SqlDataReader. Each row in the SqlDataReader implements the generic IDataRecord interface that gives you strongly typed access to the values within each row. The private helper method FillDataRecord is then used to create and fill an instance of the EmailAddress class based on the data in the IDataRecord instance:
private static EmailAddress FillDataRecord(IDataRecord myDataRecord)
{
EmailAddress myEmailAddress = new EmailAddress();
myEmailAddress.Id =
myDataRecord.GetInt32(myDataRecord.GetOrdinal("Id"));
myEmailAddress.Email =
myDataRecord.GetString(myDataRecord.GetOrdinal("Email"));
myEmailAddress.Type = (ContactType)
myDataRecord.GetInt32(myDataRecord.GetOrdinal("EmailType"));
myEmailAddress.ContactPersonId =
myDataRecord.GetInt32(myDataRecord.GetOrdinal("ContactPersonId"));
return myEmailAddress;
}
This method first creates a new instance of the EmailAddress class and then copies each of its fields from the IDataRecord in the EmailAddress. At the end of the method, the EmailAddress object is returned. This helper method is used to create a single instance of each class in GetItem, but is also used to get lists of objects in the GetList methods. To make it easier to use this code as the basis for an implementation of a different database, the method accepts an IDataRecord, an interface that all DataReaders, like the SqlDataReader and the OleDbDataReader implement.
The code in this method simply copies the data from the reader into the private fields of the classes by calling the appropriate Get* methods. These Get* methods expect a zero based index of the column in the IDataRecord. However, using “magic numbers” in your code makes it hard to maintain and read, so I used GetOrdinal to “translate” the name of a column in its index. Why the Get* methods don’t have an overload that accepts a string with the column name as well is beyond me though. This may be a nice addition for the .NET 3.5 framework.
Since all the properties of the EmailAddress are required columns in the database, there’s no need to check for null values in the helper method. In cases where you do expect null values, you need to take extra precautions, as is the case with the Address class. This is code from that class’s FillDataRecord method:
if (!myDataRecord.IsDBNull(myDataRecord.GetOrdinal("ZipCode")))
{
myAddress.ZipCode =
myDataRecord.GetString(myDataRecord.GetOrdinal("ZipCode"));
}
Since ZipCode is not a required column in the Address table, I have to check for null values using IsDBNull. Only when the column isn’t null, I assign the ZipCode property the value from the reader. Otherwise, I leave it to its default value of String.Empty.
At the end of GetItem in the data access layer, the EmailAddress object is returned to GetItem in the business logic layer which in turn returns it to the client.
As you can see, the code in the EmailAddressDB class is tied to a specific database provider: SQL Server. However, with minimal changes, you can make it work with Access or Oracle. Alternatively, you can write almost completely provider agnostic code using the new database provider factory model in .NET 2. Check out my book ASP.NET 2.0 Instant Results for an example of this in the chapter The Wrox Blog.
But, however you change the code in the DAL, you don’t need to change the business objects or business logic layers; as long as the DAL returns an object from the BO namespace, which is shared between the Bll and the Dal namespaces, everything will work, regardless of the database you’re using.
The GetList method in the EmailAddress class in the Business Layer follows a similar approach. However, since we’re potentially working with more than one e-mail address, we need some way to work with a collection of EmailAddress records. In .NET 2, Microsoft introduced a concept called Generics that allow you to write classes that can work with different types of other classes. In the case of the ContactPerson, I make use of the generic List<T> class. The List<T> works very similar to an ArrayList in that it allows you to easily add and remove objects to a list. However, one of the shortcomings of the ArrayList is the fact you can store *any* object in it. So, it’s possible to accidentally store both an EmailAddress and an Address in a list. This obviously can lead to problems when you want to use the list to bind it to a GridView for example. The GridView is set up to work with a single object type and will be confused (and raise an exception) when you pass it different object types. Also, because the GridView isn’t able to determine the actual type of the object, it also can’t generate strongly typed columns for you, impacting the design time experience and your productivity.
The List<T> on the other hand is designed to work with objects of type T. You determine what T represents when you declare a variable of type List<T> as in the following example that creates a list that can only hold EmailAddress instances:
List<EmailAddress> tempList = new List<EmailAddress>();
With this declaration, you’ll get a compile time error whenever you try to add an object to the list that is not of type EmailAddress.
In my initial design for this application, I used List<T> directly. So, my ContactPerson class had properties of type List<Address>, List<EmailAddress> and so on. Additionally, the GetList methods would work with and return types of List<T> as well. However, after a discussion with someone I work with, I decided to create four separate *List classes, each inheriting from its generics counterpart. For example, the EmailAddressList class looks like this:
public class EmailAddressList : List<EmailAddress>
{
public EmailAddressList()
{ }
}
Although at this stage I am not really adding anything to List<T> in my custom list, this additional layer serves two purposes. First of all, the API has now become a lot simpler for users unfamiliar with generics as the generics have been taken out of the API completely. So, instead of declaring a list that can hold e-mail address records with the following generics syntax:
private List<EmailAddress> emailAddresses = new List<EmailAddress>();
you can now declare an address list with this simplified code:
private EmailAddressList emailAddresses = new EmailAddressList();
This code is a lot easier to read and understand by other developers.
Another benefit of the separate class is that it is easier to add additional methods or even change the underlying type of EmailAddressList completely, to something like CollectionBase for example without breaking existing code.
Once you understand a little how List<T> and the classes that inherit from it work, the rest of the code in GetList is straight forward:
public static EmailAddressList GetList(int contactPersonId)
{
EmailAddressList tempList = null;
using (SqlConnection myConnection =
new SqlConnection(AppConfiguration.ConnectionString))
{
// Open connection here. Not shown.
using (SqlDataReader myReader = myCommand.ExecuteReader())
{
if (myReader.HasRows)
{
tempList = new EmailAddressList();
while (myReader.Read())
{
tempList.Add(FillDataRecord(myReader));
}
}
myReader.Close();
}
}
return tempList;
}
Once the ExecuteReader method returns an open SqlDataReader, the code starts looping, creating a new instance of EmailAddress with FillDataRecord and adding it to the list on each iteration. Creating the EmailAddress object is identical to the code in GetItem. The initial time it took to the create the private helper method has now been compensated for as we can easily reuse it in different situations where we need to create an EmailAddress from any object that implements the IDataRecord interface.
Inserting, Updating and Deleting Data
While it’s nice that you can read a single item or a list of items from the database, the EmailAddressManager class really needs some methods to send data to the database. In particular: it needs an Insert, an Update and a Delete method to complete the CRUD acronym I talked about earlier.
In the case of the Contact Person Manager application, both Insert and Update are handled by the same upsert method: Save(). This means that the code in the business logic and in the data access layer makes no distinction between these two operations.
The Save method in the business layer is straight forward; just as you saw with GetItem and GetList, it simply forwards the call to the DAL. It stores the return value of the Save method (which is the ID of the EmailAddress in the database) in the Id property:
public static int Save(EmailAddress myEmailAddress)
{
myEmailAddress.Id = EmailAddressDB.Save(myEmailAddress);
return myEmailAddress.Id;
}
Remember, this would be the place to do security checks. For example, you could check if the current user was a ContentManager before you’d allow the call to Save(). After I show you how Save() is implemented in the DAL, I’ll show you another variation of Save in the business layer that validates the EmailAddress before it sends it to the DAL.
The current version in the demo application calls the Save method on the associated database class. Let’s take a look at the code to see how this all works. This is the Save() method in the EmailAddressDB class in the data access layer:
public static int Save(EmailAddress myEmailAddress)
{
int result = 0;
using (SqlConnection myConnection =
new SqlConnection(AppConfiguration.ConnectionString))
{
SqlCommand myCommand = new SqlCommand(
"sprocEmailAddressInsertUpdateSingleItem", myConnection);
myCommand.CommandType = CommandType.StoredProcedure;
if (myEmailAddress.Id == -1)
{
myCommand.Parameters.AddWithValue("@id", DBNull.Value);
}
else
{
myCommand.Parameters.AddWithValue("@id", myEmailAddress.Id);
}
myCommand.Parameters.AddWithValue("@email", myEmailAddress.Email);
myCommand.Parameters.AddWithValue("@emailType", myEmailAddress.Type);
myCommand.Parameters.AddWithValue(
"@contactPersonId", myEmailAddress.ContactPersonId);
DbParameter returnValue;
returnValue = myCommand.CreateParameter();
returnValue.Direction = ParameterDirection.ReturnValue;
myCommand.Parameters.Add(returnValue);
myConnection.Open();
myCommand.ExecuteNonQuery();
result = Convert.ToInt32(returnValue.Value);
myConnection.Close();
}
return result;
}
Similar to what you saw before, this code sets up a connection and a command to call the stored procedure sprocEmailAddressInsertUpdateSingleItem.
Then, based on whether the ID of the EmailAddress is -1 (a new item) or greater than -1 (an existing item), it creates a parameter called @id and passes it the relevant value: DBNull.Value (which translates to null in database land) for the new item, or the actual ID for an existing item. This is the “magic number” I talked about earlier in the article. I’ll show you in a bit how the stored procedure deals with these values.
The code then continues to setup a few other parameters for the other fields of the EmailAddress class: email, emailAddressType and contactPersonId.
It also sets up a ReturnValue parameter to catch the return value from the stored procedure. This procedure returns the new ID of an item when it’s inserted, or the existing ID in case of an update.
At the end, ExecuteNonQuery is called to send the data to the database, the return value is retrieved from the parameter which is then returned to the calling code. It’s useful to have the Save method return the ID of the item so you can use it in your presentation layer somehow, for example, to redirect the user to a details page shopwing the new e-mail address.
The final thing you need to look at is the stored procedure that performs either the INSERT or the UPDATE:
CREATE PROCEDURE sprocEmailAddressInsertUpdateSingleItem
@id int,
@email nvarchar (100),
@emailType int,
@contactPersonId int
AS
DECLARE @ReturnValue int
IF (@id IS NULL) -- New Item
BEGIN
INSERT INTO EmailAddress
(
Email,
EmailType,
ContactPersonId
)
VALUES
(
@email,
@emailType,
@contactPersonId
)
SELECT @ReturnValue = SCOPE_IDENTITY()
END
ELSE
BEGIN
UPDATE EmailAddress SET
Email = @email,
EmailType = @emailType,
ContactPersonId = @contactPersonId
WHERE Id = @id
SELECT @ReturnValue = @id
END
IF (@@ERROR != 0)
BEGIN
RETURN -1
END
ELSE
BEGIN
RETURN @ReturnValue
END
GO
When the @id parameter contains null, the code performs an INSERT in the EmailAddress table. It then uses SCOPE_IDENTITY() to get the new ID of the record that was just inserted.
If @id does contain a value, the procedure performs an UPDATE for the requested record and then assigns the ID of the record to the @returnValue variable which is returned at the end to the calling code.
In both case, the ID of the record is returned to the calling code. There it is caught by the return value parameter, converted to an int and returned to the Save method in the business logic layer.
Earlier I said that the Save method in the business logic layer is also a good place to validate the objects you’re saving. To do this, you can implement a method like this:
private static void Validate(EmailAddress myEmailAddress)
{
if (String.IsNullOrEmpty(myEmailAddress.Email))
{
throw new Exception(@"Cannot save an EmailAddress
without a valid Email property.");
}
if (myEmailAddress.Type == ContactType.NotSet)
{
throw new Exception(@"Cannot save an EmailAddress
without a valid Type property.");
}
if (myEmailAddress.ContactPersonId == -1)
{
throw new Exception(@"Cannot save an EmailAddress
without a valid ContactPersonId property.");
}
}
This method accepts an instance of EmailAddress, validates its properties and then throws an exception when the EmailAddress doesn’t appear to be valid. In this example, the EmailAddress is considered invalid when the Email property has not been set, when the Type property is still ContactType.NotSet or when the ContactPersonId contains a value of -1.
You call the Validate method right before you send the object to the data access layer:
public static int Save(EmailAddress myEmailAddress)
{
Validate(myEmailAddress);
myEmailAddress.Id = EmailAddressDB.Save(myEmailAddress); return myEmailAddress.Id; }
Since the Save method can now throw exception when the object is not in a valid state, it’s a good idea to implement a public method Validate as well that performs the same validation but doesn’t throw the exception. You could add an overload for the Validate method and have it accept a bool that indicates whether or not you want to throw an exception when the object is in an invalid state. With the public Validate method, users of your class can check if the object is valid before they attempt to call Save.
The Delete mechanism in the EmailAddressManager class follows a very similar approach, so I won’t show you the code for this. Instead, you’re advised to download the entire application at the end of this article and see for yourself.
This concludes the fields, properties, constructors and methods of the three contact record classes. While I only showed you the implementation for the EmailAddress class, it should be easy to understand Address and PhoneNumber as well, as both of these classes follow the exact same pattern.
In the next section, I’ll show you the ContactPerson class.
The ContactPerson Class – Implementation
The ContactPerson class is discussed separately, because it has a few twists that are worth looking at.
In the Private Variables and Public Properties regions you see code that sets up three lists: one for the EmailAddresses, one for the Addresses and for the PhoneNumbers. Here’s the code for the three private backing variables:
private AddressList addresses = new AddressList(); private PhoneNumberList phoneNumbers = new PhoneNumberList(); private EmailAddressList emailAddresses = new EmailAddressList();
These lists allow you to access the respective contact data collections for a contact person. Earlier you saw how each of these lists inherits from its generics List<T> counterpart.
There’s one more public property that’s worth looking at: the FullName:
public string FullName
{
get
{
string tempValue = firstName;
if (!String.IsNullOrEmpty(middleName))
{
tempValue += " " + middleName;
}
tempValue += " " + lastName;
return tempValue;
}
}
You’ll see there’s no backing variable for the FullName property because the getter makes use of existing variables: firstName, middleName and lastName to build up the complete name of a ContactPerson. With this property, showing a contact person’s full name is now as easy as accessing this property. Client code no longer needs to bother with the individual fields, and whether the middleName actually contains a value or not.
The GetList method of ContactPersonManager is almost identical to the ones you saw before, so I won’t go over it anymore. The GetItem method, however, is quite different. In addition to getting its own data, the GetItem method is also responsible for getting the associated contact records, like Addresses and Phonenumbers. However, since there are circumstances where you may not need to associated contact records, the GetItem method has a second overload that allows you to control this. Either call GetItem with only the ID of the contact person, or call GetItem and pass false for the second parameter: getContactRecords. If you call GetItem with the ID and true as the value for the getContactRecord parameter, you get a fully populated ContactPerson object, with all of its contact data collections set up as well.
public static ContactPerson GetItem(int id)
{
return GetItem(id, false);
}
public static ContactPerson GetItem(int id, bool getContactRecords)
{
ContactPerson myContactPerson = ContactPersonDB.GetItem(id);
if (myContactPerson != null && getContactRecords)
{
myContactPerson.Addresses = AddressDB.GetList(id);
myContactPerson.EmailAddresses = EmailAddressDB.GetList(id);
myContactPerson.PhoneNumbers = PhoneNumberDB.GetList(id);
}
return myContactPerson;
}
The first overloads calls the second, and passes a default value of false for getContactRecords.
Just as with the EmailAddressManager class, the ContactPersonManager class calls ContactPersonDB.GetItem(id) to get a ContactPerson instance from the database. It then checks if the ContactPerson returned from the DAL is not null and that getContactRecords is true. If both are true, the code proceeds by assigning values to the three lists for the Addresses, EmailAddresses and the PhoneNumbers. Each of these lists gets a value by calling its associated GetList method in the DAL, passing it the ID of the contact person. That way, the Address class knows for which contact person it must retrieve address records for example.
At the end of the code, if the ContactPerson was found in the database, it is returned to the calling code. Otherwise, myContactPerson would contain null indicating to calling code that there is no ContactPerson for the requested ID.
The Save method of the ContactPersonManager class is also capable of saving the associated contact records. It does this by calling Save on each contact record in the list when requested:
public static int Save(ContactPerson myContactPerson)
{
using (TransactionScope myTransactionScope = new TransactionScope())
{
int contactPersonId = ContactPersonDB.Save(myContactPerson);
foreach (Address myAddress in myContactPerson.Addresses)
{
myAddress.ContactPersonId = contactPersonId;
AddressDB.Save(myAddress);
}
foreach (EmailAddress myEmailAddress in myContactPerson.EmailAddresses)
{
myEmailAddress.ContactPersonId = contactPersonId;
EmailAddressDB.Save(myEmailAddress);
}
foreach (PhoneNumber myPhoneNumber in myContactPerson.PhoneNumbers)
{
myPhoneNumber.ContactPersonId = contactPersonId;
PhoneNumberDB.Save(myPhoneNumber);
}
// Assign the ContactPerson its new (or existing) ID.
myContactPerson.Id = contactPersonId;
myTransactionScope.Complete();
return contactPersonId;
}
}
First, the ContactPersonManager saves the ContactPerson by calling a method with the same name in the ContactPersonDB class. This is identical to how objects like EmailAddress are saved in the database. Then the code continues to save each individual contact record by looping through the respective collections and passing each contact record to the Save method in the respective *DB class.
foreach (EmailAddress myEmailAddress in myContactPerson.EmailAddresses)
{
myEmailAddress.ContactPersonId = contactPersonId;
EmailAddressDB.Save(myEmailAddress);
}
Before Save is called, each contact record gets its ContactPersonId property assigned, which is the result of the Save method of the ContactPerson.
Notice how the entire code block for the method is wrapped inside a using block that creates a new TransactionScope object. The TransactionScope automatically sets up a transaction for all other code that falls within its scope. This is useful to ensure the database remains in a valid state. Whenever an error occurs, for example because the code tries to save an Address with an invalid ContactPersonId, all previous database actions are rolled back. So, for example, when you save a contact person, then four of its addresses and two email addresses but the code fails on the first Save action of a phone number, the transaction for the ContactPerson, Addresses and EmailAddresses is undone.
For this code to work correctly, you need to have the Microsoft Distributed Transaction Coordinator up and running. As of Service Pack 2, it’s off by default. Check the following article for more details about configuring MSDTC.
- Distributed Transaction Coordinator
- How to Enable MSDTC on the Business Management Server
- Transactional Remote Receive
If you have the web site and the database on different machines, make sure you configure MSDTC on both of them.
The Delete method in the ContactPersonManager class always deletes the associated contact records; there is no point in having an Address record in the database without an associated ContactPerson. In fact, the database will throw an exception when you try to delete a ContactPerson that still has contact records attached to it.
public static bool Delete(ContactPerson myContactPerson)
{
return ContactPersonDB.Delete(myContactPerson.Id);
}
The Delete method doesn’t use a TransactionScope object to manage transactions. All contact data records are deleted in the procedure that deletes a contact person:
CREATE PROCEDURE sprocContactPersonDeleteSingleItem
@id int
AS
BEGIN TRAN
DELETE FROM
Address
WHERE
ContactPersonId = @id
IF @@ERROR <> 0
BEGIN
ROLLBACK TRAN
RETURN -1
END
DELETE FROM
EmailAddress
WHERE
ContactPersonId = @id
IF @@ERROR <> 0
BEGIN
ROLLBACK TRAN
RETURN -1
END
DELETE FROM
PhoneNumber
WHERE
ContactPersonId = @id
IF @@ERROR <> 0
BEGIN
ROLLBACK TRAN
RETURN -1
END
DELETE FROM
ContactPerson
WHERE
Id = @id
IF @@ERROR <> 0
BEGIN
ROLLBACK TRAN
RETURN -1
END
COMMIT TRAN
This code first deletes all contact data based on the incoming ID of the contact person. Once the contact records have been deleted, it also deletes the contact person itself. Notice how after each DELETE statement the code checks for an error, and if necessary rolls the transaction back. This is necessary to avoid records from being deleted when an error occurs later in the stored procedure. Thanks to csharpdev who commented on this at the end of this article, pointing out a big mistake I had in an earlier version of this article.
Wrapping it Up
With the code you have seen so far, we have a nice API to manage ContactPerson objects and their associated contact records. With the available classes, you can now create, edit and delete ContactPersons, EmailAddresses, Addresses and PhoneNumbers programmatically. The following code snippet shows you how to create a ContactPerson and save it in the database:
ContactPerson myContactPerson = new ContactPerson(); myContactPerson.FirstName = "Imar"; myContactPerson.LastName = "Spaanjaars"; myContactPerson.DateOfBirth = new DateTime(1971, 8, 9); myContactPerson.Type = PersonType.Family; Address myAdress = new Address(); myAdress.Street = "Some Street"; myAdress.HouseNumber = "Some Number"; myAdress.ZipCode = "Some Zip"; myAdress.City = "Some City"; myAdress.Country = "Some Country"; myContactPerson.Addresses.Add(myAdress); EmailAddress myEmailAdress = new EmailAddress(); myEmailAdress.Email = "Imar@DoNotSpam.com"; myEmailAdress.Type = ContactType.Personal; myContactPerson.EmailAddresses.Add(myEmailAdress); PhoneNumber myPhoneNumber = new PhoneNumber(); myPhoneNumber.Number = "555 - 2368"; myPhoneNumber.Type = ContactType.Personal; myContactPerson.PhoneNumbers.Add(myPhoneNumber); ContactPersonManager.Save(myContactPerson);
This code creates a new ContactPerson, sets a few public properties, and then adds an Address, an EmailAddress and a PhoneNumber instance to the respective collections of the ContactPerson object. As soon as Save is called, the ContactPerson is saved in the database, as well as all of its contact records.
Getting a fully populated ContactPerson is now very easy as well. Let’s assume the ContactPerson in the previous code block got an ID of 29. Getting it from the database, together with all its associated data can be done with a single line of code:
myContactPerson = ContactPersonManager.GetItem(29, true);
To see what the ContactPerson looks like, you can set a breakpoint in your code and then look at the ContactPerson object in the watch window. To do this, follow these steps:
- Clear all existing break points by pressing Ctrl+Shift+F9
- Put your cursor on the line that calls ContactPersonManager.GetItem(29, true) and press F9. This sets a breakpoint in the code window. When the code executes, it’ll stop here.
- Hit F5 to start debugging your application. As soon as the code hits the line above, execution will halt. Notice that the actual line with the breakpoint hasn’t executed yet, so you’ll need to press F10 to execute it.
- Next, right-click the myContactPerson variable and choose Add Watch. The variable is added to the Watch window where it looks like this:

Figure 4 – The Watch Window for the ContactPerson
Notice how I expanded the Addresses and PhoneNumbers collection one level deep, while I also expanded the first EmailAddress in the collection, showing its full details. You may wonder how it’s possible that you see these details in the Watch window, instead of the default Namespace.ClassName naming scheme that Visual Studio usually uses for custom types. The full details are made available by a DebuggerDisplay attribute on the respective classes. For example, the attribute for the Address class looks like this:
[
DebuggerDisplay("Address: {Street, nq} {HouseNumber, nq}
{City, nq} - {Country, nq} ({Type})")
]
public class Address
{
Within the DebuggerDisplay’s constructor you specify a string with placeholders, each one wrapped in a pair of curly brackets. At debug time, these placeholders are replaced with their run-time values. In this example, the Address object displays its Street, Housenumber, City and Country properties and displays its type in parentheses. The nq inside the placeholders stands for no quote and is used to remove the quotes from string variables. Take a look at this article for a detailed examination of the DebuggerDisplay attribute.
While it’s certainly useful to manage your contact persons and their contact data programmatically, this isn’t always the easiest way to deal with it. It would be much easier if you’d be able to drag and drop a number of controls on a web page, set some properties using wizards, property grids and tasks panes and then be able to manage your contacts in a web browser.
This is exactly what part three of this article series will show you. You’ll see how to create pages that display a list of contact persons. This lists is retrieved by calling GetList() on the ContactPersonManager class through an ObjectDataSource control. Each contact person in the list will be updatable and obviously, you’ll be able to add new contact persons as well. You’ll also be able to add, edit and delete the contact records for each contact person.
Summary
This article showed you how to implement your own business objects in .NET. You saw how to build the classes in the Business Objects and Logic Layers and how to write code for data access in the Data Access Layer. The four classes in the business layer, ContactPersonManager, AddressManager, EmailAddressManager and PhoneNumberManager can be used to manage your contact persons and their contact data in any type of application as they are not specific to ASP.NET.
First I showed you how most of the class files in the application are laid out, with a #region for each important section of the class. Then you saw how to write the private and public properties of the classes in the business objects layer. Most of the article was spent on discussing the inner workings of the methods in the classes that are responsible for getting data from the database and for inserting, updating and deleting existing records.
At the end of the article you saw a quick example of how you can use these classes programmatically.
Since a programmatic way to manage your contact persons isn’t often the easiest solution, part three of this article series will show you how to use the classes in a web application that mostly uses declarative databinding.
Download Files
- Source Code of the Demo Application in C#. Comes with scripts to create the database
- Source Code of the Demo Application in Visual Basic .NET. Comes with scripts to create the database
- The MyGeneration templates for the Bll, BO and Dal classes in C#
- The MyGeneration templates for the Bll, BO and Dal classes in VB.NET
Building Layered Web Applications with Microsoft ASP.NET 2.0 – Part 3
Introduction
Before we dig into the code for the web site, let’s briefly recap the application’s design I showed you in the two previous articles. The following figure shows the four main components of the application:

Figure 1 – The Four Main Components of the Contact Person Manager Application
Business Objects (BO)
First, there are a number of business objects that live in the Spaanjaars.ContactManager.BO namespace, indicated by the rectangle on the right. The classes used for these objects are the ContactPerson, the Address, the EmailAddress and the PhoneNumber. They don’t have any behavior, and can therefore be considered as “dumb” objects. All they can do, is hold and expose data through their public properties. Additionally, each BO object has a List counterpart, like ContactPersonList, EmailAddressList and so on. These lists inherit from a generics list, like List<ContactPerson> and are used to hold collections of the business objects.
Each of the other three components of the application has a reference to the objects in the Business Objects layer. This means that the web site can consume objects like ContactPerson that are returned from the business layer that in turn got them from the data access layer.
Business Logic Layer (Bll)
In the middle of the diagram, you see the Business Logic Layer; the bridge between the web site and the data access layer. The Bll gets instructions from the presentation layer (the web site in this example), to carry out tasks, like retrieving items from the data layer, or sending changed objects back into this layer. Additionally, it can perform tasks like enforcing security and carrying out validation, as you saw in part two of this article series.
Data Access Layer (Dal)
The data access layer contains the code that directly interacts with the data source, a SQL Server database in the case of the Contact Person Manager application but this could be any other kind of data source, like text or XML files, Access, Oracle, DB2 databases and any other data source you can come up with.
Presentation Layer
At the top of the diagram, you see the Web Site, the presentation layer of this application. It’s the web site and the pages and code it contains that is the main subject of this article, as you have already seen the other three parts in the previous two articles.
Building the Web Site
In part two of the article series I showed you how the site was set up: the important layers each have a separate folder under the special App_Code folder. Besides that, the site contains two .aspx pages: Default.aspx and AddEditContactPerson.aspx.
The first one lists all the contact persons in the system and allows you to manage their contact records through GridView and FormView controls on the page. The AddEditContactPerson.aspx page allows you to create a new or change an existing contact person in the system.
I’ll start by a quick introduction of the setup of the site (web.config, themes, styles and so on) followed by an explanation of the markup and code in Default.aspx and AddEditContactPerson.aspx.
Setup of the Site
In part two you saw the Solution Explorer of the application mainly showing the files in the App_Code folder. Obviously, the entire site contains more files, depicted in Figure 2:

Figure 2 – The Solution Explorer for the Contact Person Manager Application
Besides the now familiar folders under App_Code, the site has a few other folders and files worth looking at.
App_Data and NLayer.mdf
This is the SQL Server 2005 Express database used for the application. In the Download for this application you also find the SQL Scripts to recreate the database on SQL Server 2000 or SQL Server 2005.
App_Themes and Css\Styles.css
The App_Themes folder contains a single theme called Default which in turn holds a single skin file that is used to change the appearance of the GridViews used in the application. Instead of styling each individual GridView used in the Default.aspx page (four in total), I created a .skin file that changes the styling of elements site-wide. Take a look at the contents of GridView.skin to see how this works:
<asp:GridView runat="server" CellPadding="4" GridLines="None"> <FooterStyle CssClass="FooterStyle" /> <RowStyle CssClass="RowStyle" /> <SelectedRowStyle CssClass="SelectedRowStyle" /> <PagerStyle CssClass="PagerStyle" /> <HeaderStyle CssClass="HeaderStyle" /> <AlternatingRowStyle CssClass="AlternatingRowStyle" /> </asp:GridView>
Each important *Style element has its CssClass set to a CSS class defined in the file Styles.css. When the page renders, these CssClass attributes are translated to HTML class attributes for the relevant HTML elements. For example, each GridView header gets a HeaderStyle class set like this:
<tr class="HeaderStyle">
The HeaderStyle class in the CSS file then changes the appearance of the header to bold white letters on a blue background:
.HeaderStyle
{
background-color: #507CD1;
color: #fff;
font-weight: bold;
}
This way, all the GridViews in the site have the exact same appearance. More importantly, when you want to change the appearance, you now need to do this in only one location: the Styles.css file. The web.config (discussed next) contains a setting that applies the Default theme to all pages in the site.
Web.config
The web.config file is mostly a standard config file, with only two important settings. The <connectionStrings> element and the <pages /> element. Under the <connectionStrings> node you find two connection strings, with one being commented out, The first contains a connection string that points to a local SQL Server Express database with the database in App_Data, while the other points to a database on a commercial version of SQL Server 2000 or 2005. Refer to this article about configuring SQL Server 2000 or 2005 if you need more information about using this second connection string.
The <pages /> element contains a single attribute that tells ASP.NET to apply the Default theme to all pages in the site:
<pages theme="Default" />
If you remove the theme attribute, you’ll see that the GridView controls in the site will return to their default layout.
With the additional files in the site looked at, let’s take a look at how you can display contact persons on a page.
Displaying all Contact Persons in the System
Because much of the work has already been done by writing the code in the BO, Bll and Dal layers, displaying a list of contact persons is now super easy. To display a list of users in a GridView, follow these 7 steps:
- Create a new page and switch to Design View. Add a GridView to the page by dragging it from the Toolbox on the design surface.
- Open the GridView’s Smart Task panel and under Choose Data Source select <New data source>.
- In the Data Source Configuration Wizard, Select Object and click OK.
- In the Choose a Business Object, make sure Show only data components is checked and then choose the appropriate business object from the list. In my case, I chose: Spaanjaars.ContactManager.Bll.ContactPersonManager:
Figure 3 – The Configure Data Source Wizard
If you want to know how I was able to limit the items in the drop down to my business objects in the App_Code folder only, and what the meaning of the [DataObjectAttribute()] on the classes in the business layer is, then be sure to check out this article about using attributes in your code . In short, [DataObjectAttribute()] signals to the designer that your class can be used in the Object Data Source wizard. In addition, you can mark methods in these classes with the [DataObjectMethod()] attribute. This attribute is used to mark any method as the default Select, Insert, Update or Delete method, like this:
[DataObjectMethod(DataObjectMethodType.Select, true)]
public static AddressList GetList(int contactPersonId)
{
return AddressDB.GetList(contactPersonId);
}
This marks the GetItem method as the default Select method of the AddressList class (due to the true argument in the attribute’s constructor). Since the Save method is used for both Insert and Update statements, ideally I’d like to apply the attribute with the default value twice: for both DataObjectMethodType.Insert and DataObjectMethodType.Update. This, unfortunately, doesn’t work. If you know a work around, please let me know . So, instead, I decided to mark the Save method as the default for Update. This means that whenever you need to select an Insert method, you’ll need to select one manually. If you don’t like this, then you can create two public methods, called Insert and Update for example, and then have both of them call Save.
- Next, on the Define Data Methods window, make sure that on the Select tab the GetList() method is selected (it was already preselected due to the DataObjectMethodType attribute). Next, clear the selection on the Update tab as you don’t need an Update method at this stage. You can leave the Insert and Delete tabs to their defaults which means there’s no Insert method selected while Delete points to Delete(ContactPerson myContactPerson) .
- Finally, click Finish to close the wizard.
You should end up with the following markup for the control:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" DataObjectTypeName="Spaanjaars.ContactManager.BO.ContactPerson" DeleteMethod="Delete" SelectMethod="GetList" TypeName="Spaanjaars.ContactManager.Bll.ContactPersonManager"> </asp:ObjectDataSource>
(I removed the OldValuesParameterFormatString attribute, as it’s not needed, but added by default).
As soon as you finish the Object Data Source wizard, the GridView refreshes to show the relevant columns from the business object that the ObjectDataSource returns:

Figure 4 – The GridView and its Associated ObjectDataSource Control
Notice how the GridView displays the data for a ContactPerson object from the BO namespace. The ObjectDataSource wizard was smart enough to figure out that although I am using the ContactPersonManager as my methods class, this Manager object returns instances of ContactPerson that I am working with.
That’s all for now. Just hit Ctrl+F5 in Visual Studio 2005 and you’ll see a list with contact persons appear:

Figure 5 – A List With Contact Persons
Not bad, don’t you think? 11 lines of code (the GridView and the ObjectDataSource together) are all that’s required to display a list in the presentation layer. Notice how the GridView is skinned automatically by the .skin file you saw earlier. For this to work, you also need to include the CSS file in the page, and enable the Theme in the web.config file. If you don’t have the theme set up correctly, your GridView will be quite plain, with black text on a white background.
Although the ASPX page only needs 11 lines of code, a lot of other code is executed under the hood. To get the data in the page, the following sequence of events takes place:
- The GridView sees it needs to display its data and requests the data from the ObjectDataSource (ODS) control.
- The ODS in turn sees it needs to call GetList on the ContactPersonManager class. Since this method is static, it is able to call it directly. If the method hadn’t been static, a new instance of the class would have been created first.
- GetList in ContactPersonManager forwards the call to GetList in the ContactPersonDB class in the DAL.
- This DAL method eventually fires a stored procedure in the database called sprocContactPersonSelectList that returns the records to the calling code.
- GetList in the DAL creates a new ContactPersonList (that derives from List<ContactPerson>) based on the records from the database and returns it to GetList in the business layer.
- That GetList forwards the list to the ODS.
- The ODS then hands over the list to the GridView which eventually displays all the contact persons on the page.
Obviously, just displaying the contact persons isn’t good enough. We also need to be able to Edit and Delete them as well as insert new ones.
Managing Contact Persons in the System
ASP.NET 2.0 offers many features that make it easy to work with data in a web page. You have the various DataSource controls to manage data, the GridView to display lists of records and the DetailsView and FormView controls to display a single record at the time, with insert and update capabilities.
However, I often find the FormView and DetailsView a bit problematic to work with, especially when you need to manage Business Objects with many properties. To fully support all data scenarios (Insert, Update and Display), these controls require you to create three separate templates, each with a number of controls bound to the data source. This can quickly lead to a lot of code that is hard to manage. It also leads to duplicate code which is even worse. In many circumstances, editing an object is identical to inserting a new one, so theoretically you should be able to reuse the templates for insert and update. So, for the Contact Person Manager application I’ll use a different approach to manage the contact persons. To show how things work, and because the contact data objects are easier to manage, I do use FormView controls to manage those objects.
However, before we look at that, let’s first look at how we can decide what contact person to edit or delete.
To give the user a way to edit or delete a contact person, I added two columns to the GridView: a standard ButtonField with a CommandName of Edit and a TemplateField with an embedded LinkButton. After I configured the fields in the Fields editor for the GridView, I ended up with the following code:
<asp:ButtonField CommandName="Edit" Text="Edit" />
<asp:TemplateField ShowHeader="False">
<ItemTemplate>
<asp:LinkButton ID="LinkButton1" runat="server"
CausesValidation="False" CommandName="Delete" Text="Delete"
OnClientClick="return confirm('Are you sure you want to
delete this contact person?');">
</asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>
These two fields end up as an Edit and a Delete link in the GridView. As you can see, both of the fields have their CommandName set. This way, when they are clicked, the GridView triggers its RowCommand event. I set up a RowCommand handler for the GridView that points to a method in the Code Behind:
<asp:GridView ID="gvContactPersons" runat="server" AutoGenerateColumns="False" DataSourceID="odsContactPersons" DataKeyNames="Id" OnRowCommand="gvContactPersons_RowCommand" AllowPaging="True" CellPadding="4" GridLines="None"> <Columns> .... </Columns> </asp:GridView>
The RowCommand event handler looks at the CommandName that is passed to it and then determines what to do:
protected void gvContactPersons_RowCommand(
object sender, GridViewCommandEventArgs e)
{
switch (e.CommandName.ToLower())
{
case "addresses":
..... Shown later
case "edit":
int rowIndex = Convert.ToInt32(e.CommandArgument);
int contactPersonId = Convert.ToInt32(
gvContactPersons.DataKeys[rowIndex].Value);
Response.Redirect(String.Format("AddEditContactPerson.aspx?Id={0}",
contactPersonId.ToString()));
break;
}
}
When the Edit link is clicked, the case block for edit fires. The index of the clicked row is available from e.CommandArgument so it’s easy to use that to retrieve the DataKey for the contact person. The DataKey is the unique ID of the contact person in the database and is used as the DataKeyNames for the GridView. This way, you can always retrieve the original ID of an item in a GridView. Once the ID is known, the code simply redirects to the AddEditContactPerson.aspx page that I’ll discuss in a minute.
Notice that you don’t see a Delete block in the RowCommand handler. That’s because deleting is done fully automatically by the LinkButton in the TemplateField that you saw earlier. The GridView knows it’s bound to a DataSource that supports deleting (because it has a DeleteMethod set) so whenever the Delete command is raised, the GridView automatically signals the ODS that it should delete the requested record. The ODS in turn then fires the shared Delete method on the ContactPersonManager class. The only tweak I made was convert a standard ButtonField to a TemplateField that contains a LinkButton with its CommandName set to Delete. By converting the ButtonField to a TemplateField, it’s much easier to add a confirmation message to the LinkButton, asking the user for confirmation before actually deleting the contact person:
<asp:TemplateField ShowHeader="False"> <ItemTemplate>
<asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="False"
CommandName="Delete" Text="Delete"
OnClientClick="return confirm('Are you sure you want to
delete this contact person?');">
</asp:LinkButton>
</ItemTemplate> </asp:TemplateField>
Finally, allowing a user to insert a new contact person is even simpler. At the top of the page, you find a button called btnNew that simply sends the user to AddEditContactPerson.aspx without passing any query string information. I’ll discuss this page next.
The AddEditContactPerson.aspx Page
The markup of this page is really simple. It contains a standard HTML <table> containing standard rows and cells that in turn contain a number of ASP.NET controls, like TextBox, Calendar, Button and a few validator controls. In Design View, the page looks like this:

Figure 6 – The Add and Edit Contact Person Page In Design View
It’s the code in the code behind that’s a lot more interesting to look at.
protected void Page_Load(object sender, EventArgs e)
{
if (Request.QueryString.Get("Id") != null)
{
contactPersonId = Convert.ToInt32(Request.QueryString.Get("Id"));
}
if (!Page.IsPostBack)
{
BindTypeDropDown();
if (contactPersonId > 0) // Edit an existing item
{
// Get person
ContactPerson myContactPerson = ContactPersonManager.GetItem(contactPersonId);
if (myContactPerson != null)
{
txtFirstName.Text = myContactPerson.FirstName;
txtMiddleName.Text = myContactPerson.MiddleName;
txtLastName.Text = myContactPerson.LastName;
calDateOfBirth.SelectedDate = myContactPerson.DateOfBirth;
if (lstType.Items.FindByText(myContactPerson.Type.ToString()) != null)
{
lstType.Items.FindByText(myContactPerson.Type.ToString()).Selected = true;
}
this.Title = "Edit " + myContactPerson.FullName;
}
}
else
{
this.Title = "Create new Contact Person";
}
}
}
When the page loads, the code checks the query string and sees if the ID of a contact person was passed in. If that’s the case, the contact person is fetched using ContactPersonManager.GetItem(contactPersonId). When the item is returned, the code accesses its public properties to fill the controls in the page, like txtFirstName and so on.
If there’s no query string, the page assumes a new contact person must be created so it leaves all fields empty.
In both cases, the Title property of the page is updated to reflect the action that is being carried out.
Take a look at the code that adds the items to the Type drop down. It uses some nifty code borrowed from the article Universal enumeration editor control on CodeProject.com :
private void BindTypeDropDown()
{
FieldInfo[] myEnumFields = typeof(PersonType).GetFields();
foreach (FieldInfo myField in myEnumFields)
{
if (!myField.IsSpecialName && myField.Name.ToLower() != "notset")
{
int myValue = (int)myField.GetValue(0);
lstType.Items.Add(new ListItem(myField.Name, myValue.ToString()));
}
}
}
This code loops over the items in the PersonType enum, skipping system items and the NotSet item and then inserts the remaining items in the DropDownList called lstType. This way, the drop down gets filled with the values from the enum type automatically. The good thing about this is you never have to worry about this list anymore. Whenever you remove or add items from the enum, this list is updated automatically.
The final piece of interesting code in this page is inside the Save button’s click handler. This code is fired when all the client side validators have done their work. The code for the handler looks like this:
protected void btnSave_Click(object sender, EventArgs e)
{
Page.Validate();
if (calDateOfBirth.SelectedDate == DateTime.MinValue)
{
valRequiredDateOfBirth.IsValid = false;
}
if (Page.IsValid)
{
ContactPerson myContactPerson;
if (contactPersonId > 0)
{
// Update existing item
myContactPerson = ContactPersonManager.GetItem(contactPersonId);
}
else
{
// Create a new ContactPerson
myContactPerson = new ContactPerson();
}
myContactPerson.FirstName = txtFirstName.Text;
myContactPerson.MiddleName = txtMiddleName.Text;
myContactPerson.LastName = txtLastName.Text;
myContactPerson.DateOfBirth = calDateOfBirth.SelectedDate;
myContactPerson.Type = (PersonType)Convert.ToInt32(lstType.SelectedValue);
ContactPersonManager.Save(myContactPerson);
EndEditing();
}
}
The page calls Page.Validate() first to ensure all controls check whether they are valid or not. It then checks the SelectedDate of the calendar and sets the IsValid property of the custom validator to false when no date has been selected. Note that the current implementation for the calendar isn’t very user-friendly for a birth date. Imagine your contact person is 35. This means you’ll need to click around 420 times (35 * 12) to get at the right month back in 1971. Instead, you could add an additional drop down with the years that could allow a user to quickly select the relevant year. As other alternatives, you could drop the entire Calendar and use three drop down instead for the year, month and day or use the new Calendar control from the Ajax Toolkit that features some cool ways to browse through the calendar. However, for the purpose of demonstrating n-layer design, the Calendar control is fine.
Once the date has been checked, the code determines whether we’re editing an existing item, or creating a new one. When contactPersonId is larger than zero, it means we’re editing an existing item. The code then retrieves this existing ContactPerson using ContactPersonManager.GetItem(contactPersonId) just as with the code in Page_Load. This is done to ensure you always get all the data for the ContactPerson. So, let’s say you have a property like CreateDate that you don’t want to update when you’re changing an existing item. If you’d create a brand new item using a default constructor and then set all the properties, this property might either default to DateTime.MinValue or get today’s date. But by retrieving the existing item from the database, and only overriding what has changed, you can leave the existing data in tact.
If we’re creating a new contact person, the code instantiates one using the object’s default constructor :
myContactPerson = new ContactPerson();
Regardless of whether we’re inserting or updating, in both cases all the public fields of the object are filled with the values from the web controls. At the end, the Save method on the ContactPersonManager is called which receives the ContactPerson instance. The ContactPersonManager forwards this object to the Save method in ContactPersonDB which eventually saves the object in the database and returns its new ID. You saw how this worked in the previous article in this series.
At the end, when the object was saved successfully, the code calls the custom method EndEditing() which simply sends the user back to the Default.aspx page.
You can see that with the Business Objects, Business Logic and Data Access Layers built, it’s quite easy to create web pages that allow users to manage objects in your system. You only need around 25 lines of code to save a contact person where most of the code contains straight forward web control to property copying.
However, in some cases, you don’t even need all of this code, as you can rely on ASP.NET 2.0’s data binding capabilities. In the next section, I’ll show you how you can use controls like the GridView and the FormView to allow a user to edit, add and delete contact data, like e-mail addresses, addresses and phone numbers. Just as in part two, I’ll only show you the code for the EmailAddress class, as Address and PhoneNumber are pretty similar.
Managing EmailAddress Objects for a ContactPerson
Earlier in this article, I showed you the code for the GridView that displays the contact persons. You saw how I used a number of BoundFields to display ContactPerson properties and how I used a ButtonField and a TemplateField to enable a user to edit or delete a contact person. Besides these two columns I added three more ButtonField columns, to allow a user to select the addresses, e-mail addresses and phone numbers respectively.
<asp:ButtonField CommandName="Addresses" Text="Addresses" /> <asp:ButtonField CommandName="EmailAddresses" Text="Email" /> <asp:ButtonField CommandName="PhoneNumbers" Text="Phonenumbers" />
When the user clicks one of the columns, the GridView fires its RowCommand that you saw before:
protected void gvContactPersons_RowCommand(
object sender, GridViewCommandEventArgs e)
{
switch (e.CommandName.ToLower())
{
case "addresses":
gvContactPersons.SelectedIndex = Convert.ToInt32(e.CommandArgument);
MultiView1.ActiveViewIndex = 0;
break;
case "emailaddresses":
gvContactPersons.SelectedIndex = Convert.ToInt32(e.CommandArgument);
MultiView1.ActiveViewIndex = 1;
break;
case "phonenumbers":
gvContactPersons.SelectedIndex = Convert.ToInt32(e.CommandArgument);
MultiView1.ActiveViewIndex = 2;
break;
..... } }
In each of the three case blocks, the code sets the SelectedIndex of the record that was just clicked. It then displays one of the Views in the MultiView control. To make it a bit easier to hide or show anything related to an e-mail address, an address or a phone number, I wrapped all the functionality in a number of different views. Showing, say, the phone number list, or the FormView to insert a new one is now as easy as showing a specific View.
Inside each view, you find five important controls:
| Control | Description |
| GridView | The GridView is used to display the contact data, like addresses or phone numbers. |
| FormView | The FormView control is used to insert new items to the data store. |
| ObjectDataSource | The ObjectDataSource is used to display, insert, update and delete the contact records. Inserting is done with the FormView, while displaying, updating and deleting is done by the GridView control |
| Two Button controls | For each contact data type, there are two buttons: an “Add New” and a “Show List” button. To make it easier for a user to focus on a task, these buttons either show or hide the GridView and the FormView, so only one is visible at any given time. |
When the View for the e-mail addresses becomes visible, the GridView knows it must display its data and tells the ObjectDataSource to get a list of EmailAddress objects from the EmailAddressManager class by calling GetList. Recall from part two that this method requires the ID of the contact person. The ObjectDataSource gets this ID by looking at the SelectedValue property of the GridView, that was set in the RowCommand you just saw.
<asp:ObjectDataSource ID="odsEmailAddresses" runat="server"
DataObjectTypeName="Spaanjaars.ContactManager.BO.EmailAddress"
DeleteMethod="Delete" InsertMethod="Save" SelectMethod="GetList"
TypeName="Spaanjaars.ContactManager.Bll.EmailAddressManager"
UpdateMethod="Save">
<SelectParameters>
<asp:ControlParameter ControlID="gvContactPersons"
Name="contactPersonId" PropertyName="SelectedValue" Type="Int32" />
</SelectParameters>
</asp:ObjectDataSource>
Finally, if the selected contact person had any e-mail addresses, they are displayed in the GridView using standard BoundFields.
Much of the editing of the EmailAddress objects is done automatically by the standard controls. For example, the Delete column will automatically trigger the Delete method of the ObjectDataSource. Similarly, when you update an item in the GridView, the Save method of EmailAddressManager is called automatically. This method then gets a reference to the EmailAddress that is being edited, so first the BLL and then finally the DAL knows what record to update.
However, there are a few events that are triggered in the code behind that contain some interesting code. If you look in the code behind, you see the following regions and the fv_ItemInserting method:

Figure 7 – The Collapsed Code for the Default.aspx Page
fv_ItemInserting is the event handler for all three FormView controls in the page and handles their ItemInserting event. I’ll get to that a little later.
Each of the regions contains similar code that responds to button clicks and other events to show or hide the GridView or the FormView. It also features an ItemCommand handler that shows the GridView again whenever the user clicks the Cancel link on the FormView to cancel an insert operation:
protected void fvEmailAddress_ItemCommand(
object sender, FormViewCommandEventArgs e)
{
switch (e.CommandName.ToLower())
{
case "cancel":
ShowEmailAddressList();
break;
}
}
Other than that, the code in the different regions isn’t very exciting.
A lot more interesting to look at is the ItemInserting even handler. This handler is set up for all three FormView controls like this:
<asp:FormView ID="fvEmailAddress" runat="server" DataKeyNames="Id"
DataSourceID="odsEmailAddresses" DefaultMode="Insert" Visible="false"
OnItemInserting="fv_ItemInserting"
OnItemCommand="fvEmailAddress_ItemCommand"
OnItemInserted="fvEmailAddress_ItemInserted" EnableViewState="False">
<InsertItemTemplate>
...
</InsertItemTemplate>
</asp:FormView>
Since all three FormView controls fire the same event and all of them need to perform the same action, all three are bound to the same event handler. The FormView control fires this event when it is about to send its data to the ObjectDataSource. It’s a perfect location to look at the data that the user entered, modify it, or add any data to it. In our case, we need to set the ContactPersonId as this ID is not entered by the user, but is retrieved from the contact person GridView instead:
protected void fv_ItemInserting(object sender, FormViewInsertEventArgs e)
{
e.Values["ContactPersonId"] =
Convert.ToInt32(gvContactPersons.SelectedDataKey.Value);
}
When the code in this event runs, you get all the available properties of the business object in a dictionary exposed by e.Values. Adding new or changing existing properties is as easy as indexing the Values collection. So, for example, if you wanted to enforce that the Type of the EmailAddress was ContactType.Business regardless of what the user has entered, you could use the following code:
e.Values["Type"] = ContactType.Business;
In the sample application though, I am just setting the ContactPersonId that I get from the contact persons GridView by accessing its SelectedDataKey and casting it to an int.
To clarify the process that takes place when a user enters a new e-mail address, here’s a detailed description of all the steps:
- The user loads the list with contact persons and then clicks the Email link to display existing e-mail addresses (if any).
- The page reloads and the existing e-mail addresses are shown.
- The user clicks the Create new Email Address button, the GridView disappears and the FormView is shown so the user can enter a new e-mail address:

Figure 8 – Part of the Page that Allows a User to Enter a New E-mail Address - The user enters a new address, chooses a type and then clicks the Insert link.
- The ItemInserting event of the FormView control is triggered. The code in the event handler retrieves the ID of the selected contact person and assigns it to the ContactPersonId key of the Values dictionary.
- A new instance of the EmailAddress class is created and each of the objects’s properties are filled with the corresponding values in the Dictionary object properties (Email, Type and ContactPersonId).
- Next, the Save method of the EmailAddressManager class is called. This method receives the EmailAddress instance created in the previous step.
- As you saw before, this Save method forwards the EmailAddress object to the DAL.
- The DAL saves the EmailAddress in the database and returns the new ID of the e-mail address to the calling code.
- The Save method in the business layer assigns the new ID returned from the data access layer to the Id property of the EmailAddress object and then returns that ID.
- Finally, the FormView control fires its ItemInserted event. This method simply calls ShowEmailAddressList() that hides the FormView and displays the GridView again that now shows the newly inserted e-mail address.
That seems like a lot of steps for something as simple as inserting an e-mail address in the database. Remember, though, that most of these steps take place in the framework that was developed earlier. In fact, from an ASPX page perspective, all you need are the controls and the code in the ItemInserting event. All the other event handlers are just there to improve the user’s experience, by showing or hiding the relevant controls.
Similar processes are executed when you either edit or delete an item in the GridView. When you edit an item, eventually the Save methods in the BLL and DAL are called, while obviously Delete is called when you delete an item from the list.
That’s about all there is to it. With the entire business and data access layer built, managing your objects in a web form is often as simple as dragging and dropping a few controls and writing a little bit of code for some relevant event handlers.
Wrapping it Up
Obviously, the code and the pages you have seen in this article series represent a simplified version of a real world application. But, simplified as it may be, it still represents real-world concepts. The layer architecture, object design, object and method implementation, page design (controls and code behind) are all concepts that can be mapped directly to your own applications.
To limit the size of this article series, I left out a few detail implementations of techniques you would otherwise include in your code:
Validation
In part two I showed you how to implement validation in your business objects, This way, the business layer becomes responsible for checking the data that is sent to the database, and guarding the data integrity. This is crucial in real-world applications.
In addition to checking data at the business layer and optionally throwing exceptions when the data doesn’t meet the standards, your presentation layer should also check the data to help the user input correct data in the first place. The various validator controls (RequiredFieldValidator, RangeValidator, CustomValidator and so on) are indispensable tools in validating user input both at the client and the server. You saw how the AddEditContactPerson.aspx page used a number of these controls to validate the input. You can use the same controls in the EditItemTemplate of the GridView columns to validate user input in the GridView as well.
Error Handling
Other than the TransactionScope object to roll back any open transaction whenever an error occurs when saving a contact person, the code has no error handling. In a real-world app, you could wrap the code in a try / catch block and log the error in a database or text file or send it by e-mail. Alternatively, you’d allow all exceptions to bubble up to code in Global.asax and handle it here, or deploy ELMAH to handle and log all errors in your system in a consistent way.
Summary
This article showed you how to use the business objects that were designed and created in parts one and two of this article series.
The article started off with a quick description of the structure of the site. You saw how the pages were organized, how the site uses a Theme and a Skin to ensure a consistent look of some of the controls in the site, and you saw how the connection string for the application is stored and accessed at run-time.
You then saw how to configure an ObjectDataSource control to allow a user to display and delete contact persons and you saw how to modify the GridView to include an Edit link.
The article then discussed a way to create new and update existing ContactPerson objects using a separate page.
Next, I showed you how to use the FormView control to insert new contact data records. Although the FormView and GridView controls become hard to use for more complex business objects, they can be great for relatively simple objects like EmailAddress or PhoneNumber.
The article ended with a brief discussion of the topics that were *not* discussed in this article, including extended validation and error handling.
If there are any topics that you feel should be included in this article series, send me a message and I’ll take a look at your request.
Download Files
- Source Code of the Demo Application in C#. Comes with scripts to create the database
- Source Code of the Demo Application in Visual Basic .NET. Comes with scripts to create the database
- The MyGeneration templates for the Bll, BO and Dal classes in C#
- The MyGeneration templates for the Bll, BO and Dal classes in VB.NET


