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

Layered Application Design Pattern

, 25 Sep 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
N-tier architecture of Project

Introduction

In critical application development, the most important thing is project architecture or project framework which comprises of various modules involved in request processing or business handling. 

If the framework is clear and robust, and completely designed with proper planning then its very easy to do the code and develop the particular requirement for business.   

For any application if a developer knows the project framework/architecture then 50% work is done after that we need to focus on functionality only and need not to worry about the inter connected business component involved in the project.   

N-tier architecture    

The most important layers in any project are: 

  1. Application Layer   
  2. Business Layer   
  3. Data Layer   

But the problem is if we use only these layer then these layers will communicate with each other directly which may result in tightly-coupled framework.Also we have to use same code for logging and error handling in various layers.   

Also if we want to use the common code logic then we need to write it in various layers.

So, for improving the same we use following layers: 

  1. Application Layer/Presentation Layer  
  2. Business Layer 
  3. Data Layer 
  4. Framework Layer 
  5. Service Proxies Layer   
  6. Services Layer   

 

Each Layer in Detail

1.Data Layer:  

This layer contains the Database connectivity i.e. data entities, data connections, etc.

Data access components in this data layer are responsible for exposing the data stored in these databases to the business layer.   

Lets implement our Data Layer. 

Add ADO.NET Entity Data Model name it as DataModel and name an entities as SampleEntities which will reflect as connection string name in app.config of DataLayer as shown in below. 

We can select what tables we want as an entities in our application. 

In attached code we are using only two tables GI_User and Log. We are now done with our Data Layer. 

app.config: 

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <connectionStrings>
    <add name="SampleEntities" 
       connectionString="metadata=res://*/DataModel.csdl|res://*/DataModel.ssdl|res://*/
         DataModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;
         Data Source=(local);Initial Catalog=Sample;User ID=uid;
         Password=pwd;MultipleActiveResultSets=True&quot;" 
       providerName="System.Data.EntityClient" />
  </connectionStrings>
</configuration>  

2.Business Layer:   

This layer contains the business processing logic.

All the CRUD operations goes here for example: 

Let's implement our Business Layer. First of all add DataLayer reference to Business Layer.

In this Layer we are validating the credentials provided by the end user with the values in our GI_User table.

For performing any operation with database we use LINQ (Language Integrated Query) and for accessing the table entities we need to make an object of the connection entities i.e., SampleEntities as shown in below code snippet. 

We are now done with our Business Layer too.  

namespace BusinessLayer
{
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.Objects;
    using System.Linq;
    using System.Web;
    using DataLayer;

    /// <summary>
    /// Authenticate class.
    /// </summary>
    public class Authenticate
    {
        /// <summary>
        /// AuthenticateUser used for authenticating an user.
        /// </summary>
        /// <param name="userName">User name provided by user.</param>
        /// <param name="password">Password provide by user.</param>
        /// <returns>Authentication status.</returns>
        public int AuthenticateUser(string userName, string password)
        {
            SampleEntities dataContext = new SampleEntities();
            return (from status in dataContext.GI_User
                    where status.UserName.Equals(userName) && status.Password.Equals(password)
                    select status.UserId).FirstOrDefault();
        }
    }
}

3.Framework Layer: 

This layer contains the common configurable items. All the logging mechanism, caching mechanism, etc., goes here. Time to implement our Framework layer which will consist all logging, mailing, and any other mechanism. For logging purpose we use log4net(read more on http://www.codeproject.com/Articles/140911/log4net-Tutorial). We declare some enumerations which are required for logging mechanism. For implementing the logging mechanism using log4net we have to add reference of log4net.dll.

Enums:

namespace Framework.Enums
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ComponentModel;
    using System.Runtime.Serialization;

    /// <summary>
    /// Framework Enumeration class.
    /// </summary>
    public static class Enums
    {
        /// <summary>
        /// Framework Enumeration ErrorLevel.
        /// </summary>
        [DataContract(Name = "ErrorLevel")]
        public enum ErrorLevel
        {
            /// <summary>
            /// ErrorLevel FATAL. 
            /// </summary>
            [EnumMember]
            FATAL = 0,

            /// <summary>
            /// ErrorLevel ERROR.
            /// </summary>
            [EnumMember]
            ERROR = 1,

            /// <summary>
            /// ErrorLevel WARN.
            /// </summary>
            [EnumMember]
            WARN = 2,

            /// <summary>
            /// ErrorLevel INFO.
            /// </summary>
            [EnumMember]
            INFO = 3,

            /// <summary>
            /// ErrorLevel DEBUG.
            /// </summary>
            [EnumMember]
            DEBUG = 4
        }
    }
}

