Click here to Skip to main content
Click here to Skip to main content

Creating ASP.NET Applications with N-Tier Architecture

By , 20 Aug 2012
 

Introduction 

This article describes how to build ASP.NET applications using n-tier architecture. The benefits of having n-tier architecture is that all the modules having dedicated functionality will be independent of each other. Changing one tier will not effect other tiers and there is no single point of failure even if some tier is not working. 

Background 

In a typical n-tier application there will be 4 Layers. The bottom most layer is the Data layer which contains the tables and stored procedures, scaler function, table values function. This Data layer is typically the database engine itself. We will be using SqlServer as the data layer in our example.

On top of Data Layer, we have a Data Access Layer (DAL). This layer is responsible for handling Database related tasks i.e. only data access. This Data access layer is created as a separate solution so that the changes in DAL only need the recompilation of DAL and not the complete website. The benefit of having this layer as a separate solution is that in case the database engine is changes we only need to change the DAL and the other areas of the website need not be changed and recompiled. Also the changes in other areas outside this solution will not demand for DAL recompilation.

On top of DAL, we have our Business Logic Layer(BLL). BLL contains all the calculations and Business Rule validations that are required in the application. It is also in a separate solution for the reason that if the Business rules change or the calculations change we only need to recompile the BLL and the other layers of the application will remain unaffected.

Finally on top of BLL we have our Presentation Layer. The Presentation layer for an ASP.NET web forms application is all the Forms (apsx pages and their code behinds) and the classes contained in the App_Code folder. The Presentation layer is responsible for taking the user input, showing the data to the user and mainly performing input data validation.

Note: Input data filtration and validation is typically done at the Presentation Layer(Both client side and server side). The business Rule validation will be done at the BLL.

So to visualize the above mentioned architecture:

Note: The Data Access Layer in this article was written using classic ADO.NET, due to which the amount of code in DAL is little too much. Nowadays using ORMs like Entity framework to generate the DAL is recommended. The DAL code will be generated by ORM itself. 

Using the code   

Let us develop a small Toy ASP.NET application that will use n-tier architecture. We will develop a small Employee Management application for the NorthWind Database. (For simplicity, I have removed all other tables from the DB and some columns from the Employee table). This application should be able to perform the basic CRUD operations on the DB.

The solution for this application will contain separate projects for DAL and BLL. The Data Layer will be SqlServer. The Presentation Layer is an ASP.NET website running on top of these projects.

The Data Layer

The data layer in this example contain only one table called Employee. The data layer also contains the stored procedures for all the basic operations on the Employee table. So let us look at the table and all the stored Procedures we have in our Data Layer. 


Now we will create a set of stored procedures to perform the operations on the Employees Table.

--1. Procedure to add a new employee 
CREATE PROCEDURE dbo.AddNewEmployee
	(		
		@LastName	nvarchar(20),
		@FirstName	nvarchar(10),
		@Title		nvarchar(30),	
		@Address	nvarchar(60),
		@City		nvarchar(15),	
		@Region		nvarchar(15),	
		@PostalCode	nvarchar(10),
		@Country	nvarchar(15),
		@Extension	nvarchar(4)
	)
AS
	insert into Employees
	(LastName, FirstName, Title, Address, City, Region, PostalCode, Country, Extension)
	values
	(@LastName, @FirstName, @Title, @Address, @City, @Region, @PostalCode, @Country, @Extension)
	RETURN

--2. Procedure to delete an employee 
CREATE PROCEDURE dbo.DeleteEmployee
	(
	@empId int
	)
	
AS
	delete from Employees where EmployeeID = @empId
	RETURN

--3. Procedure to add get an employee details 
CREATE PROCEDURE dbo.GetEmployeeDetails
	(
	@empId int
	)
	
AS
	Select * from Employees where EmployeeID = @empId
	RETURN

