RESTful Day 1: Enterprise Level Application Architecture With Web APIs Using Entity Framework, Generic Repository Pattern and Unit of Work

Introduction

I have been practicing and reading a lot about RESTful services for the past few days. To my surprise, I could not find a complete series of practical implementations of ASP.Net Web APIs on the web. My effort in this series will be to focus on how to develop basic enterprise-level application architecture with Web APIs.

We'll be discussing less theory and doing more practice to understand how RESTful services can be created using an ORM. We choose Entity Framework here. My first article in the series is to set up a basic architecture of a REST service-based application. Later on in my future articles, I'll explain how to follow best standards to achieve enterprise-level architecture.

Roadmap

My road map for the series is as follows:

I'll purposely use Visual Studio 2010 and .NET Framework 4.0 because there are a few implementations that are very hard to find in .NET Framework 4.0, but I'll make it easy by showing how to do it.

REST

Here is an excerpt from the Wikipedia:

“Unlike SOAP-based web services, there is no "official" standard for RESTful web APIs. This is because REST is an architectural style, whereas SOAP is a protocol. Even though REST is not a standard per se, most RESTful implementations make use of standards such as HTTP, URI, JSON and XML.”

I agree to it. Let's do some coding.

Setup database

I am using SQL Server 2008 as a database server. I have provided the SQL scripts to create the database in SQL Server, you can use it to create one. I have given WebApiDb as my database name. My database contains three tables for now; they are Products, Tokens and User. In this tutorial we'll only be dealing with a product table to do CRUD operations using Web API and Entity Framework. We'll use Tokens and Users in my future article. For those who fail to create database using scripts, here is the structure you can use:

Web API project

Open your Visual Studio. I am using Visual Studio 2010, you can use Visual Studio version 2010 or above.

Step 1

Create a new Project in your Visual Studio as in the following:

Step 2

There after, choose to create an ASP.Net MVC 4 Web Application and provide it the name of your choice, I used WebAPI.

Step 3

Out of various type of project templates shown to you, choose Web API project as in the following:

 

Once done, you'll get a project structure like the one shown below, with a default Home and Values controller.

You can choose to delete this ValuesController, since we'll be using our own controller to learn.

 

Setup Data Access Layer

Let's setup or data access layer first. We'll be using Entity Framework 5.0 to talk to the database. We'll use the Generic Repository Pattern and Unit of Work pattern to standardize our layer.

Let's have a look at the standard definition of Entity Framework given by Microsoft:

“The Microsoft ADO.NET Entity Framework is an Object/Relational Mapping (ORM) framework that enables developers to work with relational data as domain-specific objects, eliminating the need for most of the data access plumbing code that developers usually need to write. Using the Entity Framework, developers issue queries using LINQ, then retrieve and manipulate data as strongly typed objects. The Entity Framework's ORM implementation provides services like change tracking, identity resolution, lazy loading and query translation so that developers can focus on their application-specific business logic rather than the data access fundamentals.”

In simple language, Entity Framework is an Object/Relational Mapping (ORM) framework. It is an enhancement to ADO.NET, an upper layer to ADO.NET that gives developers an automated mechanism for accessing and storing the data in the database.

Step 1

Create a new class library in your Visual Studio and name it DataModel as shown below:

Step 2

In the same way, create one more project, again a class library, and call it BusinessEntities as in the following:

I'll explain the use of this class library soon.

Step 3

Move on to your DataModel project. Right-click on it and add a new item in the list shown, choose ADO.Net Data Model and name it WebApiDataModel.edmx.

The file .edmx will contain the databse information of our database that we created earlier, let's set this up.

You'll be presented a wizard as follows:

 

Choose generate from database. Choose Microsoft SQL Server as shown in the following image:

Click Continue, then provide the credentials for your database, in other words WebAPIdb and connect to it as in the following:

You'll get a screen showing the connection string of the database we chose as in the following:

Provide the name of the connection string as WebApiDbEntities and click Next.

Choose all the database objects, check all the check boxes and provide a name for the model. I gave it the name WebApiDbModel.

Once you finish this wizard, you'll get the schema ready in your datamodel project as follows:

We've got our schema in-place using Entity Framework. But a bit of work remains. We need our data context class and entities through which we'll communicate with the database.

So, moving on to the next step.

Step 4

Click on Tools in Visual Studio and open the Extension Manager. We need to get a db context generator for our datamodel. We can also do it using the default code generation item by right-clicking in the edmx view and add a code generation item, but that will generate an object context class that is heavier than the db context. I want a lightweight db context class to be created, so we'll use the Extension Manager to add a package and then create a db context class.

Search for Entity Framework Db context generator in the online galary and select the one for EF 5.x as in the following:

I guess you need to restart Visual Studio to get that into your templates.

Step 5

Now right-click in the .edmx file schema designer and choose “Add Code Generation Item...”.

Step 6

Now you'll see that we have the template for the extension that we added, select that EF 5.x DbContext Generator and click Add.

After adding this we'll get the db context class and its properties. This class is responsible for all database transactions that we need to do, so our structure looks as shown below.

