Multilayered architecture

Before my job as a software developer I was a software trainer. Being a software trainer I learned many things related to software engineering. But I knew very little about what is called multilayered applications. Why we use multilayered applications? Currently I know at least something about this fine and useful approach of software engineering. Blogging means to me is to share thing and thoughts. Today I want to share my thoughts and knowledge of multilayered architecture. For that I am going to develop a very small demo application. We all know about Northwnd database and I am going to develop a simple ASP.NET application using this Northwnd database Customers table. This is not only sharing of knowledge but also I want to learn more, so I will expect comments from you so that I can learn more on this particular topic

What is Multilayered Application?
Multilayered application is a particular type of software designing procedure in software engineering. In multilayered application we generally use three layers.

  • Data Access Layer (DAL)
  • Business Logic Layer (BLL)
  • User Interface Layer (UI)

This can be sub divided in more layers depending on your application requirements. We write code for data access in DAL. Business logic for our application in BLL and user interface in UI. The advantage is that if there is a change in application business logic or data access or UI logic then we have to recompile the particular component only. So we implement this architecture to make our code maintainable. One other point is that I am also defining an interface for every component through which the calling component can interact. That means I decouple the service contract and the actual implementation of that contract. The calling component that will use that component will actually use the service contract (the interface) not the actual implementation. That is the benefit of interface which is in the definition: Interface is a service contract between service provider and service consumer. The service consumer does not need to know about the implementation details. They will only dealing with the service contract which is defined in the interface.

Northwnd solution
I have created a solution for Northwnd. This solution has different components. Like POCO, DAL, BLL and UI.

Solution explorer and class view

The POCO Layer
POCO stands for Plain Old CLR Object. Here I have created a class which represents Customers table in our application. The concept is database has relational format and our application has object oriented format. So we create objects which actually map to database tables. This is the main concept of object relational map per (ORM). There are some example of ORM are LINQ TO SQL, Entity Framework, NHibernate etc. Though for this application I am not using any of this, I am writing data access code and mapping code manually. POCO is independent and plain object. For this we can use customer object from our DAL, BLL and UI in object oriented manner. We do not need to wary about how to map this object to database, which is having relational format. The customer class is defined in this POCO section of our solution. This class can be used from any other layer of this solution (DAL, BLL and UI).

// ICustomer.cs
namespace Northwind.Poco
{
    public interface ICustomer
    {
        string CustomerId { get; set; }
        string CompanyName { get; set; }
        string ContactName { get; set; }
        string ContactTitle { get; set; }
        string Address { get; set; }
        string City { get; set; }
        string Region { get; set; }
        string PostalCode { get; set; }
        string Country { get; set; }
        string Phone { get; set; }
        string Fax { get; set; }
    }
}

// Customer.cs
namespace Northwind.Poco
{
    public class Customer : ICustomer
    {
        public string CustomerId { get; set; }
        public string CompanyName { get; set; }
        public string ContactName { get; set; }
        public string ContactTitle { get; set; }
        public string Address { get; set; }
        public string City { get; set; }
        public string Region { get; set; }
        public string PostalCode { get; set; }
        public string Country { get; set; }
        public string Phone { get; set; }
        public string Fax { get; set; }

        public Customer()
        {
            this.CustomerId = string.Empty;
            this.CompanyName = string.Empty;
            this.ContactName = string.Empty;
            this.ContactTitle = string.Empty;
            this.Address = string.Empty;
            this.Country = string.Empty;
            this.Region = string.Empty;
            this.City = string.Empty;
            this.PostalCode = string.Empty;
            this.Phone = string.Empty;
            this.Fax = string.Empty;
        }

        public Customer(string customerId, string companyName, string contactName, string contactTitle,
            string address, string region, string country, string city, string postalCode, string phone,
            string fax)
        {
            this.CustomerId = customerId;
            this.CompanyName = companyName;
            this.ContactName = contactName;
            this.ContactTitle = contactTitle;
            this.Address = address;
            this.Country = country;
            this.Region = region;
            this.City = city;
            this.PostalCode = postalCode;
            this.Phone = phone;
            this.Fax = fax;
        }
    }
}

The DAL Layer
Data access layer means, in this layer we write all the code which access the data from data source. We usually write the entire sql query or call store procedure from this layer.

In my application I write an another class called ‘Common’ in which I write all the common data access codes which can be used from the other classes in the DAL. This ‘Common’ class is only used inside the component, for that I declare this class as internal.

// Common.cs
using System;
using System.Data;
using System.Data.SqlClient;

namespace Northwind.Dal
{
    // Common DAL related work.
    internal class Common : IDisposable
    {
        private static string _connectionString;
        private SqlConnection _connection;
        private SqlCommand _command;
        private SqlDataReader _reader;
        private bool _isDisposed;

        static Common()
        {
            // It will hold the connection string when the class loads.
            _connectionString = System.Web.Configuration.WebConfigurationManager.ConnectionStrings
                ["NorthwindConnectionString"].ConnectionString;
        }

        public Common()
        {
		// Initialised the connection string for every object.
            this._connection = new SqlConnection(_connectionString);

            this._command = new SqlCommand()
            {
                Connection = this._connection,
                CommandType = CommandType.Text
            };
            this._isDisposed = false;
        }