--4. Procedure to get all the employees in the table 
CREATE PROCEDURE dbo.GetEmployeeList	
AS
	Select * from Employees
	
	RETURN

--5. Procedure to update an employee details
CREATE PROCEDURE dbo.UpdateEmployee
	(
		@EmployeeID	int,
		@LastName	nvarchar(20),
		@FirstName	nvarchar(10),
		@Title		nvarchar(30),	
		@Address	nvarchar(60),
		@City		nvarchar(15),	
		@Region		nvarchar(15),	
		@PostalCode	nvarchar(10),
		@Country	nvarchar(15),
		@Extension	nvarchar(4)
	)
AS
	update Employees
	set	
		LastName = @LastName, 
		FirstName = @FirstName, 
		Title = @Title, 
		Address = @Address, 
		City = @City, 
		Region = @Region, 
		PostalCode = @PostalCode, 
		Country = @Country, 
		Extension = @Extension
	where 
		EmployeeID = @EmployeeID
	RETURN

Now we have our Data Layer ready.

The Data Access Layer

Now we will go ahead and create a Data Access Layer for our application. The data access layer will contain 2 main type of classes. A set of classes that will represent the Table entities. And classes to perform the CRUD operations on the database.  


The Employee class in the above diagram is the Entity that will represent the Employee table. This class has been created so that the Layers above the DAL will use this class to perform operations in Employee table and they need not worry about the table schema related details.

public class Employee
{
	int employeeID;
	string lastName;    //  should be (20)  chars only
	string firstName;   //	should be (10)	chars only
	string title;       //	should be (30)	chars only
	string address;     //	should be (60)	chars only
	string city;        //	should be (15)	chars only
	string region;      //	should be (15)	chars only
	string postalCode;  //	should be (10)	chars only
	string country;     //	should be (15)	chars only
	string extension;   //	should be (4)	chars only

	public int EmployeeID
	{
		get
		{
			return employeeID;
		}
		set
		{
			employeeID = value;
		}
	}

	public string LastName
	{
		get
		{
			return lastName;
		}
		set
		{
			lastName  = value;
		}
	}

	public string FirstName
	{
		get
		{
			return firstName;
		}
		set
		{
			firstName = value;
		}
	}

	public string Title
	{
		get
		{
			return title;
		}
		set
		{
			title = value;
		}
	}

	public string Address
	{
		get
		{
			return address;
		}
		set
		{
			address = value;
		}
	}

	public string City
	{
		get
		{
			return city;
		}
		set
		{
			city = value;
		}
	}

	public string Region
	{
		get
		{
			return region;
		}
		set
		{
			region = value;
		}
	}

	public string PostalCode
	{
		get
		{
			return postalCode;
		}
		set
		{
			postalCode = value;
		}
	}

	public string Country
	{
		get
		{
			return country;
		}
		set
		{
			country = value;
		}
	}

	public string Extension
	{
		get
		{
			return extension;
		}
		set
		{
			extension = value;
		}
	}
}

The EmployeeDBAccess class expose the methods to perform the CRUD operations on the Employee table.

public class EmployeeDBAccess
{
	public bool AddNewEmployee(Employee employee)
	{
		SqlParameter[] parameters = new SqlParameter[]
		{                
			new SqlParameter("@LastName", employee.LastName),
			new SqlParameter("@FirstName", employee.FirstName),
			new SqlParameter("@Title", employee.Title),
			new SqlParameter("@Address", employee.Address),
			new SqlParameter("@City", employee.City),
			new SqlParameter("@Region", employee.Region),
			new SqlParameter("@PostalCode", employee.PostalCode),
			new SqlParameter("@Country", employee.Country),
			new SqlParameter("@Extension", employee.Extension)
		};

		return SqlDBHelper.ExecuteNonQuery("AddNewEmployee", CommandType.StoredProcedure, parameters);
	}