Wow, we ended up with errors. But we got our db context class and our entity models. You can see them in our DataModel project. Errors? Nothing to worry about , it's just we did not reference Entity Framework in our project. We'll do it the right away.

Step 7

Go to Tools -> Library Packet Manager -> Packet Manager Console.

You'll get the console in the bottom-left of Visual Studio.

Select dataModel project and rovide the command “Install-Package EntityFramework –Version 5.0.0” to install Entity Framework 5 in our DataModel project.

 

Press Enter. And all the errors become resolved.

Generic Repository and Unit of Work

You can read about the Repository Pattern and creating a repository in detail from my article:

Repository Pattern in MVC3 Application With Entity Framework: Part 5

Just to list the benefits of the Repository Pattern:

  • It centralizes the data logic or Web service access logic.
  • It provides a substitution point for the unit tests.
  • It provides a flexible architecture that can be adapted as the overall design of the application evolves.

We'll create a generic repository that works for all our entities. Creating repositories for each and every entity may result in lots of duplicate code in large projects. For creating a Generic Repository you can follow:

Generic Repository Pattern in MVC3 Application With Entity Framework: Part 6

Step 1

Add a folder named GenericRepository to the DataModel project and to that folder add a class named Generic Repository. Add the following code to that class that serves as a template-based generic code for all the entities that will interact with the databse as in the following:

  1. #region Using Namespaces...  
  2.   
  3. using System;  
  4. using System.Collections.Generic;  
  5. using System.Data;  
  6. using System.Data.Entity;  
  7. using System.Linq;  
  8.  
  9. #endregion  
  10.   
  11. namespace DataModel.GenericRepository  
  12. {  
  13.     /// <summary>  
  14.     /// Generic Repository class for Entity Operations  
  15.     /// </summary>  
  16.     /// <typeparam name="TEntity"></typeparam>  
  17.     public class GenericRepository<TEntity> where TEntity : class  
  18.     {  
  19.         #region Private member variables...  
  20.         internal WebApiDbEntities Context;  
  21.         internal DbSet<TEntity> DbSet;  
  22.         #endregion  
  23.  
  24.         #region Public Constructor...  
  25.         /// <summary>  
  26.         /// Public Constructor,initializes privately declared local variables.  
  27.         /// </summary>  
  28.         /// <param name="context"></param>  
  29.         public GenericRepository(WebApiDbEntities context)  
  30.         {  
  31.             this.Context = context;  
  32.             this.DbSet = context.Set<TEntity>();  
  33.         }  
  34.         #endregion  
  35.  
  36.         #region Public member methods...  
  37.   
  38.         /// <summary>  
  39.         /// generic Get method for Entities  
  40.         /// </summary>  
  41.         /// <returns></returns>  
  42.         public virtual IEnumerable<TEntity> Get()  
  43.         {  
  44.             IQueryable<TEntity> query = DbSet;  
  45.             return query.ToList();  
  46.         }  
  47.   
  48.         /// <summary>  
  49.         /// Generic get method on the basis of id for Entities.  
  50.         /// </summary>  
  51.         /// <param name="id"></param>  
  52.         /// <returns></returns>  
  53.         public virtual TEntity GetByID(object id)  
  54.         {  
  55.             return DbSet.Find(id);  
  56.         }  
  57.   
  58.         /// <summary>  
  59.         /// generic Insert method for the entities  
  60.         /// </summary>  
  61.         /// <param name="entity"></param>  
  62.         public virtual void Insert(TEntity entity)  
  63.         {  
  64.             DbSet.Add(entity);  
  65.         }  
  66.   
  67.         /// <summary>  
  68.         /// Generic Delete method for the entities  
  69.         /// </summary>  
  70.         /// <param name="id"></param>  
  71.         public virtual void Delete(object id)  
  72.         {  
  73.             TEntity entityToDelete = DbSet.Find(id);  
  74.             Delete(entityToDelete);  
  75.         }  
  76.   
  77.         /// <summary>  
  78.         /// Generic Delete method for the entities  
  79.         /// </summary>  
  80.         /// <param name="entityToDelete"></param>  
  81.         public virtual void Delete(TEntity entityToDelete)  
  82.         {  
  83.             if (Context.Entry(entityToDelete).State == EntityState.Detached)  
  84.             {  
  85.                 DbSet.Attach(entityToDelete);  
  86.             }  
  87.             DbSet.Remove(entityToDelete);  
  88.         }  
  89.   
  90.         /// <summary>  
  91.         /// Generic update method for the entities  
  92.         /// </summary>  
  93.         /// <param name="entityToUpdate"></param>  
  94.         public virtual void Update(TEntity entityToUpdate)  
  95.         {  
  96.             DbSet.Attach(entityToUpdate);  
  97.             Context.Entry(entityToUpdate).State = EntityState.Modified;  
  98.         }  
  99.   
  100.         /// <summary>  
  101.         /// generic method to get many record on the basis of a condition.  
  102.         /// </summary>  
  103.         /// <param name="where"></param>  
  104.         /// <returns></returns>  
  105.         public virtual IEnumerable<TEntity> GetMany(Func<TEntity, bool> where)  
  106.         {  
  107.             return DbSet.Where(where).ToList();  
  108.         }  
  109.   
  110.         /// <summary>  
  111.         /// generic method to get many record on the basis of a condition but query able.  
  112.         /// </summary>  
  113.         /// <param name="where"></param>  
  114.         /// <returns></returns>  
  115.         public virtual IQueryable<TEntity> GetManyQueryable(Func<TEntity, bool> where)  
  116.         {  
  117.             return DbSet.Where(where).AsQueryable();  
  118.         }  
  119.   
  120.         /// <summary>  
  121.         /// generic get method , fetches data for the entities on the basis of condition.  
  122.         /// </summary>  
  123.         /// <param name="where"></param>  
  124.         /// <returns></returns>  
  125.         public TEntity Get(Func<TEntity, Boolean> where)  
  126.         {  
  127.             return DbSet.Where(where).FirstOrDefault<TEntity>();  
  128.         }  
  129.   
  130.         /// <summary>  
  131.         /// generic delete method , deletes data for the entities on the basis of condition.  
  132.         /// </summary>  
  133.         /// <param name="where"></param>  
  134.         /// <returns></returns>  
  135.         public void Delete(Func<TEntity, Boolean> where)  
  136.         {  
  137.             IQueryable<TEntity> objects = DbSet.Where<TEntity>(where).AsQueryable();  
  138.             foreach (TEntity obj in objects)  
  139.                 DbSet.Remove(obj);  
  140.         }  
  141.   
  142.         /// <summary>  
  143.         /// generic method to fetch all the records from db  
  144.         /// </summary>  
  145.         /// <returns></returns>  
  146.         public virtual IEnumerable<TEntity> GetAll()  
  147.         {  
  148.             return DbSet.ToList();  
  149.         }  
  150.   
  151.         /// <summary>  
  152.         /// Inclue multiple  
  153.         /// </summary>  
  154.         /// <param name="predicate"></param>  
  155.         /// <param name="include"></param>  
  156.         /// <returns></returns>  
  157.         public IQueryable<TEntity> GetWithInclude(System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate, params string[] include)  
  158.         {  
  159.             IQueryable<TEntity> query = this.DbSet;  
  160.             query = include.Aggregate(query, (current, inc) => current.Include(inc));  
  161.             return query.Where(predicate);  
  162.         }  
  163.   
  164.         /// <summary>  
  165.         /// Generic method to check if entity exists  
  166.         /// </summary>  
  167.         /// <param name="primaryKey"></param>  
  168.         /// <returns></returns>  
  169.         public bool Exists(object primaryKey)  
  170.         {  
  171.             return DbSet.Find(primaryKey) != null;  
  172.         }  
  173.   
  174.         /// <summary>  
  175.         /// Gets a single record by the specified criteria (usually the unique identifier)  
  176.         /// </summary>  
  177.         /// <param name="predicate">Criteria to match on</param>  
  178.         /// <returns>A single record that matches the specified criteria</returns>  
  179.         public TEntity GetSingle(Func<TEntity, bool> predicate)  
  180.         {  
  181.             return DbSet.Single<TEntity>(predicate);  
  182.         }  
  183.   
  184.         /// <summary>  
  185.         /// The first record matching the specified criteria  
  186.         /// </summary>  
  187.         /// <param name="predicate">Criteria to match on</param>  
  188.         /// <returns>A single record containing the first record matching the specified criteria</returns>  
  189.         public TEntity GetFirst(Func<TEntity, bool> predicate)  
  190.         {  
  191.             return DbSet.First<TEntity>(predicate);  
  192.         }  
  193.  
  194.  
  195.         #endregion  
  196.     }  
  197. }  