Logger: 

[assembly: log4net.Config.XmlConfigurator(Watch = true)]
namespace Framework.Logging
{
    using System;
    using Framework.Enums;

    /// <summary>
    /// Logger class.
    /// </summary>
    public static class Logger
    {
        /// <summary>
        /// Logger field of Logger class.
        /// </summary>
        private static readonly log4net.ILog logger = log4net.LogManager.GetLogger(
          System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        /// <summary>
        /// Logs info to DB / File based on web.config settings.
        /// </summary>
        /// <param name="ex">Exception object</param>
        /// <param name="customMessage">Custom error message.</param>
        /// <param name="errorLevel">Exception error type.</param>
        public static void Log(Exception ex, string customMessage, Enums.ErrorLevel errorLevel)
        {
            switch (errorLevel)
            {
                case Enums.ErrorLevel.DEBUG:
                    logger.Debug(customMessage, ex);
                    break;
                case Enums.ErrorLevel.ERROR:
                    logger.Error(customMessage, ex);
                    break;
                case Enums.ErrorLevel.FATAL:
                    logger.Fatal(customMessage, ex);
                    break;
                case Enums.ErrorLevel.INFO:
                    logger.Info(customMessage, ex);
                    break;
                case Enums.ErrorLevel.WARN:
                    logger.Warn(customMessage, ex);
                    break;
                default:
                    logger.Error(customMessage, ex);
                    break;
            }
        }
    }
}

app.config:

<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="log4net" 
       type="log4net.Config.Log4NetConfigurationSectionHandler,Log4net"/>
  </configSections>
  <log4net>
    <root>
      <level value="DEBUG"/>
      <appender-ref ref="ADONetAppender"/>
    </root>
    <appender name="ADONetAppender" type="log4net.Appender.ADONetAppender">
      <bufferSize value="1"/>
      <connectionType value="System.Data.SqlClient.SqlConnection, 
         System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
      <connectionString value="server=(local); uid=uid; pwd=pwd; database=Sample"/>
      <commandText value="INSERT INTO Log ([Date],[Thread],[Level],[Logger],
         [Message],[Exception]) VALUES (@log_date, @thread, @log_level, 
         @logger, @message, @exception)"/>
      <parameter>
        <parameterName value="@log_date"/>
        <dbType value="DateTime"/>
        <layout type="log4net.Layout.RawTimeStampLayout"/>
      </parameter>
      <parameter>
        <parameterName value="@thread"/>
        <dbType value="String"/>
        <size value="255"/>
        <layout type="log4net.Layout.PatternLayout">
          <conversionPattern value="%thread"/>
        </layout>
      </parameter>
      <parameter>
        <parameterName value="@log_level"/>
        <dbType value="String"/>
        <size value="50"/>
        <layout type="log4net.Layout.PatternLayout">
          <conversionPattern value="%level"/>
        </layout>
      </parameter>
      <parameter>
        <parameterName value="@logger"/>
        <dbType value="String"/>
        <size value="255"/>
        <layout type="log4net.Layout.PatternLayout">
          <conversionPattern value="%logger"/>
        </layout>
      </parameter>
      <parameter>
        <parameterName value="@message"/>
        <dbType value="String"/>
        <size value="4000"/>
        <layout type="log4net.Layout.PatternLayout">
          <conversionPattern value="%message"/>
        </layout>
      </parameter>
      <parameter>
        <parameterName value="@exception"/>
        <dbType value="String"/>
        <size value="2000"/>
        <layout type="log4net.Layout.ExceptionLayout"/>
      </parameter>
    </appender>
  </log4net>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>

4.Services Layer:    

This layer is the bridge between Application Layer and Business Layer.

Application Layer communicates with Business Layer through this layer.

If we want we can also use this layer as a bridge between Business Layer and Data Layer. 

Basically this layer is a WCF layer.  

In this layer we can add third party services also.  

Add one abstract class and make it BaseService which consist all common code and all other service classes should inherit the BaseService. 

The Bridge between Application Layer and Business Layer is Services Layer so lets jump into Services Layer.

Add the reference of Business Layer. 

Add New item as WCF Service and name it as AuthenticationService which will result in AuthenticationService.svc and IAuthenticationService.cs