	public bool UpdateEmployee(Employee employee)
	{
		SqlParameter[] parameters = new SqlParameter[]
		{
			new SqlParameter("@EmployeeID", employee.EmployeeID),
			new SqlParameter("@LastName", employee.LastName),
			new SqlParameter("@FirstName", employee.FirstName),
			new SqlParameter("@Title", employee.Title),
			new SqlParameter("@Address", employee.Address),
			new SqlParameter("@City", employee.City),
			new SqlParameter("@Region", employee.Region),
			new SqlParameter("@PostalCode", employee.PostalCode),
			new SqlParameter("@Country", employee.Country),
			new SqlParameter("@Extension", employee.Extension)
		};

		return SqlDBHelper.ExecuteNonQuery("UpdateEmployee", CommandType.StoredProcedure, parameters);
	}

	public bool DeleteEmployee(int empID)
	{
		SqlParameter[] parameters = new SqlParameter[]
		{
			new SqlParameter("@empId", empID)
		};

		return SqlDBHelper.ExecuteNonQuery("DeleteEmployee", CommandType.StoredProcedure, parameters);
	}

	public Employee GetEmployeeDetails(int empID)
	{
		Employee employee = null;

		SqlParameter[] parameters = new SqlParameter[]
		{
			new SqlParameter("@empId", empID)
		};
		//Lets get the list of all employees in a datataable
		using (DataTable table = SqlDBHelper.ExecuteParamerizedSelectCommand("GetEmployeeDetails", CommandType.StoredProcedure, parameters))
		{
			//check if any record exist or not
			if (table.Rows.Count == 1)
			{
				DataRow row = table.Rows[0];

				//Lets go ahead and create the list of employees
				employee = new Employee();

				//Now lets populate the employee details into the list of employees                                           
				employee.EmployeeID = Convert.ToInt32(row["EmployeeID"]);
				employee.LastName = row["LastName"].ToString();
				employee.FirstName = row["FirstName"].ToString();
				employee.Title = row["Title"].ToString();
				employee.Address = row["Address"].ToString();
				employee.City = row["City"].ToString();
				employee.Region = row["Region"].ToString();
				employee.PostalCode = row["PostalCode"].ToString();
				employee.Country = row["Country"].ToString();
				employee.Extension = row["Extension"].ToString();
			}
		}

		return employee;
	}

	public List<employee> GetEmployeeList()
	{
		List<employee> listEmployees = null;

		//Lets get the list of all employees in a datataable
		using (DataTable table = SqlDBHelper.ExecuteSelectCommand("GetEmployeeList", CommandType.StoredProcedure))
		{
			//check if any record exist or not
			if (table.Rows.Count > 0)
			{
				//Lets go ahead and create the list of employees
				listEmployees = new List<employee>();

				//Now lets populate the employee details into the list of employees
				foreach (DataRow row in table.Rows)
				{
					Employee employee = new Employee();
					employee.EmployeeID = Convert.ToInt32(row["EmployeeID"]);
					employee.LastName = row["LastName"].ToString();
					employee.FirstName = row["FirstName"].ToString();
					employee.Title = row["Title"].ToString();
					employee.Address = row["Address"].ToString();
					employee.City = row["City"].ToString();
					employee.Region = row["Region"].ToString();
					employee.PostalCode = row["PostalCode"].ToString();
					employee.Country = row["Country"].ToString();
					employee.Extension = row["Extension"].ToString();

					listEmployees.Add(employee);
				}
			}
		}

		return listEmployees;
	}        
}
</employee></employee></employee>

The class SqlDbHelper is a wrapper class for ADO.NET functions providing a more simpler interface to use by the rest of DAL. 

class SqlDBHelper
{
	const string CONNECTION_STRING = @"Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\NORTHWND.MDF;Integrated Security=True;User Instance=True";