Unit of Work

Again, I'll not explain in detail what Unit of Work is. You can Google about the theory or follow my existing article on MVC with Unit of Work.

To give a heads up, again from my existing article, the important responsibilities of Unit of Work are the following:

  • To manage transactions.
  • To order the database inserts, deletes and updates.
  • To prevent duplicate updates. Inside a single usage of a Unit of Work object, various parts of the code may mark the same Invoice object as changed, but the Unit of Work class will only issue a single UPDATE command to the database.

The value of using a Unit of Work pattern is to free the rest of our code from these concerns so that you can otherwise concentrate on the business logic.

Step 1

Create a folder named UnitOfWork and add a class to that folder named UnitOfWork.cs.

Add GenericRepository properties for all the three entities that we got. The class also implements an IDisposable interface and it's method Dispose to free up connections and objects. The class will be as follows.

  1. #region Using Namespaces...  
  2.   
  3. using System;  
  4. using System.Collections.Generic;  
  5. using System.Diagnostics;  
  6. using System.Data.Entity.Validation;  
  7. using DataModel.GenericRepository;  
  8.  
  9. #endregion  
  10.   
  11. namespace DataModel.UnitOfWork  
  12. {  
  13.     /// <summary>  
  14.     /// Unit of Work class responsible for DB transactions  
  15.     /// </summary>  
  16.     public class UnitOfWork : IDisposable  
  17.     {  
  18.         #region Private member variables...  
  19.   
  20.         private WebApiDbEntities _context = null;  
  21.         private GenericRepository<User> _userRepository;  
  22.         private GenericRepository<Product> _productRepository;  
  23.         private GenericRepository<Token> _tokenRepository;  
  24.         #endregion  
  25.   
  26.         public UnitOfWork()  
  27.         {  
  28.             _context = new WebApiDbEntities();  
  29.         }  
  30.  
  31.         #region Public Repository Creation properties...  
  32.   
  33.         /// <summary>  
  34.         /// Get/Set Property for product repository.  
  35.         /// </summary>  
  36.         public GenericRepository<Product> ProductRepository  
  37.         {  
  38.             get  
  39.             {  
  40.                 if (this._productRepository == null)  
  41.                     this._productRepository = new GenericRepository<Product>(_context);  
  42.                 return _productRepository;  
  43.             }  
  44.         }  
  45.   
  46.         /// <summary>  
  47.         /// Get/Set Property for user repository.  
  48.         /// </summary>  
  49.         public GenericRepository<User> UserRepository  
  50.         {  
  51.             get  
  52.             {  
  53.                 if (this._userRepository == null)  
  54.                     this._userRepository = new GenericRepository<User>(_context);  
  55.                 return _userRepository;  
  56.             }  
  57.         }  
  58.   
  59.         /// <summary>  
  60.         /// Get/Set Property for token repository.  
  61.         /// </summary>  
  62.         public GenericRepository<Token> TokenRepository  
  63.         {  
  64.             get  
  65.             {  
  66.                 if (this._tokenRepository == null)  
  67.                     this._tokenRepository = new GenericRepository<Token>(_context);  
  68.                 return _tokenRepository;  
  69.             }  
  70.         }  
  71.         #endregion  
  72.  
  73.         #region Public member methods...  
  74.         /// <summary>  
  75.         /// Save method.  
  76.         /// </summary>  
  77.         public void Save()  
  78.         {  
  79.             try  
  80.             {  
  81.                 _context.SaveChanges();  
  82.             }  
  83.             catch (DbEntityValidationException e)  
  84.             {  
  85.   
  86.                 var outputLines = new List<string>();  
  87.                 foreach (var eve in e.EntityValidationErrors)  
  88.                 {  
  89.                     outputLines.Add(string.Format("{0}: Entity of type \"{1}\" in state \"{2}\" has the following validation errors:", DateTime.Now, eve.Entry.Entity.GetType().Name, eve.Entry.State));  
  90.                     foreach (var ve in eve.ValidationErrors)  
  91.                     {  
  92.                         outputLines.Add(string.Format("- Property: \"{0}\", Error: \"{1}\"", ve.PropertyName, ve.ErrorMessage));  
  93.                     }  
  94.                 }  
  95.                 System.IO.File.AppendAllLines(@"C:\errors.txt", outputLines);  
  96.   
  97.                 throw e;  
  98.             }  
  99.   
  100.         }  
  101.  
  102.         #endregion  
  103.  
  104.         #region Implementing IDiosposable...  
  105.  
  106.         #region private dispose variable declaration...  
  107.         private bool disposed = false;   
  108.         #endregion  
  109.   
  110.         /// <summary>  
  111.         /// Protected Virtual Dispose method  
  112.         /// </summary>  
  113.         /// <param name="disposing"></param>  
  114.         protected virtual void Dispose(bool disposing)  
  115.         {  
  116.             if (!this.disposed)  
  117.             {  
  118.                 if (disposing)  
  119.                 {  
  120.                     Debug.WriteLine("UnitOfWork is being disposed");  
  121.                     _context.Dispose();  
  122.                 }  
  123.             }  
  124.             this.disposed = true;  
  125.         }  
  126.   
  127.         /// <summary>  
  128.         /// Dispose method  
  129.         /// </summary>  
  130.         public void Dispose()  
  131.         {  
  132.             Dispose(true);  
  133.             GC.SuppressFinalize(this);  
  134.         }   
  135.         #endregion  
  136.     }  
  137. }  

