Creating N-Tier ASP.NET Web API application

This article walks through creating a N-Tier ASP.NET Web API application and Data layer using design patterns.

What is N-Tier architecture

“N-Tier architecture refers to the architecture of an application that has at least 3 logical layers -- or parts -- that are separate. Each layer interacts with only the layer directly below, and has specific function that it is responsible for”. [ Source: http://www.developerfusion.com/article/3058/boosting-your-net-application-performance/2/ ]

image

Overview : N-Tier architecture

Client Layer:  Client application is the service consumer, implements business logic and responsible for presentation.

Service Layer: The Service provider, provides access to data as Web service, acts as communication channel between clients and data layer. Clients interact with data layer through service layer.

Data Layer: Persistent storage of data using any technology i.e., SQL Server, Oracle and MySQL

What are Design Patterns

Design patterns are solutions to software design problems that are found again and again in real world application development. Patterns are about reusable designs and interactions of objects. Design patterns are grouped into Creational, Structural and Behavioural categories.

In this N-Tier architecture below listed patterns are implemented.

Façade: Provide a unified interface to a set of interfaces in a subsystem. Façade defines a higher-level interface that makes the subsystem easier to use. Service layer is an implementation of the Facade design pattern (single point of entry). Service layer is responsible for calling and managing business objects, data access objects and processing the standard business logic and validating incoming requests.

Dependency Injection: IContactsManagerRepository (Interface) from data layer is injected in ContactsManagerController (Service layer) class through constructor. Inversion of Control container Unity is used to map concrete implementation to interface in Global.asax.

DI allows removing hard-coded dependencies and making it possible to change them, whether at run-time or compile-time. [Refer: http://www.martinfowler.com/articles/injection.html

Request-Response: Request/Response is the most basic and common of the client-service interaction patterns. It is used when the client must have an immediate response or wants the service to complete a task without delay. Request/Response begins when the client establishes a connection to the service. Once a connection has been established, the client sends its request and waits for a response. The service processes the request as soon as it is received and returns a response over the same connection. [source: http://servicedesignpatterns.com/ClientServiceInteractions/RequestResponse]

Data Transfer Object: DTOs are simple objects that hold data without any behaviour, responsible for storage and retrieval of own data. DTOs used in model folder of the Service layer [Refer: http://martinfowler.com/eaaCatalog/dataTransferObject.html]

Factory: Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses. Implements the concept of factories and deals with the problem of creating objects (Employee) without specifying the exact class of object that will be created. This pattern is used in Data layer. Refer ContactsManagerFactory class in Entities folder.

Repository: Methods for retrieving domain objects should delegate to a specialized Repository object such that alternative storage implementations may be easily interchanged. Used in Data layer Repository and Implementation folders

Strategy: Allow algorithm selection at runtime. Encapsulate related algorithms and let the algorithm vary and evolve from the class using it. Separate the implementation from the delivery of its results.

In this architecture data layer is class library project and service layer i.e., ASP.NET Web API application that utilises data layer to interact with the underlying database.

N-Tier: Data layer implementation

Data layer is an implementation of Factory, Repository, DTO and Strategy patterns that consists of various layers as shown below.

image

In the Visual Studio Entities folder consists of ORM generated from database, POCOs generated using T4 template and Factory class that is responsible for creating a context to access entities with data.

Creating ORM

Creating the class library, adding folder is self explanatory. In order to add EF ORM model choose Entities folder context menu, then Add –> New Item from Add context menu, select ADO.NET Entity Data Model. This process is wizard driven where it is required to choose Generate from database, then database connection, connection string name, then database objects i.e., (Tables, Views, Stored procedures) and model namespace. Selecting Finish button creates ORM model. Refer video How Do I Get Started with the EDM Wizard?

 

Creating POCOs

image

image

From ORM model i.e., ContactsManagerModel.edmx choose Add Code Generation Item… context menu, add then choose ADO.NET Entity POCO Entity Generator Visual C# Items, name the POCO model and select Add button. This process generates storngly-typed ObjectContext class and entity classes with persistence ignorance.

Creating Factory

Factory class is responsible for establishing database connection and creates context for managing entities. ContactsManagerFactory factory class source is provided below.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: using Contacts.Data.SqlServer.Entities;
   7: using System.Configuration;
   8:  
   9: namespace Contacts.Data.SqlServer
  10: {
  11:     public class ContactsManagerFactory
  12:     {
  13:         private static string connectionString = string.Empty;
  14:  
  15:         #region Constructor
  16:         /// <summary>
  17:         /// 
  18:         /// </summary>
  19:         static ContactsManagerFactory()
  20:         {
  21:             string connectionStringName = ConfigurationManager.AppSettings.Get("ContactManagerConnectionString");
  22:             connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
  23:         }
  24:         #endregion
  25:  
  26:         #region
  27:         /// <summary>
  28:         /// 
  29:         /// </summary>
  30:         /// <returns></returns>
  31:         public static ContactManagerEntities CreateContext()
  32:         {
  33:             return new ContactManagerEntities(connectionString);
  34:         }
  35:  
  36:         #endregion
  37:     }
  38: }

Above concludes the Entities folder creation.

Repository folder consists of interface with operation signatures to perform CRUD and/or custom operations.

Creating Repository

Add an interface and operation signatures to Repository folder as shown below.

   1: namespace Contacts.Data.SqlServer.Repository
   2: {
   3:     using System;
   4:     using System.Collections.Generic;
   5:     using System.Linq;
   6:     using System.Text;
   7:     
   8:     using Contacts.Data.SqlServer.Entities;
   9:     
  10:     
  11:  
  12:     /// <summary>
  13:     ///  Contains method signatures for ContactsManager repository
  14:     /// </summary>
  15:     public interface IContactsManagerRepository
  16:     {
  17:         /// <summary>
  18:         /// Gets all the contact reocrds from database
  19:         /// </summary>
  20:         /// <returns>list of case study objects</returns>
  21:         List<Contact> GetAllContacts();
  22:  
  23:         /// <summary>
  24:         /// Gets the contact record that matches with the id 
  25:         /// </summary>
  26:         /// <param name="id">ID</param>
  27:         /// <returns>single contact record</returns>
  28:         Contact  GetContactById(int id);
  29:  
  30:         /// <summary>
  31:         /// Inserts a new Contact record
  32:         /// </summary>
  33:         /// <param name="entity">Contact entity type that has to be inserted into the database</param>
  34:         Contact Insert(Contact entity);
  35:  
  36:         /// <summary>
  37:         /// Updates a single Contact record
  38:         /// </summary>
  39:         /// <param name="entity">Contact entity type that has to be updated in the database</param>
  40:         bool Update(Contact entity);
  41:  
  42:         /// <summary>
  43:         /// Delete a single record from the table
  44:         /// </summary>
  45:         /// <param name="entity">ID of the row that has to be deleted from department table</param>
  46:         void Delete(int id);
  47:     }
  48: }

Implementation folder consists of  classes that inherit interfaces (contracts) from repository layer and implements the operations as shown below.

   1: namespace Contacts.Data.SqlServer.Implementation
   2: {
   3:     using System;
   4:     using System.Collections.Generic;
   5:     using System.Data;
   6:     using System.Linq;
   7:     using System.Text;
   8:     
   9:     using Contacts.Data.SqlServer.Repository;
  10:     using Contacts.Data.SqlServer.Entities;
  11:     
  12:  
  13:     /// <summary>
  14:     /// contains implementation of IContactManagerRepository
  15:     /// </summary>
  16:     public class ContactsManagerRepository : IContactsManagerRepository
  17:     {
  18:         /// <summary>
  19:         /// Returns a list of all Contacts in database
  20:         /// </summary>
  21:         /// <returns>List of contact record</returns>
  22:         public List<Contact> GetAllContacts()
  23:         {
  24:             using(var context = ContactsManagerFactory.CreateContext())
  25:             {
  26:                 var contactslist = (from contact in context.Contacts orderby contact.ID descending select contact).ToList();
  27:                 return contactslist;
  28:             }
  29:             
  30:         }
  31:         /// <summary>
  32:         /// Returns a contact record that matches with the id passed in
  33:         /// </summary>
  34:         /// <param name="id">single contact record</param>
  35:         /// <returns></returns>
  36:         public Contact GetContactById(int id)
  37:         {
  38:             using(var context = ContactsManagerFactory.CreateContext())
  39:             {
  40:                 var contactrecord = (from contact in context.Contacts where contact.ID == id select contact).SingleOrDefault();
  41:                 return contactrecord;
  42:             }
  43:         }
  44:  
  45:         /// <summary>
  46:         /// Inserts contact record into database
  47:         /// </summary>
  48:         /// <param name="entity">contact</param>
  49:         public Contact Insert(Contact entity)
  50:         {
  51:             using(var context = ContactsManagerFactory.CreateContext())
  52:             {
  53:                 context.Contacts.AddObject(entity);
  54:                 context.SaveChanges();
  55:  
  56:                 return entity;
  57:             }
  58:         }
  59:  
  60:         /// <summary>
  61:         /// Updates the contact record in database
  62:         /// </summary>
  63:         /// <param name="entity">contact record passed</param>
  64:         public bool Update(Contact entity)
  65:         {
  66:             using(var context = ContactsManagerFactory.CreateContext())
  67:             {
  68:  
  69:                 context.Contacts.Attach(entity);
  70:                 context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
  71:                 context.SaveChanges();
  72:  
  73:                 return true;
  74:  
  75:             }
  76:         }
  77:  
  78:         /// <summary>
  79:         /// Deletes the contact record from database that matches the id passed
  80:         /// </summary>
  81:         /// <param name="id">ID</param>
  82:         public void Delete(int id)
  83:         {
  84:             using(var context = ContactsManagerFactory.CreateContext())
  85:             {
  86:                 var contact = context.Contacts.Single(x => x.ID == id);
  87:  
  88:                 context.DeleteObject(contact);
  89:                 context.SaveChanges();
  90:             }
  91:             
  92:         }
  93:     }
  94: }

Above concludes the Data layer implementation.

In Visual Studio the Data layer implementation is shown below.

image

N-Tier: Service Layer implementation

Service layer is an implementation of Facade, Dependency Injection, DTO and Request-Response patterns using ASP.NET Web API project. The project is shown as below in Visual Studio solution explorer.

image

Most of the above project folders and files are created by Visual Studio as part of ASP.NET Web API project creation.

Model folder consists of DTO with properties from Contact entity. These properties can be copied from Contact POCO in Data layer. ContactBO is shown below. Here ContactBO is business object where validation rules can be enforced using Data annotations.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Web;
   5:  
   6: using System.ComponentModel.DataAnnotations;
   7:  
   8: namespace ContactsManager.Web.Api.Models
   9: {
  10:     public class ContactBO
  11:     {
  12:         [Required]
  13:         public int Id { get; set; }
  14:         public string Name { get; set; }
  15:         public string Address { get; set; }
  16:         public string City { get; set; }
  17:         public string State { get; set; }
  18:         public string Zip { get; set; }
  19:         public string Email { get; set; }
  20:         public string Twitter { get; set; }
  21:     }
  22: }

Messages folder consists of simple class with List of ContactBO and ContactBO that are returned to the client in service response. ContactResponse source is provided below.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Web;
   5:  
   6: using ContactsManager.Web.Api.Models;
   7:  
   8: namespace ContactsManager.Web.Api.Messages
   9: {
  10:     public class ContactResponse
  11:     {
  12:         public List<ContactBO> contacts;
  13:  
  14:         public ContactBO contact;
  15:  
  16:     }
  17: }

Controller folder consists of API controller classes i.e., ContactsManagerController here. IContactManagerRepository from Data layer is injected through constructor, IOC container Microsoft Unity is utilised to map the interface to concrete implementation in Global.asax. It uses ScopeContainer.cs as well.

ScopeContainer and Global.asax source is provided below.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Web;
   5:  
   6: namespace ContactManager
   7: {
   8:     using System;
   9:     using System.Collections.Generic;
  10:     using System.Web.Http;
  11:     using System.Web.Http.Dependencies;
  12:     using Microsoft.Practices.Unity;
  13:  
  14:     class ScopeContainer : IDependencyScope
  15:     {
  16:         protected IUnityContainer container;
  17:  
  18:         public ScopeContainer(IUnityContainer container)
  19:         {
  20:             if (container == null)
  21:             {
  22:                 throw new ArgumentNullException("container");
  23:             }
  24:             this.container = container;
  25:         }
  26:  
  27:         public object GetService(Type serviceType)
  28:         {
  29:             if (container.IsRegistered(serviceType))
  30:             {
  31:                 return container.Resolve(serviceType);
  32:             }
  33:             else
  34:             {
  35:                 return null;
  36:             }
  37:         }
  38:  
  39:         public IEnumerable<object> GetServices(Type serviceType)
  40:         {
  41:             if (container.IsRegistered(serviceType))
  42:             {
  43:                 return container.ResolveAll(serviceType);
  44:             }
  45:             else
  46:             {
  47:                 return new List<object>();
  48:             }
  49:         }
  50:  
  51:         public void Dispose()
  52:         {
  53:             container.Dispose();
  54:         }
  55:     }
  56:  
  57:     class IoCContainer : ScopeContainer, IDependencyResolver
  58:     {
  59:         public IoCContainer(IUnityContainer container)
  60:             : base(container)
  61:         {
  62:         }
  63:  
  64:         public IDependencyScope BeginScope()
  65:         {
  66:             var child = container.CreateChildContainer();
  67:             return new ScopeContainer(child);
  68:         }
  69:     }
  70: }

Global.asax

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Web;
   5: using System.Web.Http;
   6: using System.Web.Mvc;
   7: using System.Web.Optimization;
   8: using System.Web.Routing;
   9:  
  10: using ContactsManager.Web.Api.Controllers;
  11: using Contacts.Data.SqlServer.Repository;
  12: using Contacts.Data.SqlServer.Implementation;
  13: using Microsoft.Practices.Unity;
  14: using ContactManager;
  15:  
  16: using Contacts.Data.SqlServer.Entities;
  17:  
  18: namespace ContactsManager.Web.Api
  19: {
  20:     // Note: For instructions on enabling IIS6 or IIS7 classic mode, 
  21:     // visit http://go.microsoft.com/?LinkId=9394801
  22:  
  23:     public class WebApiApplication : System.Web.HttpApplication
  24:     {
  25:         protected void Application_Start()
  26:         {
  27:             AreaRegistration.RegisterAllAreas();
  28:  
  29:             GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
  30:  
  31:             WebApiConfig.Register(GlobalConfiguration.Configuration);
  32:             FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
  33:             RouteConfig.RegisterRoutes(RouteTable.Routes);
  34:             BundleConfig.RegisterBundles(BundleTable.Bundles);
  35:  
  36:             this.ConfigureApi(GlobalConfiguration.Configuration);
  37:         }
  38:         void ConfigureApi(HttpConfiguration config)
  39:         {
  40:             var unity = new UnityContainer();
  41:             unity.RegisterType<ContactsManagerController>();
  42:             unity.RegisterType<IContactsManagerRepository, ContactsManagerRepository>(new ContainerControlledLifetimeManager());
  43:             
  44:             config.DependencyResolver = new IoCContainer(unity);
  45:  
  46:         }
  47:     }
  48: }
Refer Microsoft’s Mike article how to inject dependencies into ASP.NET Web API controller, using the Web API dependency resolver.

Automapper is used to map POCO <—> ContactBO as shown below.

   1: Mapper.CreateMap<Contact, ContactBO>();
   2: Mapper.CreateMap<ContactBO, Contact>();

Complete source for ContactManagerController is below.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Net;
   5: using System.Net.Http;
   6: using System.Web.Http;
   7: using Contacts.Data.SqlServer.Repository;
   8: using AutoMapper;
   9:  
  10: using ContactsManager.Web.Api.Models;
  11: using ContactsManager.Web.Api.Messages;
  12: using Contacts.Data.SqlServer.Entities;
  13: using ContactsManager.Web.Api.ServiceMapper;
  14:  
  15: namespace ContactsManager.Web.Api.Controllers
  16: {
  17:     public class ContactsController : ApiController
  18:     {
  19:         /// <summary>
  20:         /// Initialise a variable of IContactsManagerRepository from data layer
  21:         /// </summary>
  22:         private readonly IContactsManagerRepository Contactrepository;
  23:  
  24:         /// <summary>
  25:         /// Inject repository
  26:         /// </summary>
  27:         /// <param name="_repository">IContactsManagerRepository</param>
  28:         public ContactsController(IContactsManagerRepository _repository)
  29:         {
  30:             if(_repository == null)
  31:             {
  32:                 throw new ArgumentNullException("ContactManager Repository exception");
  33:             }
  34:  
  35:             this.Contactrepository = _repository;
  36:             
  37:             //Map POCO <----> BO
  38:             Mapper.CreateMap<Contact, ContactBO>();
  39:             Mapper.CreateMap<ContactBO, Contact>();
  40:  
  41:             Mapper.AssertConfigurationIsValid();
  42:         }
  43:  
  44:         /// <summary>
  45:         /// Return list of contact records
  46:         /// </summary>
  47:         /// <returns>list of contact DTO</returns>
  48:         public ContactResponse Get()
  49:         {
  50:             ContactResponse response = new ContactResponse();
  51:  
  52:             try
  53:             {
  54:                 response.contacts = Mapper.Map<List<Contact>, List<ContactBO>>(this.Contactrepository.GetAllContacts());
  55:             }
  56:             catch (Exception e)
  57:             {
  58:                 throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
  59:                 {
  60:                     Content = new StringContent("An error occurred, please try again"),
  61:                     ReasonPhrase = "Critical exception"
  62:                 });
  63:             }
  64:             
  65:             return response;
  66:         }
  67:  
  68:         /// <summary>
  69:         /// Returns a single contact record that matches with the id 
  70:         /// </summary>
  71:         /// <param name="id">contact id</param>
  72:         /// <returns>single contact DTO</returns>
  73:         public ContactResponse Get(int id)
  74:         {
  75:             ContactResponse response = new ContactResponse();
  76:             response.contact = Mapper.Map<Contact, ContactBO>(this.Contactrepository.GetContactById(id));
  77:             
  78:             if(response.contact == null)
  79:             {
  80:                 var resp = new HttpResponseMessage(HttpStatusCode.NotFound)
  81:                 {
  82:                     Content = new StringContent(string.Format("No Contact with ID = {0}", id)),
  83:                     ReasonPhrase = "Contact ID Not Found"
  84:                 };
  85:                 
  86:                 throw new HttpResponseException(resp);
  87:             }
  88:  
  89:             return response;
  90:         }
  91:  
  92:         /// <summary>
  93:         /// Create new contact record in database
  94:         /// </summary>
  95:         /// <param name="request">contct data transfer object</param>
  96:         /// <returns>newly created resource with the uri</returns>
  97:         [HttpPost]
  98:         public HttpResponseMessage Post(ContactBO  request)
  99:         {
 100:             var data = ContactsMap.Map(request);
 101:  
 102:             var entity = Contactrepository.Insert(data);
 103:             var response = Request.CreateResponse(HttpStatusCode.Created, entity);
 104:             
 105:             try
 106:             {
 107:                 string uri = Url.Link("DefaultApi", new { id = entity.ID });
 108:                 response.Headers.Location = new Uri(uri);
 109:             }
 110:             catch (Exception e)
 111:             {
 112:                 throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
 113:                 {
 114:                     Content = new StringContent("An error occurred, please try again"),
 115:                     ReasonPhrase = "Critical exception"
 116:                 });
 117:             }
 118:             
 119:             return response;
 120:         }
 121:  
 122:         /// <summary>
 123:         /// Updates the contact record
 124:         /// </summary>
 125:         /// <param name="id">contact id</param>
 126:         /// <param name="contact">contact DTO</param>
 127:         /// <returns>Http status code</returns>
 128:         public HttpResponseMessage Put(int id, ContactBO contact)
 129:         {
 130:             contact.Id = id;
 131:             var entity = ContactsMap.Map(contact);
 132:  
 133:             if(!Contactrepository.Update(entity))
 134:             {
 135:                 throw new HttpResponseException(HttpStatusCode.NotModified);
 136:             }
 137:             else
 138:             {
 139:                 return new HttpResponseMessage(HttpStatusCode.OK);
 140:             }
 141:             
 142:         }
 143:  
 144:         /// <summary>
 145:         /// Delete the record from database with the ID passed
 146:         /// </summary>
 147:         /// <param name="id">contact id</param>
 148:         public void Delete(int id)
 149:         {
 150:             ContactBO contact = ContactsMap.Map(this.Contactrepository.GetContactById(id));
 151:  
 152:             if(contact == null)
 153:             {
 154:                 throw new HttpResponseException(HttpStatusCode.NotFound);
 155:             }
 156:  
 157:             Contactrepository.Delete(id);
 158:         }
 159:     }
 160:  
 161:     
 162: }

Web Api Request processing

When the request is received from the client, Service creates an instance of ContactResponse, retrieves data through data layer interface(IContactManagerRepository) and returns either a List or single ContactBO (DTO) by mapping Contact POCO to ContactBO using Automapper.

Service URI Example: http://localhost:51203/api/contacts/

When the above request is received from the client, list of ContactBO is returned to the client in Json format. WebApi routing maps the request to Get() operation (in ContactController) where an instance of ContactResponse is created, data is retrieved from data layer through IContactManagerRepository (ContactRepository) and object mapping is performed using Automapper.

http://localhost:51203/api/contacts/2

Above URI returns a single record where Contact ID is 2.

Refer Creating a Web API that Supports CRUD Operations

This concludes the N-Tier architecture for ASP.NET Web API application that utilises Data layer to interact with underlying database.

Testing: In order to test the application any HTTP tool can be used such as any browser, web debugging tool Fiddler by simply navigating to the above URIs. Note that IE asks to save or open the service response (Contact DTO). JSONView for Chrome or JSONView for Firefox can be installed to view the data in JSon format.

To implement API help page refer Creating Help Pages for ASP.NET Web API

API Help page provides all the operations with example data and the URI with xml comments from the operations.

Below video tutorial shows how to customize the Web API Help Pages as well as a number of tools that will help you develop and document your APIs.

References

 

 

 


2 Comments

Comments have been disabled for this content.