        // Gets the state of the object weather it is disposed or not.
        protected bool IsDisposed
        {
            get
            {
                lock (this) // Make it thread safe.
                {
                    return this._isDisposed;
                }
            }
        }

        public SqlDataReader ExecuteReader(string query)
        {
            if (!this.IsDisposed) // Check if the object is live or disposed.
            {
                this._command.CommandText = query;

                this._connection.Open();
                this._reader = _command.ExecuteReader(CommandBehavior.CloseConnection);

                return this._reader;
            }
            else
            {
                throw new ObjectDisposedException("Common class object is already disposed.");
            }
        }

        public SqlDataReader ExecuteReader(string query, params  SqlParameter[] parameters)
        {
            if (!this.IsDisposed)
            {
                this._command.CommandText = query;
                foreach (SqlParameter parameter in parameters)
                {
                    this._command.Parameters.Add(parameter);
                }

                this._connection.Open();
                this._reader = _command.ExecuteReader(CommandBehavior.CloseConnection);

                return _reader;
            }
            else
            {
                throw new ObjectDisposedException("Common class object is already disposed.");
            }
        }

        public int ExecuteNonQuery(string query)
        {
            if (!this.IsDisposed)
            {
                this._command.CommandText = query;

                int effectedRecords = 0;
                try
                {
                    this._connection.Open();
                    effectedRecords = this._command.ExecuteNonQuery();
                }
                finally
                {
                    this._connection.Close();
                }

                return effectedRecords;
            }
            else
            {
                throw new ObjectDisposedException("Common class object is already disposed.");
            }
        }

        public int ExecuteNonQuery(string query, params SqlParameter[] parameters)
        {
            if (!this.IsDisposed)
            {
                this._command.CommandText = query;
                foreach (SqlParameter parameter in parameters)
                {
                    this._command.Parameters.Add(parameter);
                }

                int effectedRecords = 0;
                try
                {
                    this._connection.Open();
                    effectedRecords = this._command.ExecuteNonQuery();
                }
                finally
                {
                    this._connection.Close();
                }

                return effectedRecords;
            }
            else
            {
                throw new ObjectDisposedException("Common class object is already disposed.");
            }
        }

        public object ExecuteScalar(string query)
        {
            if (!this.IsDisposed)
            {
                this._command.CommandText = query;

                this._connection.Open();
                object scalarValue = this._command.ExecuteScalar();
                this._connection.Close();

                return scalarValue;
            }
            else
            {
                throw new ObjectDisposedException("Common class object is already disposed.");
            }
        }

        public object ExecuteScalar(string query, params SqlParameter[] parameters)
        {
            if (!this.IsDisposed)
            {
                this._command.CommandText = query;
                foreach (SqlParameter parameter in parameters)
                {
                    this._command.Parameters.Add(parameter);
                }

                object scalarValue = null;
                try
                {
                    this._connection.Open();
                    scalarValue = this._command.ExecuteScalar();
                }
                finally
                {
                    this._connection.Close();
                }

                return scalarValue;
            }
            else
            {
                throw new ObjectDisposedException("Common class object is already disposed.");
            }
        }

        public void Dispose()
        {
            lock (this) // Make it thread safe.
            {
                if (!this.IsDisposed)
                {
                    this.CleanUp();
                    this._isDisposed = true;
                    GC.SuppressFinalize(this); // No longer need finalizing.
                }
            }
        }

        // Derive class can override this method to implement specific logic.
        protected virtual void CleanUp()
        {
            if (this._connection != null)
            {
                this._connection.Dispose();
            }

            if (this._command != null)
            {
                this._command.Dispose();
            }

            if (this._reader != null)
            {
                this._reader.Dispose();
            }
        }
    }
}

This common class is used by the Customer class which is implementing the data access logic of customer entity.

// ICustomer.cs
using System;
using System.Collections.Generic;
using Poco = Northwind.Poco;

namespace Northwind.Dal
{
    public interface ICustomer : IDisposable
    {
        IList GetData();
        IList GetData(string customerId);
        int Create(Poco::ICustomer newCustomer);
        int Update(Poco::ICustomer existingCustomer);
        int Delete(string customerId);
    }
}

// Customer.cs
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Text;
using Northwind.Dal.Extentions;
using Poco = Northwind.Poco;

namespace Northwind.Dal
{
    public class Customer : ICustomer
    {
        private Common _common;
        private bool _isDisposed;

        public Customer()
        {
            this._common = new Common();
            this._isDisposed = false;
        }

        protected bool IsDisposed
        {
            get
            {
                lock (this) // Make it thread safe.
                {
                    return this._isDisposed;
                }
            }
        }

        protected virtual void CleanUp()
        {
            this._common.Dispose();
        }

        public IList GetData()
        {
            if (!this.IsDisposed) // Check if the object is live or disposed.
            {
                List customers;

                SqlDataReader reader = this._common.ExecuteReader("SELECT * FROM Customers");
                customers = reader.GetCustomers(); // Thanks to extention method.

                return customers;
            }
            else
            {
                throw new ObjectDisposedException("Dal.Customer object is already disposed.");
            }
        }