Now we have completely set up our data access layer and our project structure looks as shown below.

Setup Business Entities

Remember, we created a business entities project. You may wonder, since we already have database entities to interact with the database, why do we need Business Entities? The answer is as simple as, we are trying to follow a proper structure of communication and one would never want to expose the database entities to the end client, in our case the Web API, it involves a lot of risk. Hackers may manipulate the details and get access to your database. Instead, we'll use database entities in our business logic layer and use Business Entities as transfer objects to communicate between the business logic and the Web API project. So business entities may have different names, but their properties remain the same as database entities. In our case we'll add same-named business entity classes appended with the word “Entity” in our BusinessEntity project. So we'll end up having the following three classes:

Product Entity

  1. public class ProductEntity  
  2. {  
  3.     public int ProductId { getset; }  
  4.     public string ProductName { getset; }  
  5. }  

Token entity

  1. public class TokenEntity  
  2. {  
  3.     public int TokenId { getset; }  
  4.     public int UserId { getset; }  
  5.     public string AuthToken { getset; }  
  6.     public System.DateTime IssuedOn { getset; }  
  7.     public System.DateTime ExpiresOn { getset; }  
  8. }  

User Entity

  1. public class UserEntity  
  2. {  
  3.     public int UserId { getset; }  
  4.     public string UserName { getset; }  
  5.     public string word { getset; }  
  6.     public string Name { getset; }  
  7. }  