	// This function will be used to execute R(CRUD) operation of parameterless commands
	internal static DataTable ExecuteSelectCommand(string CommandName, CommandType cmdType)
	{
		DataTable table = null;
		using (SqlConnection con = new SqlConnection(CONNECTION_STRING))
		{
			using (SqlCommand cmd = con.CreateCommand())
			{
				cmd.CommandType = cmdType;
				cmd.CommandText = CommandName;

				try
				{
					if (con.State != ConnectionState.Open)
					{
						con.Open();
					}

					using (SqlDataAdapter da = new SqlDataAdapter(cmd))
					{
						table = new DataTable();
						da.Fill(table);
					}
				}
				catch
				{
					throw;
				}                   
			}
		}

		return table;
	}

	// This function will be used to execute R(CRUD) operation of parameterized commands
	internal static DataTable ExecuteParamerizedSelectCommand(string CommandName, CommandType cmdType, SqlParameter[] param)
	{
		DataTable table = new DataTable();

		using (SqlConnection con = new SqlConnection(CONNECTION_STRING))
		{
			using (SqlCommand cmd = con.CreateCommand())
			{
				cmd.CommandType = cmdType;
				cmd.CommandText = CommandName;
				cmd.Parameters.AddRange(param);

				try
				{
					if (con.State != ConnectionState.Open)
					{
						con.Open();
					}

					using (SqlDataAdapter da = new SqlDataAdapter(cmd))
					{
						da.Fill(table);
					}
				}
				catch
				{
					throw;
				}                    
			}
		}

		return table;
	}

	// This function will be used to execute CUD(CRUD) operation of parameterized commands
	internal static bool ExecuteNonQuery(string CommandName, CommandType cmdType, SqlParameter[] pars)
	{
		int result = 0;

		using (SqlConnection con = new SqlConnection(CONNECTION_STRING))
		{
			using (SqlCommand cmd = con.CreateCommand())
			{
				cmd.CommandType = cmdType;
				cmd.CommandText = CommandName;
				cmd.Parameters.AddRange(pars);                    

				try
				{
					if (con.State != ConnectionState.Open)
					{
						con.Open();
					}

					result = cmd.ExecuteNonQuery();
				}
				catch
				{
					throw;
				}                   
			}
		}

		return (result > 0);        
	}        
}

Note: If we use any ORM(Object Relation Mapper) then DAL need not be written. The ORM will generate all the DAL code. Entity framework is one of the best ORMs available. This DAL can simply be replaced with a class library containing the Entity Framework generated Entities and Contexts. 

The Business Logic Layer

The business logic layer will have a reference to the DAL and will mainly perform Business rule validation and business logic specific calculations. In out example, I will write a simple BLL that will govern the IO between the DAL and Presentation layer. In real applications the BLL will contain more logic and code. 

public class EmployeeHandler
{
	// Handle to the Employee DBAccess class
	EmployeeDBAccess employeeDb = null;

	public EmployeeHandler()
	{
		employeeDb = new EmployeeDBAccess();
	}

	// This fuction does not contain any business logic, it simply returns the 
	// list of employees, we can put some logic here if needed
	public List<employee> GetEmployeeList()
	{
		return employeeDb.GetEmployeeList();
	}

	// This fuction does not contain any business logic, it simply returns the 
	// list of employees, we can put some logic here if needed
	public bool UpdateEmployee(Employee employee)
	{
		return employeeDb.UpdateEmployee(employee);
	}

	// This fuction does not contain any business logic, it simply returns the 
	// list of employees, we can put some logic here if needed
	public Employee GetEmployeeDetails(int empID)
	{
		return employeeDb.GetEmployeeDetails(empID);
	}

	// This fuction does not contain any business logic, it simply returns the 
	// list of employees, we can put some logic here if needed
	public bool DeleteEmployee(int empID)
	{
		return employeeDb.DeleteEmployee(empID);
	}

	// This fuction does not contain any business logic, it simply returns the 
	// list of employees, we can put some logic here if needed
	public bool AddNewEmployee(Employee employee)
	{
		return employeeDb.AddNewEmployee(employee);
	}
}