        public IList GetData(string customerId)
        {
            if (!this.IsDisposed)
            {
                SqlParameter customerIdParameter = new SqlParameter("@CustomerID", SqlDbType.NChar, 5)
                {
                    Value = customerId,
                    Direction = ParameterDirection.Input
                };

                List customers;
                SqlDataReader reader = this._common.ExecuteReader
                    ("SELECT * FROM Customers WHERE CustomerID = @CustomerID", customerIdParameter);
                customers = reader.GetCustomers(); // Thanks to extention method.

                return customers;
            }
            else
            {
                throw new ObjectDisposedException("Dal.Customer object is already disposed.");
            }
        }

        public int Create(Poco::ICustomer newCustomer)
        {
            if (!this.IsDisposed)
            {
                SqlParameter[] parameters = GetParameters(newCustomer);
                StringBuilder insertCommand = GetInsertCommand();
                int effectedRecords = this._common.ExecuteNonQuery(insertCommand.ToString(), parameters);

                return effectedRecords;
            }
            else
            {
                throw new ObjectDisposedException("Dal.Customer object is already disposed.");
            }
        }

        public int Update(Poco::ICustomer existingCustomer)
        {
            if (!this.IsDisposed)
            {
                SqlParameter[] parameters = GetParameters(existingCustomer);
                StringBuilder updateCommand = GetUpdateCommand();
                int effectedRecords = this._common.ExecuteNonQuery(updateCommand.ToString(), parameters);

                return effectedRecords;
            }
            else
            {
                throw new ObjectDisposedException("Dal.Customer object is already disposed.");
            }
        }

        public int Delete(string customerId)
        {
            if (!this.IsDisposed)
            {
                SqlParameter customerIdParameter = new SqlParameter("@CustomerID", SqlDbType.NChar, 5)
                {
                    Value = customerId,
                    Direction = ParameterDirection.Input
                };

                int effectedRecords = this._common.ExecuteNonQuery
                    ("DELETE Customers WHERE CustomerID = @CustomerId", customerIdParameter);

                return effectedRecords;
            }
            else
            {
                throw new ObjectDisposedException("Dal.Customer object is already disposed.");
            }
        }

        public void Dispose()
        {
            lock (this) // Make it thread safe.
            {
                if (!this.IsDisposed)
                {
                    this.CleanUp();
                    this._isDisposed = true;
                    GC.SuppressFinalize(this); // No longer need finalizing.
                }
            }
        }

        private static SqlParameter[] GetParameters(Poco::ICustomer newCustomer)
        {
            SqlParameter customerIdParameter = new SqlParameter("@CustomerID", SqlDbType.NChar, 5)
            {
                Value = newCustomer.CustomerId != null ? newCustomer.CustomerId : string.Empty,
                Direction = ParameterDirection.Input
            };

            SqlParameter companyNameParameter = new SqlParameter("@CompanyName", SqlDbType.NVarChar, 40)
            {
                Value = newCustomer.CompanyName != null ? newCustomer.CompanyName : string.Empty,
                Direction = ParameterDirection.Input
            };

            SqlParameter contactNameParameter = new SqlParameter("@ContactName", SqlDbType.NVarChar, 30)
            {
                Value = newCustomer.ContactName != null ? newCustomer.ContactName : string.Empty,
                Direction = ParameterDirection.Input
            };

            SqlParameter contactTitleParameter = new SqlParameter("@ContactTitle", SqlDbType.NVarChar, 30)
            {
                Value = newCustomer.ContactTitle != null ? newCustomer.ContactTitle : string.Empty,
                Direction = ParameterDirection.Input
            };

            SqlParameter addressParameter = new SqlParameter("@Address", SqlDbType.NVarChar, 60)
            {
                Value = newCustomer.Address != null ? newCustomer.Address : string.Empty,
                Direction = ParameterDirection.Input
            };

            SqlParameter cityParameter = new SqlParameter("@City", SqlDbType.NVarChar, 15)
            {
                Value = newCustomer.City != null ? newCustomer.City : string.Empty,
                Direction = ParameterDirection.Input
            };

            SqlParameter regionParameter = new SqlParameter("@Region", SqlDbType.NVarChar, 15)
            {
                Value = newCustomer.Region != null ? newCustomer.Region : string.Empty,
                Direction = ParameterDirection.Input
            };

            SqlParameter postalCodeParameter = new SqlParameter("@PostalCode", SqlDbType.NVarChar, 10)
            {
                Value = newCustomer.PostalCode != null ? newCustomer.PostalCode : string.Empty,
                Direction = ParameterDirection.Input
            };

            SqlParameter countryParameter = new SqlParameter("@Country", SqlDbType.NVarChar, 15)
            {
                Value = newCustomer.Country != null ? newCustomer.Country : string.Empty,
                Direction = ParameterDirection.Input
            };

            SqlParameter phoneParameter = new SqlParameter("@Phone", SqlDbType.NVarChar, 24)
            {
                Value = newCustomer.Phone != null ? newCustomer.Phone : string.Empty,
                Direction = ParameterDirection.Input
            };

            SqlParameter faxParameter = new SqlParameter("@Fax", SqlDbType.NVarChar, 24)
            {
                Value = newCustomer.Fax != null ? newCustomer.Fax : string.Empty,
                Direction = ParameterDirection.Input
            };

            SqlParameter[] parameters =
            {
                customerIdParameter,
                companyNameParameter,
                contactNameParameter,
                contactTitleParameter,
                addressParameter,
                cityParameter,
                countryParameter,
                postalCodeParameter,
                phoneParameter,
                faxParameter,
                regionParameter
            };

            return parameters;
        }