Setup Business Services Project

Add a new class library to the solution named BusinessServices. This layer will act as our business logic layer. Note that, we can use our API controllers to write the business logic, but I am trying to segregate my business logic in an extra layer so that if in the future I want to use WCF, MVC, ASP.Net Web Pages or any other application as my presentation layer then I can easily integrate my Business logic layer into it.

We'll make this layer testable, so we need to create an interface and in it declare CRUD operations to be performed over a product table. Before we proceed, add a reference for the BusinessEntities project and DataModel project to this newly-created project.

Step 1

Create an interface named IProductServices and add the following code to it for CRUD operations methods.

  1. using System.Collections.Generic;  
  2. using BusinessEntities;  
  3.   
  4. namespace BusinessServices  
  5. {  
  6.     /// <summary>  
  7.     /// Product Service Contract  
  8.     /// </summary>  
  9.     public interface IProductServices  
  10.     {  
  11.         ProductEntity GetProductById(int productId);  
  12.         IEnumerable<ProductEntity> GetAllProducts();  
  13.         int CreateProduct(ProductEntity productEntity);  
  14.         bool UpdateProduct(int productId,ProductEntity productEntity);  
  15.         bool DeleteProduct(int productId);  
  16.     }  
  17. }  

Step 2

Create a class to implement this interface. Name that class ProductServices.

The class contains a private variable of UnitOfWork and a constructor to initialize that variable as in the following:

  1. private readonly UnitOfWork _unitOfWork;  
  2.   
  3.  /// <summary>  
  4.  /// Public constructor.  
  5.  /// </summary>  
  6.  public ProductServices()  
  7.  {  
  8.      _unitOfWork = new UnitOfWork();  
  9.  }  

We have decided not to expose our db entities to the Web API project, so we need something to map the db entities data to my business entity classes. We'll make use of AutoMapper. You can read about AutoMapper in my this article.

Step 3

Just right-click the project then select Extension manager, search for AutoMapper in the online galary and add it to the BusinessServices project as in the following:

Step 4

Implement the following methods in the ProductServices class.

Add the following code to the class:

  1. using System.Collections.Generic;  
  2. using System.Linq;  
  3. using System.Transactions;  
  4. using AutoMapper;  
  5. using BusinessEntities;  
  6. using DataModel;  
  7. using DataModel.UnitOfWork;  
  8.   
  9. namespace BusinessServices  
  10. {  
  11.     /// <summary>  
  12.     /// Offers services for product specific CRUD operations  
  13.     /// </summary>  
  14.     public class ProductServices:IProductServices  
  15.     {  
  16.         private readonly UnitOfWork _unitOfWork;  
  17.   
  18.         /// <summary>  
  19.         /// Public constructor.  
  20.         /// </summary>  
  21.         public ProductServices()  
  22.         {  
  23.             _unitOfWork = new UnitOfWork();  
  24.         }  
  25.   
  26.         /// <summary>  
  27.         /// Fetches product details by id  
  28.         /// </summary>  
  29.         /// <param name="productId"></param>  
  30.         /// <returns></returns>  
  31.         public BusinessEntities.ProductEntity GetProductById(int productId)  
  32.         {  
  33.             var product = _unitOfWork.ProductRepository.GetByID(productId);  
  34.             if (product != null)  
  35.             {  
  36.                 Mapper.CreateMap<Product, ProductEntity>();  
  37.                 var productModel = Mapper.Map<Product, ProductEntity>(product);  
  38.                 return productModel;  
  39.             }  
  40.             return null;  
  41.         }  
  42.   
  43.         /// <summary>  
  44.         /// Fetches all the products.  
  45.         /// </summary>  
  46.         /// <returns></returns>  
  47.         public IEnumerable<BusinessEntities.ProductEntity> GetAllProducts()  
  48.         {  
  49.             var products = _unitOfWork.ProductRepository.GetAll().ToList();  
  50.             if (products.Any())  
  51.             {  
  52.                 Mapper.CreateMap<Product, ProductEntity>();  
  53.                 var productsModel = Mapper.Map<List<Product>, List<ProductEntity>>(products);  
  54.                 return productsModel;  
  55.             }  
  56.             return null;  
  57.         }  
  58.   
  59.         /// <summary>  
  60.         /// Creates a product  
  61.         /// </summary>  
  62.         /// <param name="productEntity"></param>  
  63.         /// <returns></returns>  
  64.         public int CreateProduct(BusinessEntities.ProductEntity productEntity)  
  65.         {  
  66.             using (var scope = new TransactionScope())  
  67.             {  
  68.                 var product = new Product  
  69.                 {  
  70.                     ProductName = productEntity.ProductName  
  71.                 };  
  72.                 _unitOfWork.ProductRepository.Insert(product);  
  73.                 _unitOfWork.Save();  
  74.                 scope.Complete();  
  75.                 return product.ProductId;  
  76.             }  
  77.         }  
  78.   
  79.         /// <summary>  
  80.         /// Updates a product  
  81.         /// </summary>  
  82.         /// <param name="productId"></param>  
  83.         /// <param name="productEntity"></param>  
  84.         /// <returns></returns>  
  85.         public bool UpdateProduct(int productId, BusinessEntities.ProductEntity productEntity)  
  86.         {  
  87.             var success = false;  
  88.             if (productEntity != null)  
  89.             {  
  90.                 using (var scope = new TransactionScope())  
  91.                 {  
  92.                     var product = _unitOfWork.ProductRepository.GetByID(productId);  
  93.                     if (product != null)  
  94.                     {  
  95.                         product.ProductName = productEntity.ProductName;  
  96.                         _unitOfWork.ProductRepository.Update(product);  
  97.                         _unitOfWork.Save();  
  98.                         scope.Complete();  
  99.                         success = true;  
  100.                     }  
  101.                 }  
  102.             }  
  103.             return success;  
  104.         }  
  105.   
  106.         /// <summary>  
  107.         /// Deletes a particular product  
  108.         /// </summary>  
  109.         /// <param name="productId"></param>  
  110.         /// <returns></returns>  
  111.         public bool DeleteProduct(int productId)  
  112.         {  
  113.             var success = false;  
  114.             if (productId > 0)  
  115.             {  
  116.                 using (var scope = new TransactionScope())  
  117.                 {  
  118.                     var product = _unitOfWork.ProductRepository.GetByID(productId);  
  119.                     if (product != null)  
  120.                     {  
  121.   
  122.                         _unitOfWork.ProductRepository.Delete(product);  
  123.                         _unitOfWork.Save();  
  124.                         scope.Complete();  
  125.                         success = true;  
  126.                     }  
  127.                 }  
  128.             }  
  129.             return success;  
  130.         }  
  131.     }  
  132. }  