Declare AuthenticateUser(string userName,string password) in IAuthenticationService.cs interface and implement that interface method in AuthenticationService.svc clss which will access the business layer method.

BaseService.cs:

namespace Services
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using Framework.Logging;    

    /// <summary>
    /// BaseService Class.
    /// </summary>
    public abstract class BaseService
    {
        /// <summary>
        /// Used for logging the web service errors.
        /// </summary>
        /// <param name="ex">Exception generated.</param>
        /// <param name="customMessage">Custom message.</param>
        /// <param name="errorLevel">Error level.</param>
        /// <param name="errCode">Error code.</param>
        /// <returns>Fault exception.</returns>
        public System.ServiceModel.FaultException LogWcfError(Exception ex, 
          string customMessage, Framework.Enums.Enums.ErrorLevel errorLevel, string errCode)
        {
            Logger.Log(ex, customMessage, errorLevel);
            return new System.ServiceModel.FaultException(
              new System.ServiceModel.FaultReason(customMessage), 
              new System.ServiceModel.FaultCode(errCode));
        }
    }
}

web.config: 

<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,Log4net"/>
  </configSections>
  <log4net>
    <root>
      <level value="DEBUG"/>
      <appender-ref ref="ADONetAppender"/>
    </root>
    <appender name="ADONetAppender" type="log4net.Appender.ADONetAppender">
      <bufferSize value="1"/>
      <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, 
         Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
      <connectionString value="server=(local); uid=uid; pwd=pwd; database=Sample"/>
      <commandText value="INSERT INTO Log ([Date],[Thread],[Level],[Logger],
        [Message],[Exception]) VALUES (@log_date, @thread, @log_level, @logger, @message, @exception)"/>
      <parameter>
        <parameterName value="@log_date"/>
        <dbType value="DateTime"/>
        <layout type="log4net.Layout.RawTimeStampLayout"/>
      </parameter>
      <parameter>
        <parameterName value="@thread"/>
        <dbType value="String"/>
        <size value="255"/>
        <layout type="log4net.Layout.PatternLayout">
          <conversionPattern value="%thread"/>
        </layout>
      </parameter>
      <parameter>
        <parameterName value="@log_level"/>
        <dbType value="String"/>
        <size value="50"/>
        <layout type="log4net.Layout.PatternLayout">
          <conversionPattern value="%level"/>
        </layout>
      </parameter>
      <parameter>
        <parameterName value="@logger"/>
        <dbType value="String"/>
        <size value="255"/>
        <layout type="log4net.Layout.PatternLayout">
          <conversionPattern value="%logger"/>
        </layout>
      </parameter>
      <parameter>
        <parameterName value="@message"/>
        <dbType value="String"/>
        <size value="4000"/>
        <layout type="log4net.Layout.PatternLayout">
          <conversionPattern value="%message"/>
        </layout>
      </parameter>
      <parameter>
        <parameterName value="@exception"/>
        <dbType value="String"/>
        <size value="2000"/>
        <layout type="log4net.Layout.ExceptionLayout"/>
      </parameter>
    </appender>
  </log4net>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information, set the value below 
            to false and remove the metadata endpoint above before deployment -->
          <serviceMetadata httpGetEnabled="true"/>
          <!-- To receive exception details in faults for debugging purposes, 
            set the value below to true.  Set to false before deployment 
            to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
  <connectionStrings>
    <add name="SampleEntities" 
      connectionString="metadata=res://*/DataModel.csdl|res://*/DataModel.ssdl|
         res://*/DataModel.msl;provider=System.Data.SqlClient;provider connection string=
         &quot;Data Source=(local);Initial Catalog=Sample;User ID=uid;
         Password=pwd;MultipleActiveResultSets=True&quot;" 
      providerName="System.Data.EntityClient" />
  </connectionStrings>
</configuration>

5.Service Proxies Layer

Let's implement our Service Proxies Layer whose advantages we already discussed. 

Add reference of Services Layer.

Add service reference and click on Discover it will show the AuthenticationService so click on OK and it will add the service reference of AuthenticationService in Service Proxies layer.

It will automatically adds all the required information like binding,endpoint,etc. 

Basically this contains the services from Services layer.  

If we don't use this layer then suppose if anyone made some changes in any service and he forgot to check-in that updated service response then all other  users get an error.

Now if we use this layer then if anyone made any changes and check-in the same then if particular user wants that change he/she simply needs to update the proxies and build the solution.

For achieving this simply add one class library and name it as ServiceProxies.

