The Stratifier - 30 Oct 2009

Layering ASP.NET Application Code

Dan Wahlin

October 30, 2009

17 Min Read
ITPro Today logo in a gray background | ITPro Today

CoverStory

LANGUAGES:C#

ASP.NETVERSIONS: 2.0

 

The Stratifier

Layering ASP.NET Application Code

 

By Dan Wahlin

 

It seems like more and more is being asked of developersthese days, and more and more we are expected to be coding superheroes.Although development platforms are becoming increasingly productive, moreapplications are connecting to legacy or external systems and data sources,resulting in additional complexities. This increase in complexity results inmore failure points and more spaghetti code. Added complexity requires thatcode be divided properly so it can more easily be re-used, maintained, andmanaged.

 

In this article I ll focus on the fundamentals of creatingn-tier/n-layer ASP.NET applications, as well as discuss the pros and cons of separatingyour code into distinct tiers or layers. Throughout the article you ll see howto create applications that scale well, promote code re-use, and easemaintenance headaches, so you, too, can become a coding superhero.

 

N-tier versus N-layer

N-tier is a phrase most seasoned developers have heard,but it has multiple meanings depending on to whom you talk. In this article I lldefine n-tier as, A software architecture methodology that defines distinctphysical or process boundaries between application modules. N-tierarchitectures typically break down tiers into presentation, business, and datatiers, although other tiers and patterns may certainly be used. Figure 1 showsan example of a standard n-tier architecture.

 

Figure 1: N-tier architectures dividecode into physical tiers to promote code-reuse.

 

N-tier architectures have several advantages, includingbetter code re-use, easier maintenance, enhanced scalability, and faultisolation. However, there are disadvantages as well, such as additional failurepoints in the application and added complexity as code makes calls betweenmultiple machines on the network.

 

While the term n-tier is certainly relevant for manyapplications, I prefer to minimize failure points as much as possible and keepthings simple. As a result, I subscribe to the n-layer approach, where code isdivided into distinct layers (presentation, business, and data for example),but is not separated by physical or process boundaries unless absolutelynecessary. Doing this keeps things simple and preserves the principles ofcode-reuse, easier maintenance, and so on. Figure 2 shows an example of ann-layer architecture. Notice that the presentation, business, and data classesare all located on the Web server, but are logically separated.

 

Figure 2: N-layer architecturesorganize code into logical modules to promote code-reuse and minimize failurepoints.

 

There are two main ways to create n-layer applicationsusing Visual Studio .NET 2005 and ASP.NET 2.0 (see Figure 3). The first involvesthe App_Code folder available in ASP.NET 2.0. The code-behind files in the Website contain the presentation logic; the business and data classes are placedin App_Code. Although this is the simplest solution, it doesn t make thebusiness and data classes as re-useable as they could be.

 

Figure 3: Visual Studio .NET 2005provides two main ways for logically separating code. Separate projects can becreated for each layer, or layers can be added into the App_Code folder.

 

In cases where business and data classes will be re-usedacross several applications, separate VS.NET 2005 class library projects can becreated to separate the business and data classes into unique dlls. Doing thisallows the classes to be given a strong-name (sn.exe) and installed in the GACon the Web server (gacutil.exe). The downloadable code for this articledemonstrates using both techniques, although the remainder of the article willfocus on using the App_Code folder.

 

So why would you want to take the time to separate yourapplication logic into separate layers? After all, putting it all directly intoyour code-behind file works fine, right? First, by creating separate codelayers, code can more easily be re-used across one or more applications. If youput all your logic directly into code-behind pages, then only that page can useit without resorting to hacks. Second, by layering, code maintenance becomesmuch easier. If there s a bug in the business logic, you know where to startlooking. If you ve ever waded through someone else s spaghetti code, you knowwhat I m talking about. Finally, by layering code you can minimize the amountof code in the actual Web pages by using controls, such as the newObjectDataSource control, to bind layers together.

 

Steps to Create an N-layer Application