Let me explain the idea of the code. We have the following 5 methods:

  1. To get a product by id (GetproductById): We call the repository to get the product by id. Id is a parameter from the calling method to that service method. It returns the product entity from the database. Note that it will not return the exact db entity, instead we'll map it with our business entity using AutoMapper and return it to the calling method.
    1. /// <summary>  
    2. /// Fetches product details by id  
    3. /// </summary>  
    4. /// <param name="productId"></param>  
    5. /// <returns></returns>  
    6. public BusinessEntities.ProductEntity GetProductById(int productId)  
    7. {  
    8.     var product = _unitOfWork.ProductRepository.GetByID(productId);  
    9.     if (product != null)  
    10.     {  
    11.         Mapper.CreateMap<Product, ProductEntity>();  
    12.         var productModel = Mapper.Map<Product, ProductEntity>(product);  
    13.         return productModel;  
    14.     }  
    15.     return null;  
    16. }  
  2. Get all products from the database (GetAllProducts): This method returns all the products residing in the database, again we use AutoMapper to map the list and return it.
    1. /// <summary>  
    2. /// Fetches all the products.  
    3. /// </summary>  
    4. /// <returns></returns>  
    5. public IEnumerable<BusinessEntities.ProductEntity> GetAllProducts()  
    6. {  
    7.     var products = _unitOfWork.ProductRepository.GetAll().ToList();  
    8.     if (products.Any())  
    9.     {  
    10.         Mapper.CreateMap<Product, ProductEntity>();  
    11.         var productsModel = Mapper.Map<List<Product>, List<ProductEntity>>(products);  
    12.         return productsModel;  
    13.     }  
    14.     return null;  
    15. }  
  3. Create a new product (CreateProduct): This method takes the product's BusinessEntity as an argument and creates a new object of an actual database entity and inserts it using a unit of work.
    1. /// <summary>  
    2. /// Creates a product  
    3. /// </summary>  
    4. /// <param name="productEntity"></param>  
    5. /// <returns></returns>  
    6. public int CreateProduct(BusinessEntities.ProductEntity productEntity)  
    7. {  
    8.     using (var scope = new TransactionScope())  
    9.     {  
    10.         var product = new Product  
    11.         {  
    12.             ProductName = productEntity.ProductName  
    13.         };  
    14.         _unitOfWork.ProductRepository.Insert(product);  
    15.         _unitOfWork.Save();  
    16.         scope.Complete();  
    17.         return product.ProductId;  
    18.     }  
    19. }  

I guess you can now write update and delete methods. So the following is the code for the complete class.

  1. using System.Collections.Generic;  
  2. using System.Linq;  
  3. using System.Transactions;  
  4. using AutoMapper;  
  5. using BusinessEntities;  
  6. using DataModel;  
  7. using DataModel.UnitOfWork;  
  8.   
  9. namespace BusinessServices  
  10. {  
  11.     /// <summary>  
  12.     /// Offers services for product specific CRUD operations  
  13.     /// </summary>  
  14.     public class ProductServices:IProductServices  
  15.     {  
  16.         private readonly UnitOfWork _unitOfWork;  
  17.   
  18.         /// <summary>  
  19.         /// Public constructor.  
  20.         /// </summary>  
  21.         public ProductServices()  
  22.         {  
  23.             _unitOfWork = new UnitOfWork();  
  24.         }  
  25.   
  26.         /// <summary>  
  27.         /// Fetches product details by id  
  28.         /// </summary>  
  29.         /// <param name="productId"></param>  
  30.         /// <returns></returns>  
  31.         public BusinessEntities.ProductEntity GetProductById(int productId)  
  32.         {  
  33.             var product = _unitOfWork.ProductRepository.GetByID(productId);  
  34.             if (product != null)  
  35.             {  
  36.                 Mapper.CreateMap<Product, ProductEntity>();  
  37.                 var productModel = Mapper.Map<Product, ProductEntity>(product);  
  38.                 return productModel;  
  39.             }  
  40.             return null;  
  41.         }  
  42.   
  43.         /// <summary>  
  44.         /// Fetches all the products.  
  45.         /// </summary>  
  46.         /// <returns></returns>  
  47.         public IEnumerable<BusinessEntities.ProductEntity> GetAllProducts()  
  48.         {  
  49.             var products = _unitOfWork.ProductRepository.GetAll().ToList();  
  50.             if (products.Any())  
  51.             {  
  52.                 Mapper.CreateMap<Product, ProductEntity>();  
  53.                 var productsModel = Mapper.Map<List<Product>, List<ProductEntity>>(products);  
  54.                 return productsModel;  
  55.             }  
  56.             return null;  
  57.         }  
  58.   
  59.         /// <summary>  
  60.         /// Creates a product  
  61.         /// </summary>  
  62.         /// <param name="productEntity"></param>  
  63.         /// <returns></returns>  
  64.         public int CreateProduct(BusinessEntities.ProductEntity productEntity)  
  65.         {  
  66.             using (var scope = new TransactionScope())  
  67.             {  
  68.                 var product = new Product  
  69.                 {  
  70.                     ProductName = productEntity.ProductName  
  71.                 };  
  72.                 _unitOfWork.ProductRepository.Insert(product);  
  73.                 _unitOfWork.Save();  
  74.                 scope.Complete();  
  75.                 return product.ProductId;  
  76.             }  
  77.         }  
  78.   
  79.         /// <summary>  
  80.         /// Updates a product  
  81.         /// </summary>  
  82.         /// <param name="productId"></param>  
  83.         /// <param name="productEntity"></param>  
  84.         /// <returns></returns>  
  85.         public bool UpdateProduct(int productId, BusinessEntities.ProductEntity productEntity)  
  86.         {  
  87.             var success = false;  
  88.             if (productEntity != null)  
  89.             {  
  90.                 using (var scope = new TransactionScope())  
  91.                 {  
  92.                     var product = _unitOfWork.ProductRepository.GetByID(productId);  
  93.                     if (product != null)  
  94.                     {  
  95.                         product.ProductName = productEntity.ProductName;  
  96.                         _unitOfWork.ProductRepository.Update(product);  
  97.                         _unitOfWork.Save();  
  98.                         scope.Complete();  
  99.                         success = true;  
  100.                     }  
  101.                 }  
  102.             }  
  103.             return success;  
  104.         }  
  105.   
  106.         /// <summary>  
  107.         /// Deletes a particular product  
  108.         /// </summary>  
  109.         /// <param name="productId"></param>  
  110.         /// <returns></returns>  
  111.         public bool DeleteProduct(int productId)  
  112.         {  
  113.             var success = false;  
  114.             if (productId > 0)  
  115.             {  
  116.                 using (var scope = new TransactionScope())  
  117.                 {  
  118.                     var product = _unitOfWork.ProductRepository.GetByID(productId);  
  119.                     if (product != null)  
  120.                     {  
  121.   
  122.                         _unitOfWork.ProductRepository.Delete(product);  
  123.                         _unitOfWork.Save();  
  124.                         scope.Complete();  
  125.                         success = true;  
  126.                     }  
  127.                 }  
  128.             }  
  129.             return success;  
  130.         }  
  131.     }  
  132. }  

The job is niw done at the business service level. Let's move on to the API controller to call these methods.

Setup WebAPI project

Step 1

Just add references for BusinessEntity and BusinessService to the WebAPI project, our architecture becomes like the following:

Step 2

Add a new WebAPI controller to the Controller folder. Right-click the Controller folder and add a new controller.