app.config: 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_IAuthenticateService" />
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:9321/Classes/AuthenticateService.svc"
                binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IAuthenticateService"
                contract="AuthenticateService.IAuthenticateService" name="BasicHttpBinding_IAuthenticateService" />
        </client>
    </system.serviceModel>
</configuration>

6.Application Layer:  

This layer contains the presentation logic. 

The application consists of a series of forms (pages) with which the user interacts. Each form contains a number of fields that display output from lower layers and collect user input.

Add one class and make it BasePage which will inherit from System.Web.UI.Page and all other pages should inherit the BasePage

The advantage of having the BasePage is that we don't need to write some configurable items in all the pages like setting of culture, CSRF checking,etc. 

Now all the layers are implemented except our Application Layer.

Add references of Framework and Service Proxies layer.

Copy paste the <system.serviceModel> portion from Service Proxies layer's app.config to Application Layer's Web.config to access the implemented service.

Now just access the AuthenticationService's method by using the client object of the same which will in turn gives call to Business Layer's functionality.

We use logging mechanism in Application Layer only as of now for exception logging but if we want to log any informational messages then we can also do that.

The main logic of doing an exception logging in application layer is that ultimately we call business logic through services and if any one fails then it will caught in application layer's method from which the call has been made.

Also for logging any WCF errors/exception we used the same logging mechanism in Services Layer.

namespace ApplicationLayer.BasePage
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Globalization;
    using Framework.Logging;
    using System.Web.UI.WebControls;
    using System.Web.UI;
    using System.IO;

    /// <summary>
    /// BasePage class.
    /// </summary>
    public class BasePage : System.Web.UI.Page
    {
        /// <summary>
        /// OnInit predefined event.
        /// </summary>
        /// <param name="e">Event arguments.</param>
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            System.Threading.Thread.CurrentThread.CurrentCulture = 
                         new CultureInfo("en-US", true);
        }

        /// <summary>
        /// OnPreLoad predefined event.
        /// </summary>
        /// <param name="e">Event arguments.</param>
        protected override void OnPreLoad(System.EventArgs e)
        {
            base.OnPreLoad(e);
        }

        /// <summary>
        /// OnLoad predefined event.
        /// </summary>
        /// <param name="e">Event arguments.</param>
        protected override void OnLoad(EventArgs e)
        {

        }        

        /// <summary>
        /// LogError used for logging various types of information.
        /// </summary>
        /// <param name="ex">Exception occured.</param>
        /// <param name="customException">Custom exception text.</param>
        /// <param name="level">Error level.</param>
        public void LogError(Exception ex, string customException, 
                    Framework.Enums.Enums.ErrorLevel level)
        {
            Logger.Log(ex, customException, level);
        }
    }
}

References     

License

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

Share

About the Author

Rajan Patekar
Software Developer (Senior)
India India
Rajan, ASP.NET Developer at CMMI Level 5 version 1.3 company in Pune, India, is a radiant and rationalist person.
Other than coding he is having his interest in playing chess, pool and reading novels. Google+
Follow on   LinkedIn

Comments and Discussions

 
SuggestionAbout the logger Pinmemberkornakar7-Oct-13 0:40 
GeneralRe: About the logger PinprofessionalRajan Patekar9-Oct-13 3:52 
GeneralMy vote of 5 PinmemberCyclingFoodmanPA28-Sep-13 12:49 
GeneralRe: My vote of 5 PinprofessionalRajan Patekar29-Sep-13 5:46 
AnswerDownload link is working now PinprofessionalRajan Patekar25-Sep-13 8:52 
Questionplease fix the download link PinmembersamiraTeimooriiiiii25-Sep-13 4:42 
Questioncode download link not working... Pinmemberrobby96525-Sep-13 2:49 
Questioncode download link not working Pinmemberrobby96525-Sep-13 2:49 
Questioncode dowmload link not working PinmemberTridip Bhattacharjee24-Sep-13 21:11 
GeneralSeriously PinprofessionalPIEBALDconsult24-Sep-13 15:39 
GeneralMy vote of 4 PinmemberMember 1029476424-Sep-13 5:51 
GeneralRe: My vote of 4 PinprofessionalRajan Patekar27-Sep-13 1:44 
QuestionDownload link is not available PinmemberAsutosha24-Sep-13 4:12 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.141015.1 | Last Updated 25 Sep 2013
Article Copyright 2013 by Rajan Patekar
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid