Introduction
In critical application development, the most important thing is project architecture or project framework which comprises various modules involved in request processing or business handling.
If the framework is clear and robust, and completely designed with proper planning, then it's 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 worry about the inter connected business component involved in the project.
N-tier Architecture
The most important layers in any project are:
- Application Layer
- Business Layer
- Data Layer
But the problem is if we use only these layers, then these layers will communicate with each other directly which may result in tightly-coupled framework. Also, we have to use the 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 the following layers:
- Application Layer/Presentation Layer
- Business Layer
- Data Layer
- Framework Layer
- Service Proxies Layer
- 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.
Let's implement our Data Layer.
Add ADO.NET Entity Data Model, name it as DataModel
and name entities as SampleEntities
which will reflect as connection string name in app.config of DataLayer
as shown below.
We can select what tables we want as entities in our application.
In the attached code, we are using only two tables, GI_User
and Log
. We are now done with our Data Layer.
app.config:
="1.0"="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="
Data Source=(local);Initial Catalog=Sample;User ID=uid;
Password=pwd;MultipleActiveResultSets=True""
providerName="System.Data.EntityClient" />
</connectionStrings>
</configuration>
2. Business Layer
This layer contains the business processing logic.
All the CRUD operations go 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 the 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;
public class Authenticate
{
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 of all logging, mailing, and any other mechanism. For logging purposes, 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;
public static class Enums
{
[DataContract(Name = "ErrorLevel")]
public enum ErrorLevel
{
[EnumMember]
FATAL = 0,
[EnumMember]
ERROR = 1,
[EnumMember]
WARN = 2,
[EnumMember]
INFO = 3,
[EnumMember]
DEBUG = 4
}
}
}
Logger:
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
namespace Framework.Logging
{
using System;
using Framework.Enums;
public static class Logger
{
private static readonly log4net.ILog logger = log4net.LogManager.GetLogger(
System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
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:
="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 let's 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;
public abstract class BaseService
{
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:
="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>
<serviceMetadata httpGetEnabled="true"/>
<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=
"Data Source=(local);Initial Catalog=Sample;User ID=uid;
Password=pwd;MultipleActiveResultSets=True""
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 add 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 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:
="1.0"="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 get 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;
public class BasePage : System.Web.UI.Page
{
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
System.Threading.Thread.CurrentThread.CurrentCulture =
new CultureInfo("en-US", true);
}
protected override void OnPreLoad(System.EventArgs e)
{
base.OnPreLoad(e);
}
protected override void OnLoad(EventArgs e)
{
}
public void LogError(Exception ex, string customException,
Framework.Enums.Enums.ErrorLevel level)
{
Logger.Log(ex, customException, level);
}
}
}
References
History
- 25th September, 2013: Initial version