We get a controller as follows:

  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.   
  8. namespace WebApi.Controllers  
  9. {  
  10.     public class ProductController : ApiController  
  11.     {  
  12.         // GET api/product  
  13.         public IEnumerable<string> Get()  
  14.         {  
  15.             return new string[] { "value1""value2" };  
  16.         }  
  17.   
  18.         // GET api/product/5  
  19.         public string Get(int id)  
  20.         {  
  21.             return "value";  
  22.         }  
  23.   
  24.         // POST api/product  
  25.         public void Post([FromBody]string value)  
  26.         {  
  27.         }  
  28.   
  29.         // PUT api/product/5  
  30.         public void Put(int id, [FromBody]string value)  
  31.         {  
  32.         }  
  33.   
  34.         // DELETE api/product/5  
  35.         public void Delete(int id)  
  36.         {  
  37.         }  
  38.     }  
  39. }  

We get HTTP VERBS as method names. The Web API is smart enough to recognize a request with the name of the VERB itself. In our case we are doing CRUD operations, so we don't need to change the names of the method, we just needed this. We only need to write the calling logic inside these methods. In my future articles of the series, we will work out how to define new routes and provide method names of our choice with those routes.

Step 3

Add logic to call Business Service methods. Just make an object of the Business Service and call its respective methods, our Controller class becomes like:

  1. using System.Collections.Generic;  
  2. using System.Linq;  
  3. using System.Net;  
  4. using System.Net.Http;  
  5. using System.Web.Http;  
  6. using BusinessEntities;  
  7. using BusinessServices;  
  8.   
  9. namespace WebApi.Controllers  
  10. {  
  11.     public class ProductController : ApiController  
  12.     {  
  13.   
  14.         private readonly IProductServices _productServices;  
  15.  
  16.          #region Public Constructor  
  17.   
  18.         /// <summary>  
  19.         /// Public constructor to initialize product service instance  
  20.         /// </summary>  
  21.         public ProductController()  
  22.         {  
  23.             _productServices =new ProductServices();  
  24.         }  
  25.  
  26.         #endregion  
  27.   
  28.         // GET api/product  
  29.         public HttpResponseMessage Get()  
  30.         {  
  31.             var products = _productServices.GetAllProducts();  
  32.               if(products!=null)
  33.                 {
  34.                   var productEntities = products as List<ProductEntity> ?? products.ToList();  
  35.                      if (productEntities.Any())  
  36.                         return Request.CreateResponse(HttpStatusCode.OK, productEntities);  
  37.                 }
  38.             return Request.CreateErrorResponse(HttpStatusCode.NotFound, "Products not found");  
  39.         }  
  40.   
  41.         // GET api/product/5  
  42.         public HttpResponseMessage Get(int id)  
  43.         {  
  44.             var product = _productServices.GetProductById(id);  
  45.             if (product != null)  
  46.                 return Request.CreateResponse(HttpStatusCode.OK, product);  
  47.             return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No product found for this id");  
  48.         }  
  49.   
  50.         // POST api/product  
  51.         public int Post([FromBody] ProductEntity productEntity)  
  52.         {  
  53.             return _productServices.CreateProduct(productEntity);  
  54.         }  
  55.   
  56.         // PUT api/product/5  
  57.         public bool Put(int id, [FromBody]ProductEntity productEntity)  
  58.         {  
  59.             if (id  > 0)  
  60.             {  
  61.                 return _productServices.UpdateProduct(id, productEntity);  
  62.             }  
  63.             return false;  
  64.         }  
  65.   
  66.         // DELETE api/product/5  
  67.         public bool Delete(int id)  
  68.         {  
  69.             if (id > 0)  
  70.                 return _productServices.DeleteProduct(id);  
  71.             return false;  
  72.         }  
  73.     }  
  74. }  

Just run the application and we will get:

But now how do we test our API? We don't have a client. Guys, we'll not be writing a client now to test it. We'll add a package that will do all the work.

Just go to Manage Nuget Packages by right-clicking WebAPI project and type WebAPITestClient into the search box in online packages as in the following:

You'll get “A simple Test Client for ASP.NET Web API”, just add it. You'll get a help controller in Areas -> HelpPage like shown below.

Running the Application

Before running the application, I have put some test data in our product table.

Just hit F5, you get the same page as you got earlier, just append “/help” to its URL and you'll get the test client as in the following:

You can test each service by clicking on it.

Service for GetAllProduct:

To create a new product:

In the database, we get a new product as in the following:

Update product:

We get the following in the database:

Delete product:

In the database:

Job done.

Design Flaws
  1. The architecture is tightly coupled. Inversion of Control (IOC) needs to be there.
  2. We cannot define our own routes.
  3. No exception handling and logging.
  4. No unit tets.
Conclusion

We now know how to create a WebAPI and perform CRUD operations using an n layered architecture.

But still, there are some flaws in this design. In my next two articles I'll explain how to make the system loosely-coupled using the Dependency Injection Principle. We'll also cover all the design flaws to make our design better and stronger. Until then Happy Coding. You can also download the source code from GitHub.

Read more:

For more technical articles you can reach out to CodeTeddy

My other series of articles:

 

Up Next
    Ebook Download
    View all
    Learn
    View all