        private static StringBuilder GetInsertCommand()
        {
            StringBuilder insertCommand = new StringBuilder();

            insertCommand = insertCommand.Append("INSERT INTO Customers");
            insertCommand = insertCommand.Append("(CustomerId, CompanyName, ContactName, ContactTitle, ");
            insertCommand = insertCommand.Append("Country, Address, City, Region, PostalCode, Phone, Fax)");
            insertCommand = insertCommand.Append("VALUES (@CustomerId, @CompanyName, @ContactName,");
            insertCommand = insertCommand.Append("@ContactTitle, @Country, @Address, @City, @Region, ");
            insertCommand = insertCommand.Append("@PostalCode, @Phone, @Fax)");

            return insertCommand;
        }

        private static StringBuilder GetUpdateCommand()
        {
            StringBuilder insertCommand = new StringBuilder();

            insertCommand = insertCommand.Append("UPDATE Customers ");
            insertCommand = insertCommand.Append("SET CompanyName = @CompanyName, ");
            insertCommand = insertCommand.Append("ContactName = @ContactName, ContactTitle = @ContactTitle, ");
            insertCommand = insertCommand.Append("Country = @Country, Address = @Address, City = @City, ");
            insertCommand = insertCommand.Append("Region = @Region, PostalCode = @PostalCode, Phone = @Phone, Fax = @Fax ");
            insertCommand = insertCommand.Append("WHERE CustomerId = @CustomerId");

            return insertCommand;
        }
    }
}

Another class in the DAL component is a static one and this is for extension method for ‘SqlDataReader’ class. Extension method is a procedure by which we can extents an existing type with non object oriented manner.

// SqlDataReaderExtention.cs
using System.Collections.Generic;
using System.Data.SqlClient;

namespace Northwind.Dal.Extentions
{
    // Having the extention method of SqlDataReader.
    internal static class SqlDataReaderExtention
    {
        // Get customer collection from data reader.
        public static List GetCustomers(this SqlDataReader reader)
        {
            List customers = new List();

            if (reader.HasRows)
            {
                int customerIdOrdinal = reader.GetOrdinal("CustomerID");
                int companyNameOrdinal = reader.GetOrdinal("CompanyName");
                int contactNameOrdinal = reader.GetOrdinal("ContactName");
                int contactTitleOrdinal = reader.GetOrdinal("ContactTitle");
                int addressOrdinal = reader.GetOrdinal("Address");
                int cityOrdinal = reader.GetOrdinal("City");
                int regionOrdinal = reader.GetOrdinal("Region");
                int postalCodeOrdinal = reader.GetOrdinal("PostalCode");
                int countryOrdinal = reader.GetOrdinal("Country");
                int phoneOrdinal = reader.GetOrdinal("Phone");
                int faxOrdinal = reader.GetOrdinal("Fax");

                while (reader.Read())
                {
                    Northwind.Poco.ICustomer customer = new Northwind.Poco.Customer();

                    customer.CustomerId = GetStringOrEmpty(reader, customerIdOrdinal);
                    customer.CompanyName = GetStringOrEmpty(reader, companyNameOrdinal);
                    customer.ContactName = GetStringOrEmpty(reader, contactNameOrdinal);
                    customer.ContactTitle = GetStringOrEmpty(reader, contactTitleOrdinal);
                    customer.Address = GetStringOrEmpty(reader, addressOrdinal);
                    customer.City = GetStringOrEmpty(reader, cityOrdinal);
                    customer.Region = GetStringOrEmpty(reader, regionOrdinal);
                    customer.PostalCode = GetStringOrEmpty(reader, postalCodeOrdinal);
                    customer.Country = GetStringOrEmpty(reader, countryOrdinal);
                    customer.Phone = GetStringOrEmpty(reader, phoneOrdinal);
                    customer.Fax = GetStringOrEmpty(reader, faxOrdinal);

                    customers.Add(customer);
                }
            }

            return customers;
        }

        // Return the string value if present or empty string if null.
        private static string GetStringOrEmpty(SqlDataReader reader, int ordinal)
        {
            return !reader.IsDBNull(ordinal) ? reader.GetString(ordinal) : string.Empty;
        }
    }
}

The BLL Layer
Every mid level to high level application has some business logic. In three layered architecture we writes business logic in BLL. In simple term, if data access layer has the responsibility to access the data source then data manipulation is done in business logic layer. In the case of data retrieval it makes the data ready for UI and in the case of insert, update and delete it makes the data ready for data access layer. So it actually stay just between the DAL and UI.

Though there is not much business logic for this demo application. But in real world there are usually much to do in BLL.

// ICustomer.cs
using System;
using System.Collections.Generic;
using Poco = Northwind.Poco;

namespace Northwind.Bll
{
    public interface ICustomer : IDisposable
    {
        IList GetData();
        IList GetData(string customerId);
        int Create(Poco::ICustomer newCustomer);
        int Update(Poco::ICustomer existingCustomer);
        int Delete(string customerId);
    }
}

// Customer.cs
using System;
using System.Collections.Generic;
using Dal = Northwind.Dal;
using Poco = Northwind.Poco;

namespace Northwind.Bll
{
    public class Customer : ICustomer
    {
        private Dal::ICustomer _customer; // _customer is a Interface type to decouple the DAL.
        private bool _isDisposed;

        public Customer()
        {
            this._customer = new Dal::Customer();
            this._isDisposed = false;
        }

        protected bool IsDisposed
        {
            get
            {
                lock (this) // Make it thread safe.
                {
                    return this._isDisposed;
                }
            }
        }

        protected virtual void CleanUp()
        {
            this._customer.Dispose();
        }

        public IList GetData()
        {
            if (!this.IsDisposed) // Check if the object is live or disposed.
            {
                return this._customer.GetData();
            }
            else
            {
                throw new ObjectDisposedException("Bll.Customer class object is already disposed.");
            }
        }

        public IList GetData(string customerId)
        {
            if (!this.IsDisposed)
            {
                return this._customer.GetData(customerId);
            }
            else
            {
                throw new ObjectDisposedException("Bll.Customer class object is already disposed.");
            }
        }

        public int Create(Poco::ICustomer newCustomer)
        {
            if (!this.IsDisposed)
            {
                return this._customer.Create(newCustomer);
            }
            else
            {
                throw new ObjectDisposedException("Bll.Customer class object is already disposed.");
            }
        }

        public int Update(Poco::ICustomer existingCustomer)
        {
            if (!this.IsDisposed)
            {
                return this._customer.Update(existingCustomer);
            }
            else
            {
                throw new ObjectDisposedException("Bll.Customer class object is already disposed.");
            }
        }

        public int Delete(string customerId)
        {
            if (!this.IsDisposed)
            {
                return this._customer.Delete(customerId);
            }
            else
            {
                throw new ObjectDisposedException("Bll.Customer class object is already disposed.");
            }
        }

        public void Dispose()
        {
            lock (this) // Make it thread safe.
            {
                if (!this.IsDisposed)
                {
                    this.CleanUp();
                    this._isDisposed = true;
                    GC.SuppressFinalize(this); // No longer need finalizing.
                }
            }
        }
    }
}

In the DAL and BLL I also do deterministic life cycle memory management.

The UI Layer
The main responsibility of UI is to render the result and the user interaction. I have implemented the validation logic in UI using ASP.NET validator control. There are two aspx pages. One to show the customer data in a ‘GridView’ control and another is edit, delete and insert data.

Customer.aspx

<%@ Page Title="Customer" Language="C#" AutoEventWireup="true" CodeBehind="Customer.aspx.cs"
    Inherits="Northwind.Ui.Customer" MasterPageFile="~/Site.Master" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
    <asp:GridView ID="grvCustomers" runat="server">
        <Columns>
            <asp:HyperLinkField DataTextField="CompanyName" DataNavigateUrlFields="CustomerId"
                DataNavigateUrlFormatString="~/ManageCustomer.aspx?CustomerId={0}" HeaderText="Company Name" />
            <asp:BoundField DataField="ContactName" HeaderText="Contact Name" />
            <asp:BoundField DataField="ContactTitle" HeaderText="Contact Title" />
            <asp:BoundField DataField="Address" HeaderText="Address" />
            <asp:BoundField DataField="Country" HeaderText="Country" />
            <asp:BoundField DataField="Region" HeaderText="Region" />
            <asp:BoundField DataField="City" HeaderText="City" />
            <asp:BoundField DataField="PostalCode" HeaderText="Postal Code" />
            <asp:BoundField DataField="Phone" HeaderText="Phone" />
            <asp:BoundField DataField="Fax" HeaderText="Fax" />
        </Columns>
    </asp:GridView>
</asp:Content>

// Customer.aspx.cs
using System;
using System.Collections.Generic;
using System.Web.UI;

namespace Northwind.Ui
{
    public partial class Customer : Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            IList customers;

            // customer is an interface type to decouple the BLL.
            using (Northwind.Bll.ICustomer customer = new Northwind.Bll.Customer())
            {
                customers = customer.GetData();
            }

            if (customers != null)
            {
                this.grvCustomers.DataSource = customers;
                this.grvCustomers.DataBind();
            }
        }
    }
}

In manage customer page I have used a ‘DetailsView’ control to insert update and delete.

ManageCustomer.aspx

<%@ Page Title="Manage Customer" Language="C#" AutoEventWireup="true" CodeBehind="ManageCustomer.aspx.cs"
    Inherits="Northwind.Ui.ManageCustomer" MasterPageFile="~/Site.Master" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
    <asp:ValidationSummary ID="ValidationSummary1" runat="server" HeaderText="Please solve the following errors:"
        ShowMessageBox="True" />
    <asp:DetailsView ID="dtlvCustomer" runat="server" OnModeChanging="dtlvCustomer_ModeChanging"
        OnItemDeleting="dtlvCustomer_ItemDeleting" OnItemInserting="dtlvCustomer_ItemInserting"
        OnItemUpdating="dtlvCustomer_ItemUpdating" DataKeyNames="CustomerId">
        <Fields>
            <asp:TemplateField HeaderText="Customer Id">
                <EditItemTemplate>
                    <asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("CustomerId") %>' ReadOnly="true"></asp:TextBox>
                    <asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="TextBox1"
                        Display="Dynamic" ErrorMessage="Please enter customer id" SetFocusOnError="True">*</asp:RequiredFieldValidator>
                </EditItemTemplate>
                <InsertItemTemplate>
                    <asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("CustomerId") %>'></asp:TextBox>
                    <asp:RequiredFieldValidator ID="RequiredFieldValidator2" runat="server" ControlToValidate="TextBox1"
                        Display="Dynamic" ErrorMessage="Please enter customer id" SetFocusOnError="True">*</asp:RequiredFieldValidator>
                </InsertItemTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label1" runat="server" Text='<%# Bind("CustomerId") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Company Name">
                <EditItemTemplate>
                    <asp:TextBox ID="TextBox2" runat="server" Text='<%# Bind("CompanyName") %>'></asp:TextBox>
                    <asp:RequiredFieldValidator ID="RequiredFieldValidator3" runat="server" ControlToValidate="TextBox2"
                        Display="Dynamic" ErrorMessage="Please enter company name" SetFocusOnError="True">*</asp:RequiredFieldValidator>
                </EditItemTemplate>
                <InsertItemTemplate>
                    <asp:TextBox ID="TextBox2" runat="server" Text='<%# Bind("CompanyName") %>'></asp:TextBox>
                    <asp:RequiredFieldValidator ID="RequiredFieldValidator4" runat="server" ControlToValidate="TextBox2"
                        Display="Dynamic" ErrorMessage="Please enter company name" SetFocusOnError="True">*</asp:RequiredFieldValidator>
                </InsertItemTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label2" runat="server" Text='<%# Bind("CompanyName") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Contact Name">
                <EditItemTemplate>
                    <asp:TextBox ID="TextBox3" runat="server" Text='<%# Bind("ContactName") %>'></asp:TextBox>
                    <asp:RequiredFieldValidator ID="RequiredFieldValidator5" runat="server" ControlToValidate="TextBox3"
                        Display="Dynamic" ErrorMessage="Please enter contact name" SetFocusOnError="True">*</asp:RequiredFieldValidator>
                </EditItemTemplate>
                <InsertItemTemplate>
                    <asp:TextBox ID="TextBox3" runat="server" Text='<%# Bind("ContactName") %>'></asp:TextBox>
                    <asp:RequiredFieldValidator ID="RequiredFieldValidator6" runat="server" ControlToValidate="TextBox3"
                        Display="Dynamic" ErrorMessage="Please enter contact name" SetFocusOnError="True">*</asp:RequiredFieldValidator>
                </InsertItemTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label3" runat="server" Text='<%# Bind("ContactName") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Contact Title">
                <EditItemTemplate>
                    <asp:TextBox ID="TextBox4" runat="server" Text='<%# Bind("ContactTitle") %>'></asp:TextBox>
                </EditItemTemplate>
                <InsertItemTemplate>
                    <asp:TextBox ID="TextBox4" runat="server" Text='<%# Bind("ContactTitle") %>'></asp:TextBox>
                </InsertItemTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label4" runat="server" Text='<%# Bind("ContactTitle") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Country">
                <EditItemTemplate>
                    <asp:TextBox ID="TextBox6" runat="server" Text='<%# Bind("Country") %>'></asp:TextBox>
                    <asp:RequiredFieldValidator ID="RequiredFieldValidator7" runat="server" ControlToValidate="TextBox6"
                        Display="Dynamic" ErrorMessage="Please enter country" SetFocusOnError="True">*</asp:RequiredFieldValidator>
                </EditItemTemplate>
                <InsertItemTemplate>
                    <asp:TextBox ID="TextBox6" runat="server" Text='<%# Bind("Country") %>'></asp:TextBox>
                    <asp:RequiredFieldValidator ID="RequiredFieldValidator8" runat="server" ControlToValidate="TextBox6"
                        Display="Dynamic" ErrorMessage="Please enter country" SetFocusOnError="True">*</asp:RequiredFieldValidator>
                </InsertItemTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label6" runat="server" Text='<%# Bind("Country") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Region">
                <EditItemTemplate>
                    <asp:TextBox ID="TextBox7" runat="server" Text='<%# Bind("Region") %>'></asp:TextBox>
                </EditItemTemplate>
                <InsertItemTemplate>
                    <asp:TextBox ID="TextBox7" runat="server" Text='<%# Bind("Region") %>'></asp:TextBox>
                </InsertItemTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label7" runat="server" Text='<%# Bind("Region") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="City">
                <EditItemTemplate>
                    <asp:TextBox ID="TextBox8" runat="server" Text='<%# Bind("City") %>'></asp:TextBox>
                </EditItemTemplate>
                <InsertItemTemplate>
                    <asp:TextBox ID="TextBox8" runat="server" Text='<%# Bind("City") %>'></asp:TextBox>
                </InsertItemTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label8" runat="server" Text='<%# Bind("City") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Postal Code">
                <EditItemTemplate>
                    <asp:TextBox ID="TextBox9" runat="server" Text='<%# Bind("PostalCode") %>'></asp:TextBox>
                </EditItemTemplate>
                <InsertItemTemplate>
                    <asp:TextBox ID="TextBox9" runat="server" Text='<%# Bind("PostalCode") %>'></asp:TextBox>
                </InsertItemTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label9" runat="server" Text='<%# Bind("PostalCode") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Phone">
                <EditItemTemplate>
                    <asp:TextBox ID="TextBox10" runat="server" Text='<%# Bind("Phone") %>'></asp:TextBox>
                </EditItemTemplate>
                <InsertItemTemplate>
                    <asp:TextBox ID="TextBox10" runat="server" Text='<%# Bind("Phone") %>'></asp:TextBox>
                </InsertItemTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label10" runat="server" Text='<%# Bind("Phone") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Fax">
                <EditItemTemplate>
                    <asp:TextBox ID="TextBox11" runat="server" Text='<%# Bind("Fax") %>'></asp:TextBox>
                </EditItemTemplate>
                <InsertItemTemplate>
                    <asp:TextBox ID="TextBox11" runat="server" Text='<%# Bind("Fax") %>'></asp:TextBox>
                </InsertItemTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label11" runat="server" Text='<%# Bind("Fax") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Address">
                <EditItemTemplate>
                    <asp:TextBox ID="TextBox12" runat="server" SkinID="MultilineTextBox" TextMode="MultiLine"
                        Text='<%# Bind("Address") %>' />
                </EditItemTemplate>
                <InsertItemTemplate>
                    <asp:TextBox ID="TextBox12" runat="server" SkinID="MultilineTextBox" TextMode="MultiLine"
                        Text='<%# Bind("Address") %>' />
                </InsertItemTemplate>
                <ItemTemplate>
                    <asp:Label ID="Label5" runat="server" Text='<%# Bind("Address") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField>
                <ItemTemplate>
                    <div class="RightAlign">
                        <asp:Button ID="btnEdit" runat="server" Text="Edit" CommandName="Edit" CausesValidation="false" />
                        <asp:Button ID="btnDelete" runat="server" Text="Delete" CommandName="Delete" CausesValidation="false" />
                        <asp:Button ID="btnNew" runat="server" Text="New" CommandName="New" CausesValidation="false" />
                    </div>
                </ItemTemplate>
                <EditItemTemplate>
                    <div class="RightAlign">
                        <asp:Button ID="btnUpdate" runat="server" Text="Update" CommandName="Update" />
                        <asp:Button ID="btnCancel" runat="server" Text="Cancel" CommandName="Cancel" CausesValidation="false" />
                    </div>
                </EditItemTemplate>
                <InsertItemTemplate>
                    <div class="RightAlign">
                        <asp:Button ID="btnInsert" runat="server" Text="Insert" CommandName="Insert" />
                        <asp:Button ID="btnCancel" runat="server" Text="Cancel" CommandName="Cancel" CausesValidation="false" />
                    </div>
                </InsertItemTemplate>
            </asp:TemplateField>
        </Fields>
    </asp:DetailsView>
</asp:Content>
// ManageCustomer.aspx.cs
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Web.UI;
using System.Web.UI.WebControls;
using Bll = Northwind.Bll;
using Poco = Northwind.Poco;

namespace Northwind.Ui
{
    public partial class ManageCustomer : Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!this.IsPostBack)
            {
                if (Request.QueryString["CustomerId"] != null)
                {
                    IList customers = this.GetCustomers();
                    this.BindDetailsView(customers);
                }
            }
        }

        protected void dtlvCustomer_ModeChanging(object sender, DetailsViewModeEventArgs e)
        {
            dtlvCustomer.ChangeMode(e.NewMode);
            IList customers = this.GetCustomers();
            this.BindDetailsView(customers);
        }

        protected void dtlvCustomer_ItemInserting(object sender, DetailsViewInsertEventArgs e)
        {
            if (this.IsValid)
            {
                try
                {
                    Poco::ICustomer newCustomer = this.GetCustomerObject();
                    using (Bll::ICustomer blCustomer = new Bll::Customer())
                    {
                        blCustomer.Create(newCustomer);
                    }

                    Response.Redirect("~/Customer.aspx"); // Go and show all data.
                }
                catch (SqlException ex)
                {
                    Response.Write(ex.ToString());
                }
                catch (ObjectDisposedException ex)
                {
                    Response.Write(ex.ToString());
                }
                catch (Exception ex)
                {
                    Response.Write(ex.ToString());
                }
            }
        }

        protected void dtlvCustomer_ItemUpdating(object sender, DetailsViewUpdateEventArgs e)
        {
            if (this.IsValid)
            {
                try
                {
                    Poco::ICustomer existingCustomer = this.GetCustomerObject();
                    using (Bll::ICustomer blCustomer = new Bll::Customer())
                    {
                        blCustomer.Update(existingCustomer);
                    }

                    // Back to read only view.
                    dtlvCustomer.ChangeMode(DetailsViewMode.ReadOnly);
                    IList customers = this.GetCustomers();
                    this.BindDetailsView(customers);
                }
                catch (SqlException ex)
                {
                    Response.Write(ex.ToString());
                }
                catch (ObjectDisposedException ex)
                {
                    Response.Write(ex.ToString());
                }
                catch (Exception ex)
                {
                    Response.Write(ex.ToString());
                }
            }
        }

        protected void dtlvCustomer_ItemDeleting(object sender, DetailsViewDeleteEventArgs e)
        {
            try
            {
                using (Bll::ICustomer blCustomer = new Bll::Customer())
                {
                    if (Request.QueryString["CustomerId"] != null)
                    {
                        blCustomer.Delete(Request.QueryString["CustomerId"]);
                    }

                    Response.Redirect("~/Customer.aspx"); // Go and show all data.
                }
            }
            catch (SqlException ex)
            {
                Response.Write(ex.ToString());
            }
            catch (ObjectDisposedException ex)
            {
                Response.Write(ex.ToString());
            }
            catch (Exception ex)
            {
                Response.Write(ex.ToString());
            }
        }

        private IList GetCustomers()
        {
            IList customers;

            using (Northwind.Bll.ICustomer customer = new Northwind.Bll.Customer())
            {
                customers = customer.GetData(Request.QueryString["CustomerId"]);
            }

            return customers;
        }

        private void BindDetailsView(IList customers)
        {
            if (customers != null)
            {
                this.dtlvCustomer.DataSource = customers;
                this.dtlvCustomer.DataBind();
            }
        }

        private Poco::ICustomer GetCustomerObject()
        {
            // Create customer object with data from details view.
            Poco::ICustomer newCustomer = new Poco::Customer()
            {
                CustomerId = ((TextBox)dtlvCustomer.Rows[0].Cells[1].FindControl("TextBox1")) != null
                    ? ((TextBox)dtlvCustomer.Rows[0].Cells[1].FindControl("TextBox1")).Text : string.Empty,
                CompanyName = ((TextBox)dtlvCustomer.Rows[1].Cells[1].FindControl("TextBox2")) != null
                    ? ((TextBox)dtlvCustomer.Rows[1].Cells[1].FindControl("TextBox2")).Text : string.Empty,
                ContactName = ((TextBox)dtlvCustomer.Rows[2].Cells[1].FindControl("TextBox3")) != null
                    ? ((TextBox)dtlvCustomer.Rows[2].Cells[1].FindControl("TextBox3")).Text : string.Empty,
                ContactTitle = ((TextBox)dtlvCustomer.Rows[3].Cells[1].FindControl("TextBox4")) != null
                    ? ((TextBox)dtlvCustomer.Rows[3].Cells[1].FindControl("TextBox4")).Text : string.Empty,
                Country = ((TextBox)dtlvCustomer.Rows[10].Cells[1].FindControl("TextBox6")) != null
                    ? ((TextBox)dtlvCustomer.Rows[10].Cells[1].FindControl("TextBox6")).Text : string.Empty,
                Region = ((TextBox)dtlvCustomer.Rows[4].Cells[1].FindControl("TextBox7")) != null
                    ? ((TextBox)dtlvCustomer.Rows[4].Cells[1].FindControl("TextBox7")).Text : string.Empty,
                City = ((TextBox)dtlvCustomer.Rows[5].Cells[1].FindControl("TextBox8")) != null
                    ? ((TextBox)dtlvCustomer.Rows[5].Cells[1].FindControl("TextBox8")).Text : string.Empty,
                PostalCode = ((TextBox)dtlvCustomer.Rows[6].Cells[1].FindControl("TextBox9")) != null
                    ? ((TextBox)dtlvCustomer.Rows[6].Cells[1].FindControl("TextBox9")).Text : string.Empty,
                Phone = ((TextBox)dtlvCustomer.Rows[7].Cells[1].FindControl("TextBox10")) != null
                    ? ((TextBox)dtlvCustomer.Rows[7].Cells[1].FindControl("TextBox10")).Text : string.Empty,
                Fax = ((TextBox)dtlvCustomer.Rows[8].Cells[1].FindControl("TextBox11")) != null
                    ? ((TextBox)dtlvCustomer.Rows[8].Cells[1].FindControl("TextBox11")).Text : string.Empty,
                Address = ((TextBox)dtlvCustomer.Rows[9].Cells[1].FindControl("TextBox12")) != null
                    ? ((TextBox)dtlvCustomer.Rows[9].Cells[1].FindControl("TextBox12")).Text : string.Empty
            };

            return newCustomer;
        }
    }
}

multilayered architecture

Is the design ok? Am I missing something? Please give comments.
Download full code here

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s