3 tier architecture in asp.net - a complete article

Sheonarayan
Posted by in ASP.NET category on for Intermediate level | Points: 250 | Views : 196070 red flag
Rating: 5 out of 5  
 8 vote(s)

There are many articles on 3 tier architecture on the internet however still I felt there is a need of more refined and well explained article. So here is this, in this article we are going to look into almost all aspects of designing a 3 tier architecture website in ASP.NET.


 Download source code for 3 tier architecture in asp.net - a complete article

Introduction


Writing all code into the code file of ASP.NET or presentation layer of desktop or any other application makes an application less maintainable, scalable and extendible. Apart from this there are many other disadvantages of keeping all types of code into the same file.

N-tier application is the solution of this problem where separation of concern methodology is followed where a single tier is responsible for a single type of job. N-tier means more than one tier of application where the code of different types of jobs are written into different tiers (or projects).

This article will not explain the advantages or disadvantages of 3 tier architecture as the possibility of you are here and reading this article you already know the advantages.

In this article, we will learn how to create 3-tier architecture application (that is very popular) in ASP.NET web form. Even if this is designed in ASP.NET Web Form website, the same approach can be followed for any other type of application. The only thing changes is presentation layer; in web application or website, the presentation layer is ASP.NET Web Form; in Desktop application, the presentation layer if Windows Form and so on.

Objective


The objective of this article is to create a CRUD application using 3 tier architecture in ASP.NET Web Form with C# of a Person database table whose structure is same as displayed in picture below.


3 tier architecture application solution structure


A normal 3 tier architecture application has following tiers

  1. Presentation Layer - ASP.NET Web Form project that contains .aspx, .ascx and other types of files that is helpful to create a User Interface (UI) of the application or website or any other type of application.

  2. Business Access Layer (BAL) - This tier contains business logic, secondary validations, calculations and call to Data Access Layer methods if needed. If there is no business logic and any other types of validations to be performed, it simply calls Data Access Layer on need basis.

  3. Data Access Layer (DAL) - This tier contains necessary code to connect to the database and perform database activities (such as insert, update, delete, load records) and returns result to the Business Access Layer.
So here is the typical solution structure of a 3 tier architecture application solution.



Designing 3 tier architecture solution


For this demo, we have created a website and we are going to take that as an example and learn the complete process of creating 3 tier architecture solution.

So open Visual Studio and go to File > New Web Site > select Visual C# from left panel under Templates and select ASP.NET Empty Web Site template. Click OK and your solution explorer would look something similar to below picture.



Adding Business Access Layer (BAL) and Data Access Layer (DAL) tiers


To add other layers like Business Access Layer (BAL) and Data Access Layer (DAL), follow below step as displayed in the this picture.


Right click your Solution and go to Add > New Project ... In the Add New Project dialogue box, select Windows from left panel and Class Library from the Template, write the Name and Location of the Project to create and click OK. Ideally, Web Project, BAL and DAL all should be into the same parent folder.


After adding BAL and DAL, the project structure looks like below picture. Where 3-tier is our Presentation Layer and BAL is Business Access Layer and DAL is Data Access Layer projects.



By default when we add a Class Library, we get Class1.cs file so we have to rename that so that it looks more meaningful like it is done in this demo solution. 

Lets create with Data Access Layer (DAL) first


Our DAL for Person database table looks like below where we have following methods to perform respective operation against Person database table.
  1. Insert - Insert record into Person database table
  2. Update - Update record into Person database table
  3. Delete - Delete record from Person database table
  4. LoadAll - Load all records from the Person database table
  5. Load - Load a single record from Person database table
Below is the code snippet of PersonDAL.cs file from Data Access Layer (DAL).

using System;
using System.Configuration;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;

namespace DAL
{
    public class PersonDAL
    {
        static string _connStr = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;

        /// <summary>
        /// Insert record into the database
        /// </summary>
        /// <param name="firstName"></param>
        /// <param name="lastName"></param>
        /// <param name="age"></param>
        /// <returns></returns>
        public int Insert(string firstName, string lastName, int age)
        {
            var returnValue = 0;
            using (SqlConnection conn = new SqlConnection(_connStr))
            {
                using (SqlCommand dCmd = new SqlCommand("InsertPerson", conn))
                {
                    dCmd.CommandType = CommandType.StoredProcedure;
                    SqlParameter[] prms = new SqlParameter[3];
                    prms[0] = new SqlParameter("@FirstName", SqlDbType.VarChar, 50);
                    prms[0].Value = firstName;
                    prms[1] = new SqlParameter("@LastName", SqlDbType.VarChar, 50);
                    prms[1].Value = lastName;
                    prms[2] = new SqlParameter("@Age", SqlDbType.Int);
                    prms[2].Value = age;

                    dCmd.Parameters.AddRange(prms);

                    conn.Open();
                    returnValue = dCmd.ExecuteNonQuery();
                    conn.Close();
                }
            }
            return returnValue;
        }

        /// <summary>
        /// Update record into the database
        /// </summary>
        /// <param name="autoId"></param>
        /// <param name="firstName"></param>
        /// <param name="lastName"></param>
        /// <param name="age"></param>
        /// <returns></returns>
        public int Update(int autoId, string firstName, string lastName, int age)
        {
            var returnValue = 0;
            using (SqlConnection conn = new SqlConnection(_connStr))
            {
                using (SqlCommand dCmd = new SqlCommand("UpdatePerson", conn))
                {
                    dCmd.CommandType = CommandType.StoredProcedure;
                    SqlParameter[] prms = new SqlParameter[4];
                    prms[0] = new SqlParameter("@FirstName", SqlDbType.VarChar, 50);
                    prms[0].Value = firstName;
                    prms[1] = new SqlParameter("@LastName", SqlDbType.VarChar, 50);
                    prms[1].Value = lastName;
                    prms[2] = new SqlParameter("@Age", SqlDbType.Int);
                    prms[2].Value = age;
                    prms[3] = new SqlParameter("@AutoId", SqlDbType.Int);
                    prms[3].Value = autoId;
                    
                    dCmd.Parameters.AddRange(prms);

                    conn.Open();
                    returnValue = dCmd.ExecuteNonQuery();
                    conn.Close();
                }
            }
            return returnValue;
        }
        
        /// <summary>
        /// Delete record from the database tbale
        /// </summary>
        /// <param name="autoId"></param>
        /// <returns></returns>
        public int Delete(int autoId)
        {
            var returnValue = 0;
            using (SqlConnection conn = new SqlConnection(_connStr))
            {
                using (SqlCommand dCmd = new SqlCommand("DeletePerson", conn))
                {
                    dCmd.CommandType = CommandType.StoredProcedure;
                    SqlParameter prm = new SqlParameter("@AutoId", SqlDbType.Int);
                    prm.Value = autoId;

                    dCmd.Parameters.Add(prm);

                    conn.Open();
                    returnValue = dCmd.ExecuteNonQuery();
                    conn.Close();
                }
            }
            return returnValue;
        }

        /// <summary>
        /// Load all records from database table
        /// </summary>
        /// <returns></returns>
        public DataTable LoadAll()
        {
            DataTable table = new DataTable();
            using (SqlConnection conn = new SqlConnection(_connStr))
            {
                using (SqlDataAdapter ad = new SqlDataAdapter("LoadAll", conn))
                {
                    ad.SelectCommand.CommandType = CommandType.StoredProcedure;
                    ad.Fill(table);
                }
            }
            return table;
        }

        /// <summary>
        /// Load a single records from database table
        /// </summary>
        /// <returns></returns>
        public DataTable Load(int autoId)
        {
            DataTable table = new DataTable();
            using (SqlConnection conn = new SqlConnection(_connStr))
            {
                using (SqlDataAdapter ad = new SqlDataAdapter("LoadPerson", conn))
                {
                    ad.SelectCommand.CommandType = CommandType.StoredProcedure;
                    SqlParameter prm = new SqlParameter("@AutoId", SqlDbType.Int);
                    prm.Value = autoId;
                    ad.SelectCommand.Parameters.Add(prm);

                    ad.Fill(table);
                }
            }
            return table;
        }

    }
}
PersonDAL.cs file

First we have a static variable for the database connection string that we have stored in the web.config file (of Presentation Layer, in this case ASP.NET Web Form). In order to get it we need to reference System.Configuration namespace. To do that Right click the DAL project and select Add Reference ... and select the namespace as displayed in the picture below.



Methods in PersonDAL.cs file

Insert

It accepts 3 parameters and uses SqlConnection, SqlCommand and other SqlParameter objects and execute InsertPerson stored procedure to insert record into the database. It also returns number of records affected by the Stored Procedure.

InsertPerson stored procedure
CREATE PROCEDURE [dbo].[InsertPerson]
	@FirstName varchar(50),
	@LastName varchar(50),
	@Age int
AS
BEGIN
	SET NOCOUNT OFF; -- Keep it off to returns no. of records affected

    INSERT INTO [dbo].[Person]
           ([FirstName]
           ,[LastName]
           ,[Age])
     VALUES
           (@FirstName,
		   @LastName,
		   @Age)

END

Update

It accepts four parameters and executes UpdatePerosn stored procedure the way we have done in Insert method to update record into the database.

UpdatePerson stored procedure

CREATE PROCEDURE [dbo].[UpdatePerson]
	@AutoId int,
	@FirstName varchar(50),
	@LastName varchar(50),
	@Age int
AS
BEGIN
	SET NOCOUNT OFF; -- Keep it off to returns no. of records affected

	UPDATE [dbo].[Person]
	   SET [FirstName] = @FirstName
		  ,[LastName] = @LastName
		  ,[Age] = @Age
	 WHERE AutoId = @AutoId

END

Delete

It accepts autoId as parameter and executes DeletePerson stored procedure to delete record from the database.

DeletePerson stored procedure

CREATE PROCEDURE [dbo].[DeletePerson]
	@AutoId int
AS
BEGIN
	SET NOCOUNT OFF; -- Keep it off to returns no. of records affected

	DELETE Person WHERE AutoId = @AutoId

END

LoadAll

It simply executes LoadAll stored procedure and returns all records from the Person database table.

CREATE PROCEDURE [dbo].[LoadAll]
	
AS
BEGIN
	-- SET NOCOUNT ON added to prevent extra result sets from
	-- interfering with SELECT statements.
	SET NOCOUNT ON;

	SELECT AutoId, FirstName, LastName, Age FROM Person ORDER BY AutoId DESC

END

Load

It accepts autoId parameter and executes LoadPerson stored procedure to return a single record from the Person database table.

ALTER PROCEDURE [dbo].[LoadPerson]
	-- Add the parameters for the stored procedure here
	@AutoId int
AS
BEGIN
	-- SET NOCOUNT ON added to prevent extra result sets from
	-- interfering with SELECT statements.
	SET NOCOUNT ON;

	SELECT AutoId, FirstName, LastName, Age FROM Person WHERE AutoId = @AutoId

END

Creating Business Access Layer (BAL)


As explained above, BAL is responsible for business logic, secondary validation, calculations and Data Access if any. In our case we have no business logic or calculations so this will simply call the respective Data Access Layer methods and returns the data to Presentation layer.

In order to call the Data Access Layer methods, we will need to add the DAL project reference to this project so do that as shown in the picture below by right clicking the BAL project and selecting Add Reference ... .



Below is the code snippet for PersonBAL.cs file from BAL.

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using DAL;

namespace BAL
{
    public class PersonBAL
    {
        /// <summary>
        /// Insert record into the database
        /// </summary>
        /// <param name="firstName"></param>
        /// <param name="lastName"></param>
        /// <param name="age"></param>
        /// <returns></returns>
        public int Insert(string firstName, string lastName, int age)
        {
            // your businees logic, calculations or validations may go here
            return new PersonDAL().Insert(firstName, lastName, age);
        }

        /// <summary>
        /// Update record into the database
        /// </summary>
        /// <param name="autoId"></param>
        /// <param name="firstName"></param>
        /// <param name="lastName"></param>
        /// <param name="age"></param>
        /// <returns></returns>
        public int Update(int autoId, string firstName, string lastName, int age)
        {
            // your businees logic, calculations or validations may go here
            return new PersonDAL().Update(autoId, firstName, lastName, age);
        }

        /// <summary>
        /// Delete record from the database tbale
        /// </summary>
        /// <param name="autoId"></param>
        /// <returns></returns>
        public int Delete(int autoId)
        {
            // your businees logic, calculations or validations may go here
            return new PersonDAL().Delete(autoId);
        }

        /// <summary>
        /// Load all records from database table
        /// </summary>
        /// <returns></returns>
        public DataTable LoadAll()
        {
            // your businees logic, calculations or validations may go here
            return new PersonDAL().LoadAll();
        }

        /// <summary>
        /// Load a single records from database table
        /// </summary>
        /// <returns></returns>
        public DataTable Load(int autoId)
        {
            // your businees logic, calculations or validations may go here
            return new PersonDAL().Load(autoId);
        }
    }
}

This has again the same method names as in the PersonDAL.cs file
  1. Insert - Calls Insert method of the Data Access Layer
  2. Update - Calls Update method of the Data Access Layer
  3. Delete - Calls Delete method of the Data Access Layer
  4. LoadAll - Calls LoadAll method of the Data Access Layer
  5. Load - Calls Load method of the Data Access Layer
The inside code of these methods are just calling respective methods of DAL so separate explanations of each method might not make much sense so it has been skipped here.

Creating Presentation Layer


As explained above, presentation layer is primarily responsible for giving a nice clean user interface to the end user  to interact with the application and to perform necessary functionality. It also contains primary validation of the data to ensure that only valid data is going to BAL.

To avoid complexity of this demo project, validation logic and validation controls have been avoided.

Inserting a record - Insert.aspx



The code to create a form to insert a record into the Person database table is below.

<form id="form1" runat="server">
    <h1>Insert Record</h1>
    <div>
        <p>First Name: <asp:TextBox ID="txtFirstName" runat="server" /></p>
        <p>Last Name: <asp:TextBox ID="txtLastName" runat="server" /></p>
        <p>Age: <asp:TextBox ID="txtAge" runat="server" /></p>
        <asp:Button ID="btnSave" runat="server" OnClick="btnSave_Click" Text="Save" />
    </div>
        <p><asp:Label ID="lblMessage" runat="server" EnableViewState="false" /></p>
    </form>

Clicking on the Save buttons fires btnSave_Click method of the code file of Insert.aspx and below is the code for that.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using BAL;

public partial class Insert : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {

    }

    protected void btnSave_Click(object sender, EventArgs e)
    {
        var result = 0;
        try
        {
            // Validate your data, if any
            int age = 0;
            int.TryParse(txtAge.Text, out age);

            result = new PersonBAL().Insert(txtFirstName.Text.Trim(), txtLastName.Text.Trim(), age);
            if (result > 0)
            {
                lblMessage.Text = "Record inserted successfully !";
            }
            else
            {
                lblMessage.Text = "No record afftected.";
            }
        }
        catch (Exception ee)
        {
            lblMessage.Text = "Error occured. " + ee.Message;
        }
    }
}
In above code snippet, first we are converting age textbox value to the integer value and calling the Insert method of BAL. Based on what value the method is returning, we are writing message to the Label control. In this case if a record is inserted 1 will be returned. 

As we are calling BAL method inside the try catch block so if any error occurs in either BAL or even in the DAL it will be caught and displayed into the Label.

Listing records from the database - default.aspx



In this page, we will list records  into GridView and to facilitate users to see complete details and update a record we will have two more columns added named Details and Update. Clicking on these column links sends user to Details.aspx and Update.aspx page respectively.
<asp:Label ID="lblMessage" runat="server" EnableViewState="false" />
        <asp:GridView ID="GridView1" runat="server" EnableViewState="false">
            <Columns>
                <asp:TemplateField HeaderText="Details">
                    <ItemTemplate>
                            <a href="details.aspx?autoid=<%# Eval("AutoId") %>">Details</a>
                        </span>
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField HeaderText="Update">
                    <ItemTemplate>
                            <a href="update.aspx?autoid=<%# Eval("AutoId") %>">Update</a>
                        </span>
                    </ItemTemplate>
                </asp:TemplateField>
            </Columns>
        </asp:GridView>

The code file of default.aspx file looks like below

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using BAL;

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            LoadRecords();
        }
    }

    private void LoadRecords()
    {
        try
        {
            DataTable table = new PersonBAL().LoadAll();
            GridView1.DataSource = table;
            GridView1.DataBind();
        }
        catch (Exception ee)
        {
            lblMessage.Text = ee.Message;
        }
    }
}
In the above code snippet, we are calling LoadRecords() method if the request is not Post back. In LoadRecords() method, we are calling LoadAll() method of BAL and returning DataTable and then the same is being set as DataSource of the GridView that lists records from the DataTable in tabular structure on the page.

Seeing details of the record - Details.aspx


This uses DetailsView control and displays record details in tabular format. This also has a Delete button to delete the record in case user want it. Delete button is wrapped into span element that helps us to get a confirmation from end user before calling btnDelete_Click method of the code file.

<asp:Label ID="lblMessage" runat="server" EnableViewState="false" />
            <asp:DetailsView ID="DetailsView1" runat="server" EnableViewState="false"></asp:DetailsView>

            <p>
                <span onclick="return confirm('Are you sure to delete?')">
                    <asp:Button ID="btnDelete" runat="server" Text="Delete" OnClick="btnDelete_Click" />
                </span>
            </p>
The code snippet of Details.aspx code file.

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using BAL;

public partial class Details : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack && !string.IsNullOrWhiteSpace(Request.QueryString["autoid"]))
        {
            ViewState["AutoId"] = Request.QueryString["autoid"];
            var autoId = int.Parse(Request.QueryString["autoid"]);
            
            ShowDtails(autoId);
        }
    }

    private void ShowDtails(int autoId)
    {
        try
        {
            DetailsView1.DataSource = new PersonBAL().Load(autoId);
            DetailsView1.DataBind();
        }
        catch (Exception ee)
        {
            lblMessage.Text = ee.Message;
        }
    }

    protected void btnDelete_Click(object sender, EventArgs e)
    {
        if (ViewState["AutoId"] == null)
        {
            return;
        }
        
        var result = 0;
        try
        {
            var autoId = int.Parse(ViewState["AutoId"].ToString());

            result = new PersonBAL().Delete(autoId);

            if (result > 0)
            {
                lblMessage.Text = "Record deleted successfully !";
                btnDelete.Visible = false;
            }
            else
            {
                lblMessage.Text = "No record affected.";
            }
        }
        catch (Exception ee)
        {
            lblMessage.Text = ee.Message;
        }
    }
}
Page_Load method

It checks if the request is not post back and there is a querystring coming in to this page then we are setting the value of querystring to the ViewState["AutoId"] that will be used to delete this record later on. Then we are calling the ShowDetails method by passing autoId as parameter.

ShowDetails method

This method calls the Load method of the BAL and bind the data into DetailsView control.

btnDelete_Click method

It checks for ViewState["AutoId"] and if it is null (means not a valid request to delete the record), it simply breaks  the method by calling return. If not, it calls Delete method of the BAL and write proper message.

Updating record - update.aspx


This page receives autoid as parameter and populate the record into the form for editing. The code for this form is below.

<form id="form1" runat="server">
    <h1>Update Record</h1>
    <div>
        <p>First Name: <asp:TextBox ID="txtFirstName" runat="server" /></p>
        <p>Last Name: <asp:TextBox ID="txtLastName" runat="server" /></p>
        <p>Age: <asp:TextBox ID="txtAge" runat="server" /></p>
        <asp:Button ID="btnSave" runat="server" OnClick="btnSave_Click" Text="Update" />
    </div>
        <p><asp:Label ID="lblMessage" runat="server" EnableViewState="false" /></p>
    </form>

Clicking on Update button execute btnSave_Click method of the code file.

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using BAL;

public partial class Update : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack && !string.IsNullOrWhiteSpace(Request.QueryString["autoid"]))
        {
            ViewState["AutoId"] = Request.QueryString["autoid"];
            var autoId = int.Parse(Request.QueryString["autoid"]);

            FillInPersonDetails(autoId);
        }
        else if (!IsPostBack)
        {
            Response.Redirect("~/default.aspx", true);
        }
    }

    private void FillInPersonDetails(int autoId)
    {
        try
        {
            DataTable table = new PersonBAL().Load(autoId);
            
            if (table.Rows.Count > 0)
            {
                DataRow row = table.Rows[0];
                txtAge.Text = row["Age"].ToString();
                txtFirstName.Text = row["FirstName"].ToString();
                txtLastName.Text = row["LastName"].ToString();
            }
        }
        catch (Exception ee)
        {
            lblMessage.Text = ee.Message;
        }
    }

    protected void btnSave_Click(object sender, EventArgs e)
    {
        if (ViewState["AutoId"] == null)
        {
            return;
        }

        var result = 0;
        try
        {
            // Validate your data, if any
            int age = 0;
            int.TryParse(txtAge.Text, out age);
            var autoId = int.Parse(ViewState["AutoId"].ToString());

            result = new PersonBAL().Update(autoId, txtFirstName.Text.Trim(), txtLastName.Text.Trim(), age);
            if (result > 0)
            {
                lblMessage.Text = "Record updated successfully !";
            }
            else
            {
                lblMessage.Text = "No record afftected.";
            }
        }
        catch (Exception ee)
        {
            lblMessage.Text = "Error occured. " + ee.Message;
        }
    }
}

Page_Load method

If the request is not post back and coming with autoid querystring then sets the value of autoid to the ViewState["AutoId"] and call FillInPersonDetails method else if it is not post back then redirects the user to default.aspx page (It happens when end user directly comes to the update.aspx page).

FillInPersonDetails method

This method calls Load method of the BAL by passing autoId parameter and sets the data to texboxes for editing.

btnSave_Click method

This method checks for the ViewState["AutoId"] and if its null then simply returns from the method else calls the Update method of the BAL by passing respective parameters and writes proper message based on what is returned from this method.

Final structure of this solution looks like below



Other Point of Interest - Error Handling


It is clear from the code snippet of BAL and DAL that we have not used try catch block there as it is actually not required. We can simply wrap the BAL method calls into try catch block in the presentation layer as it is done in above code snippets and display or log the error into Catch block. Wrapping code in try catch block in BAL and DAL is overhead to the application and unnecessary creates performance issue and confusion.

Please feel free to download and use the demo application created for this article.

Conclusion

Apart from many other advantages of 3 tier architecture that you might have already come across, it is very easy and useful for developer as well as application owner as it helps to understand the code easily and works best in big team size where each team member is responsible for doing a separate module and it also helps in easily maintaining, extending the application.

 Thanks for reading. Hope you liked this article, do comment and vote for this article and share to your friends and colleague.


Page copy protected against web site content infringement by Copyscape

About the Author

Sheonarayan
Full Name: Sheo Narayan
Member Level: HonoraryPlatinum
Member Status: Administrator
Member Since: 7/8/2008 6:32:14 PM
Country: India
Regards, Sheo Narayan http://www.dotnetfunda.com

Ex-Microsoft MVP, Author, Writer, Mentor & architecting applications since year 2001. Connect me on http://www.facebook.com/sheo.narayan | https://twitter.com/sheonarayan | http://www.linkedin.com/in/sheonarayan

Login to vote for this post.

Comments or Responses

Posted by: Shibin4m on: 12/6/2013 | Points: 25
Can you please explain me the difference between Layered and tiered architecture.I think above described is layered architecture.because all layers are under single solution file
Posted by: Sheonarayan on: 12/6/2013 | Points: 25
Hi Shibin,

Thanks for asking this question. I will try to answer your question in brief.

Single solution file doesn't necessarily guarantee that all application/projects of that solution will not be hosted separately.

In order to host the BAL and DAL on separate servers in this case, you can expose them via services.

Layers architecture is an architecture where your BAL and DAL remain in the same project (under subfolders). In this case BAL and DAL are separate project altogether so its tier not layer. In a giant application, you can have them hosted on separate servers exposed via services, this you may not do using Layered architecture.

In tier architecture, your BAL and DAL works for all different types of applications, just Presentation layer changes (in this case its Web, in other cases it might be desktop, mobile or console applications).

There are other points too to justify that this is tiered architecture but that will be again a separate topic.

Hope this helps.

Posted by: Meet1424 on: 2/27/2014 | Points: 25
Sir, Can you please explain some advantages of this? This functionality can be achieved by simply taking BAL and DAL as two classes rather than project.
Posted by: Ghasem110deh on: 4/22/2015 | Points: 25
hi,
i want insert image (image field in sql server 2008 r2)
butt error !

ImageToByte(pictureBox1.Image)


this my code :

public static byte[] ImageToByte(Image img) // image  

{
ImageConverter converter = new ImageConverter();
return (byte[])converter.ConvertTo(img, typeof(byte[]));
}

private void Btn_Insert_Click(object sender, EventArgs e)
{
var result = 0;
try
{
result = new Agent_Bll().Insert(Txt_Agent.Text.Trim(), Txt_Mob.Text.Trim(), ImageToByte(pictureBox1.Image) , Txt_Toz.Text.Trim());
if (result > 0)


please help !
Posted by: Manideepgoud on: 7/30/2016 | Points: 25
Thanks a lot sir, very well explained


Login to post response

Comment using Facebook(Author doesn't get notification)