You ll probably want to jump right into coding afterbusiness requirements have been gathered for an application. After all, where sthe fun in planning? Although coding is fun, before creating an n-layerapplication you ll want to take the time to plan what business rules yourapplication has, how data will be stored and organized, how data will be passedbetween layers, etc., before going too far. Taking the time to architect and specout the application up front can save you a lot of time down the road. Byarchitecting the application first you ll know how each layer (presentation,business, and data) will be organized, and what they will contain. Althoughapplication architecture is beyond the scope of this article, you ll find manygreat examples at MSDN s Patterns and Practices Web site (http://msdn.microsoft.com/practices).

 

Once an application s architecture is finished, I like totake a top-down approach to building the code. These are the steps I normallyfollow:

  • Create the database (tables, log tables, views, triggers, and stored procedures).

  • Create the model layer (data entity classes that will be filled with data and passed between layers; more on this later).

  • Create the data layer (data access classes).

  • Create the business layer (business rules and data processing classes).

  • Create the presentation layer (Web pages and code-behind pages that consume and gather data).

 

Once the database is created, the steps that follow cancertainly be divided among different people if you re working with a team. Let stake a more detailed look at the steps involved in creating the model, data,business, and presentation layers.

 

The Model Layer

The model layer contains data entity classes that have fieldsand properties defined in them, but typically have no methods. These classesare normally filled with data in the data layer and then passed to the businessand presentation layers. Think of them as the glue between all the layers.Figure 4 shows a portion of a model layer class named Customer. Although thisclass can be created by hand, I used the xsd.exe command-line utility togenerate the class automatically. This was done by adding an empty XML Schemafile in the Web site and then dragging the Customers table (from the Northwinddatabase) onto the schema design surface from the Server Explorer. I then ranthe following syntax using the Visual Studio Command Prompt to generate theclass and place it in a namespace of Model.

 

xsd /classes /namespace:Model Customer.xsd

namespace Model {

   public partial classCustomer {

       private stringcustomerIDField;

       private stringcompanyNameField;

       private stringcontactNameField;

       private stringcontactTitleField;

       private stringaddressField;

       private stringcityField;

       private stringregionField;

       private stringpostalCodeField;

       private stringcountryField;

       private stringphoneField;

       private stringfaxField;

       public stringCustomerID {

           get {

               returnthis.customerIDField;

           }

           set {

               this.customerIDField = value;

           }

       }

       public stringCompanyName {

           get {

               returnthis.companyNameField;

           }

           set {

               this.companyNameField = value;

           }

       }

       //Additionalproperties would follow

   }

}

Figure 4: TheModel.Customer class is used to hold data that is passed between other layers.

 

When using the App_Code folder to create an n-layerapplication, I like to create a folder for each layer. The Customer class shownin Figure 4 is placed in the Model folder (again, refer to Figure 3).Alternatively, you may prefer to use strongly-typed DataSets to pass databetween layers rather than creating custom classes. I normally place mystrongly-typed DataSets in the model layer. However, because .NET version 2offers TableAdapters that can access the database and act like the data layer,you may feel more comfortable placing them in the data layer. Although I won tdiscuss strongly-typed DataSets in this article, the downloadable code containsexamples of using them within n-layer applications.

 

The Data Layer

The data layer has a very simple (yet important) job;perform select, insert, update, and delete operations against the database. Itcan also be used to perform those types of operations against a Web service.When performing select operations, it is responsible for creating a model layerobject, filling it with data, and passing it to the business layer. Whenperforming insert, update, or delete operations, it will be passed a modellayer object to use as appropriate for the operations. ListingOne shows an example of a data layer class named DAL that lives in the Datanamespace. The class selects a customer s data from the Northwind database,instantiates a Model.Customer class, fills it with data, and returns it to thecaller. The data layer is responsible for performing database operations andpassing Model objects back to the business layer. It is not responsible forperforming any business rules.

 

Looking through the code in Listing One you can see thatthe GetCustomer method accepts a customer ID parameter, which it uses to querythe database. This method uses the new .NET version 2.0 DbProviderFactory classto create a generic DbConnection object that can be used against SQL Server,Oracle, and many other databases. The records are streamed from the databaseusing the DbDataReader class, which is then passed to a method namedBuildCustomer that is responsible for creating the Model.Customer objectinstance, filling it with data, and returning it to GetCustomer. GetCustomerthen forwards it to the calling business layer code.

 

The Business Layer

The business layer acts as the middleman between thepresentation and data layers. It is responsible for processing data andapplying business rules. Figure 5 shows an example of a business layer classnamed BAL (business access layer) that performs a few simple business rulesbefore a customer record is updated by the data layer. In the real world you llmore than likely give your business layer classes more descriptive namesbecause multiple classes may exist in an application.

 

namespace Biz

{

 public class BAL

 {

   public static boolUpdateCustomer(Customer cust)

   {

       Customer newCust =ApplyBusinessRules(cust);

       returnData.DAL.UpdateCustomer(newCust);

   }

   private static CustomerApplyBusinessRules(Customer cust)

   {

       //Business rulesays that all sales titles must

       //be stored as"Sales Staff" in database

       if(cust.ContactTitle.ToLower().Contains("sales"))

       {

           cust.ContactTitle = "Sales Staff";

       }

       if(String.IsNullOrEmpty(cust.Region))

       {

           cust.Region ="No Region";

       }

       cust.Phone =FixPhone(cust.Phone);

       cust.Fax =FixPhone(cust.Fax);

       return cust;

   }

   private static stringFixPhone(string phone)

   {

       phone =phone.Replace("(", String.Empty);

       phone =phone.Replace(")", String.Empty);

       phone =phone.Replace("-", String.Empty);

       phone =phone.Replace(" ", String.Empty);

       return phone;

   }

 }

}

Figure 5: Thebusiness layer is responsible for performing business logic in the application.This example shows how rules are applied to customer data to change valuesbefore a customer record is updated.

 

While the business rules shown in Figure 5 could certainlybe performed in the presentation layer, by placing them into a separate layer(and namespace) they can be re-used by multiple pages or applications in caseswhere the business assembly is placed in the Global Assembly Cache.

 

There will be cases where no business rules need to beapplied to perform a particular operation. Should you create a business layermethod for the operation or have the presentation layer talk directly to thedata layer classes? In this situation I always create a business layer methodas it allows for adding business rules in the future. Having a business layeralso isolates the presentation layer from the data layer. If the databaseback-end is switched out, the presentation layer won t know the differencebecause it only communicates directly with the business layer and never talkswith the data layer directly.

 

Figure 6 shows a simple method named GetCustomer thatcalls the Data.DAL.GetCustomer method shown in Listing One. It doesn t performany business rules functionality and simply acts as a pass-through in thiscase.

 

namespace Biz

{

 public class BAL

 {

   public staticModel.Customer GetCustomer(string custID)

   {

     returnData.DAL.GetCustomer(custID);

   }

 }

}

Figure 6: Businesslayer methods may act as pass-through objects in cases where no business rulesneed to be applied to an operation.

 

The Presentation Layer

The presentation layer is where many applications writtenin the not so distant past place the majority of their code. Using the n-layerapproach, combined with code-behind files, spaghetti code can be greatlyminimized - resulting in easier to maintain and less bug infested applications.It can be challenging to know which code should go in the presentation layerand which code should go in the business layer. My general rule is that if thecode directly manipulates a control, it should go in the presentation layer. Ifthe code applies a rule to data, it should go in the business layer. Therealways are, of course, exceptions to the rule.

 

While you could certainly write VB.NET or C# code to tiethe presentation layer to the business layer, the new ObjectDataSource controlminimizes the amount of code you need to write. Figure 7 shows code from anASP.NET Web Form that uses the ObjectDataSource control to call a businesslayer object and bind data to a DetailsView control.

 

 DataSourceID="odsCustomer" AutoGenerateRows="False"  DataKeyNames="CustomerID">                        HorizontalAlign="Center" />             ForeColor="White" />                          HeaderText="CustomerID" SortExpression="CustomerID"/>                  HeaderText="ContactName"SortExpression="ContactName" />                    

 DataObjectTypeName="Model.Customer"  SelectMethod="GetCustomer" TypeName="Biz.BAL"  UpdateMethod="UpdateCustomer"  OnUpdated="odsCustomer_Updated">                       PropertyName="SelectedValue" Type="String" />    

Figure 7: TheObjectDataSource control can be used to call business layer objects (or anyobjects for that matter) with a minimal amount of code.

 

This code uses the ObjectDataSource control to call theBiz.BAL class GetCustomer method and return a Model.Customer object. It grabsthe customer ID value to use in the query from a GridView control by using theasp:ControlParameter control. Several different parameter controls areavailable - such as session, querystring, and form - for retrieving data topass to business layer methods.

 

The DataView control is associated with theObjectDataSource control by using the DataSourceID property. Once theObjectDataSource control retrieves the Model.Customer object it isautomatically bound to the DetailsView control without writing any VB.NET or C#code. Figure 8 shows the output generated by the n-layer application availablewith this article s accompanying downloadable code.

 


Figure 8: Output generated by then-layer application.

 

Conclusion

In this article you ve seen how code can be divided intolayers to promote better code re-use and maintenance. There are many differentways to architect and organize code in ASP.NET applications; it s recommendedyou study the various code patterns available before deciding what is best foryour project. Regardless of which pattern you choose, by organizing code intolayers you ll certainly reap many benefits down the road.

 

The source code accompanying this article is available fordownload.

 

Dan Wahlin(Microsoft Most Valuable Professional for ASP.NET and XML Web services) is a.NET development instructor at Interface Technical Training (http://www.interfacett.com/). Dan foundedthe XML for ASP.NET Developers Web site (http://www.xmlforasp.net/),which focuses on using XML, ADO.NET, and Web services in Microsoft s .NETplatform. He s also on the INETA Speaker s Bureau and speaks at severalconferences. Dan co-authored Professional Windows DNA (Wrox), ASP.NET: Tips,Tutorials, and Code (SAMS), ASP.NET 1.1 Insider Solutions (SAMS), and ASP.NET2.0 MVP Hacks (Wrox), and authored XML for ASP.NET Developers (SAMS). When he snot writing code, articles, or books, Dan enjoys writing and recording musicand playing golf and basketball with his wife and kids. He recently wrote andrecorded a new song with Spike Xavier called No More DLL Hell , which can bedownloaded from http://www.interfacett.com/dllhell.

 

Coding Conventions

Based on some positive feedback and questions about thisarticle, I thought I d go into a little more detail about some practices thatcan be implemented when deciding on names for namespaces, classes, fields,methods, and events. Thanks to Robert Dannelly for sending some references andfor suggesting that I discuss best practices that developers should follow withregard to naming and casing. Whether you work on a team with several developersor are your own one man or woman team, starting a project with codingguidelines in mind can ease maintenance down the road and make applicationprogramming interfaces (APIs) more consistent.

 

Microsoft published an excellent document that shouldserve as the starting point for any developer or company looking to establish aset of guidelines (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconcapitalizationstyles.asp).While flexibility and common sense need to be applied to any company documentthat provides coding guidelines and standards, the following table (provided inMicrosoft s document) serves as a good starting point:

 

Identifier

Case

Example

Class

Pascal

AppDomain

Enum type

Pascal

ErrorLevel

Enum values

Pascal

FatalError

Event

Pascal

ValueChange

Exception class

Pascal

WebException

Read-only Static field

Pascal

RedValue

Interface

Pascal

IDisposable

Method

Pascal

ToString

Namespace

Pascal

System.Drawing

Parameter

Camel

typeName

Property

Pascal

BackColor

Protected instance field

Camel

redValue

Note: Rarely used. A property is preferable to using a protected instance field.

Public instance field

Pascal

RedValue

Note: Rarely used. A property is preferable to using a public instance field.

 

I m a big believer in the guidelines listed in theprevious table, and like to use Pascal or Camel casing as indicated. Inaddition to casing guidelines, you may also want to enhance the guidelines evenmore by establishing naming guidelines. For example, if you work in the financedepartment for AcmeCorp and are writing data access classes for an applicationnamed Payroll , you might decide on the following namespace to hold yourrelated data classes:

 

namespace AcmeCorp.Finance.Payroll.Data

{

}

 

Classes that perform business rules functionality may gointo the following namespace:

 

namespace AcmeCorp.Finance.Payroll.Biz

{

}

 

Coming up with a naming convention for namespaces,classes, events, properties, and methods can help establish a consistentframework for teams that makes debugging other people s code a more pleasantexperience overall (not that debugging someone else s code is ever pleasant).In addition to the guidelines you ve seen to this point, there are a few othersI like to follow.

 

First, I like to prefix all private fields with anunderscore character. For example, to define a first name field I do thefollowing:

 

string _FirstName;

 

Adding the underscore allows me to instantly know when anobject is a private field and it matches up nicely with related propertystatements:

 

public string FirstName

{

    get { return_FirstName; }

    set { _FirstName =value; }

}

 

Other common naming practices that you ll see with the.NET Framework are delegate names. When defining a delegate you ll want to addthe word Handler on the end of it. So, if I have an event namedStageCompleted, I ll normally name my delegate StageCompletedHandler, and if Icreated a custom EventArgs class, I ll name it StageCompletedEventArgs:

 

public delegate void StageCompletedHandler(object sender,

 StageCompletedEventArgse);

 

There are a lot of other naming conventions that could becovered, but I ll leave that as an exercise for the reader since it really dependson your company s guidelines and, in some cases, personal preference. Afterall, should you name a textbox control txtFirstName, firstNameTextBox, orsomething else? I certainly have my preference (hint: shorter is better), butit s something you ll need to debate with your team or manager.

 

If your company doesn t have any guidelines I recommendputting together a simple document that outlines the standards. Publish it to aplace where all team members have access so everyone knows the expectations. Establishingcore coding guidelines will make your applications more consistent and easierto work with in the future.

 

Begin Listing One a data layer classnamed DAL

namespace Data

{

 public class DAL

 {

   public static CustomerGetCustomer(string custID)

   {

     DbConnection conn =null;

     DbDataReader reader =null;

     try

     {

       conn =GetConnection();

       DbCommand cmd =conn.CreateCommand();

       cmd.CommandText ="ap_GetCustomer";

       cmd.CommandType =CommandType.StoredProcedure;

       DbParameter param =cmd.CreateParameter();

       param.ParameterName= "@CustomerID";

       param.Value =custID;

       cmd.Parameters.Add(param);

       conn.Open();

       reader =cmd.ExecuteReader();

       Customer[] custs =BuildCustomer(reader);

       if (custs != null&& custs.Length > 0)

       {

           returncusts[0];

       }

     }

     catch (Exception exp)

     {

       HttpContext.Current.Trace.Warn("Error",

         "Error inGetCustomer: " + exp.Message);

     }

     finally

     {

      if (conn != null)conn.Close();

      if (reader != null)reader.Close();

     }

     return null;

   }

   //Re-useable routinethat maps a reader to the

   //Model.Customer classproperties

   public staticCustomer[] BuildCustomer(

    DbDataReader reader)

   {

     Listcusts = new List();

     if (reader != null&& reader.HasRows)

     {

       while(reader.Read())

       {

         Model.Customercust = new Model.Customer();

         cust.Address =reader["Address"].ToString();

         cust.City =reader["City"].ToString();

         cust.CompanyName=

          reader["CompanyName"].ToString();

         cust.ContactName=

          reader["ContactName"].ToString();

         cust.ContactTitle=

          reader["ContactTitle"].ToString();

         cust.Country =reader["Country"].ToString();

         cust.CustomerID =reader["CustomerID"].ToString();

         cust.Fax =reader["Fax"].ToString();

         cust.Phone =reader["Phone"].ToString();

         cust.PostalCode =reader["PostalCode"].ToString();

         cust.Region =reader["Region"].ToString();

         custs.Add(cust);

       }

     }

     returncusts.ToArray();

   }

   //Create genericdatabase connection object using new

   //DbProviderFactoriesclass

   public staticDbConnection GetConnection()

   {

     ConnectionStringSettings connStr =

       ConfigurationManager.ConnectionStrings[

        "NorthwindConnStr"];

     DbProviderFactory f =

       DbProviderFactories.GetFactory(

        connStr.ProviderName);

     DbConnection conn =f.CreateConnection();

     conn.ConnectionString= connStr.ConnectionString;

     return conn;

   }

 }

}

End Listing One

 

 

 

 

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like