The Presentation Layer

The presentation layer now contains only a set of pages and code behinds and it will use the BLL and the the Employee class to perform all the operations. The add Operation can be seen as an example how the BLL is being used to perform an operation. 

Employee emp = new Employee();

emp.LastName = txtLName.Text;
emp.FirstName = txtFName.Text;
emp.Address = txtAddress.Text;
emp.City = txtCity.Text;
emp.Country = txtCountry.Text;
emp.Region = txtRegion.Text;
emp.PostalCode = txtCode.Text;
emp.Extension = txtExtension.Text;
emp.Title = txtTitle.Text;

EmployeeHandler empHandler = new EmployeeHandler();

if (empHandler.AddNewEmployee(emp) == true)
{
	//Successfully added a new employee in the database
	Response.Redirect("Default.aspx");
}

Note: All the CRUD operations have been implemented. Please refer tio the sample code for all the details. When we run the application we can see all the EDIT/UPDATE, DELETE and ADD operations in action.

Point of Interest

I created this small application to demonstrate application development using n-tier architecture. The demo application has been created to show the basic idea behind the 3-tier architecture. There are many things that are still missing from this sample from the completion perspective. Client side validation and server side validation in presentation layer, Business rule validation and calculations in BLL are some missing things.

Since the idea here was to talk about how to put n-tier architecture in actual code, I think this article might have provided some useful information on that. I hope this has been informative.

History

  • 13 August 2012: First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Rahul Rajat Singh
Software Developer (Senior)
India India
Member
I Started my Programming career with C++. Later got a chance to develop Windows Form applications using C#. Currently using C#, ASP.NET & ASP.NET MVC to create Information Systems, e-commerce/e-governance Portals and Data driven websites.

My interests involves Programming, Website development and Learning/Teaching subjects related to Computer Science/Information Systems.
 
Some CodeProject Achievements:
  • 9th in Best Web Dev article of March 2013
  • 7th in Best Web Dev article of January 2013
  • 2nd in Best C# article of December 2012
  • 5th in Best overall article of December 2012
  • 5th in Best C# article of October 2012
  • 4th in Best Web Dev article of September 2012
  • 3rd in Best C# article of August 2012
  • 5th in Best Web Dev article of August 2012
  • 5th in Best Web Dev article of July 2012
  • 3rd in Best Overall article of June 2012
  • 2nd in Best Web Dev article of June 2012
  • 5th in Best Web Dev article of May 2012
  • 6th in Best Web Dev article of April 2012
  • 4th in Best C++ article of April 2012
  • 5th in Best C++ article of March 2012
  • 7th in Best Web Dev article of March 2012
  • 5th in Best Web Dev article of February 2012
  • 7th in Best Web Dev article of February 2012
  • 9th in Best Web Dev article of February 2012
  • 5th in Best C++ article of February 2012

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionRestore DataBasememberIndrajitDasgupat11 Apr '13 - 23:42 
AnswerRe: Restore DataBasemvpRahul Rajat Singh11 Apr '13 - 23:47 
Questionyos 10000000000 $memberقناص سلوان13 Feb '13 - 6:36 
Newsدردشة دلعmemberقناص سلوان13 Feb '13 - 6:35 
Questiondal3 palstaen plaezmemberقناص سلوان13 Feb '13 - 6:32 
QuestionWindows8 and Visual Studio 2012 UltimatememberShantanu Chandra30 Jan '13 - 0:43 
GeneralExcellentmemberrashidalikhan10 Jan '13 - 20:43 
QuestionHow to handle transactions associated with multi business objects or entities?memberxport7 Dec '12 - 21:10 
AnswerRe: How to handle transactions associated with multi business objects or entities?memberRahul Rajat Singh7 Dec '12 - 21:32 
QuestionIntegrate With jQuerymemberChinni0077 Dec '12 - 5:17 
Questionmissing directive/assembly referencememberYinYang20501 Dec '12 - 7:03 
AnswerRe: missing directive/assembly referencememberRahul Rajat Singh3 Dec '12 - 16:58 
Questioninterference UI with DALmemberMember 120847821 Nov '12 - 0:43 
AnswerRe: interference UI with DALmemberteest19 Mar '13 - 21:14 
QuestionMaster detailmembers_safdar14 Nov '12 - 7:36 
AnswerRe: Master detailmemberRahul Rajat Singh14 Nov '12 - 17:13 
GeneralMy vote of 4membervsanju11 Nov '12 - 22:00 
GeneralMy vote of 5memberDarkjo7 Nov '12 - 21:46 
GeneralMy vote of 5memberSavalia Manoj M7 Nov '12 - 16:46 
GeneralVery good.membermark merrens2 Oct '12 - 7:56 
GeneralExcellentmemberShashank Shrivastava28 Aug '12 - 21:26 
AnswerRe: ExcellentmemberRahul Rajat Singh28 Aug '12 - 21:45 
GeneralRe: ExcellentmemberSanjay K. Gupta31 Aug '12 - 3:24 
AnswerRe: ExcellentmemberRahul Rajat Singh31 Aug '12 - 18:17 
AnswerArticle of the Day on Microsoft's site [modified]memberRahul Rajat Singh28 Aug '12 - 18:20 
GeneralMy vote of 4memberpradiprenushe27 Aug '12 - 2:57 
GeneralMy vote of 3membermahdi87_gh22 Aug '12 - 23:48 
QuestionN Tier or layermemberSakshi Smriti21 Aug '12 - 18:54 
AnswerRe: N Tier or layermemberRahul Rajat Singh21 Aug '12 - 19:01 
GeneralRe: N Tier or layermemberSakshi Smriti21 Aug '12 - 19:24 
GeneralMy vote of 5memberSakshi Smriti21 Aug '12 - 18:51 
GeneralMy vote of 4memberPhani Kiran Chikkala21 Aug '12 - 2:27 
GeneralMy vote of 4memberChristian Amado14 Aug '12 - 6:02 
GeneralMy vote of 5memberRaselCoder13 Aug '12 - 23:25 
GeneralMy vote of 5memberFarhan Ghumra13 Aug '12 - 23:14 
QuestionHimemberNitin Sawant13 Aug '12 - 19:28 
AnswerRe: HimemberRahul Rajat Singh13 Aug '12 - 19:49 
GeneralRe: HimemberNitin Sawant13 Aug '12 - 19:51 
QuestionNot a advance conceptmemberSaleth Prakash13 Aug '12 - 18:40 
GeneralMy vote of 3memberAbdul Quader Mamun13 Aug '12 - 5:24 
GeneralRe: My vote of 3memberAbdul Quader Mamun13 Aug '12 - 5:26 
GeneralRe: My vote of 3memberRahul Rajat Singh13 Aug '12 - 5:35 
GeneralRe: My vote of 3memberAbdul Quader Mamun15 Aug '12 - 1:28 
GeneralRe: My vote of 3memberAbdul Quader Mamun15 Aug '12 - 1:28 
QuestionIt's a fine article...memberkaschimer13 Aug '12 - 3:41 
AnswerRe: It's a fine article...memberRahul Rajat Singh13 Aug '12 - 5:30 
GeneralRe: It's a fine article...memberkaschimer13 Aug '12 - 6:12 
GeneralRe: It's a fine article...memberRudolf Grauberger14 Aug '12 - 2:08 
GeneralRe: It's a fine article...memberkaschimer14 Aug '12 - 2:16 
GeneralRe: It's a fine article...memberRudolf Grauberger14 Aug '12 - 3:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 21 Aug 2012
Article Copyright 2012 by Rahul Rajat Singh
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid