Click here to Skip to main content
13,143,008 members (57,624 online)
Click here to Skip to main content
Add your own
alternative version

Stats

13.9K views
39 bookmarked
Posted 23 Jun 2017

Architecting Web API Project With Repository Layer,Dependency Injection And Entity FrameWork 6.0

, 23 Jun 2017
Rate this:
Please Sign up or sign in to vote.
Here in this article we will learn step by step creating WebAPI with layered archicture,dependecy Injection and code first migration.

Introduction

ASP.Net Web API is a framework from Microsoft to develop RESTful services that reach a broad range of clients like(Browser,Mobile,Desktop application,iot,etc).It is mainly used to create HTTP  services.
These API transfer data through XML,JSON or any format.So in this article we will learn how to create web Api.Here  you will get to know abouy following things.

Table of contents

  • Introduction of REST and WebAPI
  • Working with WEBAPI project.
  • Creating project Layer in WEB API.
  • Working with Entity Framework Identity framework.
  • Dependency Injection in WebAPI.
  • Repository pattern Implemantation.
  • Testing API using POSTMAN.
  • Authentication and Authorization in Web API
     

 Introduction To REST AND WEB API

As we know webAPI is used to create Restful service.so lets look what exactly the REST is???

REST stands for "Representational state transfer".So to find the exact meaning of REST i did a homework and got diffrent ideas of diffrent authors,but what i understand is given below.

Representational - Every data,image, page, video,audio, etc in the server is consider as Resources.
When a user requested for any resources to the server the resources are transmitted to the user and  represented  in diffrent format like HTML, Image, JSON, XML etc.
 So the term Representational described how the data is represented to its client.If it is an website normally the data is represented by HTML where as if we are creating any services it may be represented as JSON or XML.

State Transfer:-The  data or information/resources of a particular entity present in a specific time in the server is called state.
so the state of the entity may be change over time.Lets take an example of registered user records present in the server,So the  registered users can be increased or decresed at any time, Changing the users per time represents its state .
So the term transfer represent the exchange of these state resources to the clients over HTTP protocol.To access these data from the client side we have HTTP verbs like(GET,POST,PUT,DELETE).
 

So now lets start creating a WebApi from scratch.To crate a webapi project goto create new Project and select an api template there,



After selecting this Web API template .please change the Authntication to Individual User Account.

This account is mainly responsible to add the controllers,model that are related to the User Identity database.If you dont select any authentication by default No Authentication will be selected.After clicking OK you will successfully create your project as follow.
 

So here our main intension to create a  WEBAPI project with Layered archicture with implementing Repository pattern and Dependency Injection.So add 4 more projects of type class Library and give them a meaning ful name as follow.

So lets see and find the Purpose of each layer here.

  1. WebAPI.IBLL Layer:

    As we will work on Repository pattern we need a intermediate layer between our controller and Data Access layer,so this layer plays an important role in creating loosly coupled application by acting as a mediator between the controller and Dataaccess layer.
    This layer mainly contains interfaces  where the abstract methods are declared.
    Lets consider we are going to create a products related API projects, so this layer will contain interface as follow.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WebAPI.Model;



namespace WebAPI.IBLL
{
    public interface IProductDetails
    {
        bool SaveProducts(Products pob);
        List<Products> showDetails();
        bool DeleteDetails(int id);
    }
}

 

 2.WebAPI.BLL Layer:
 

This layer  contains classes  which is mainly used to implement all the interface methods in that.The classes which implement the interface abstract methods must be inherited from that interface.

This layer can be considered as the Data Access layer of the project.This contains all the data quering techniques to interact directly with database.

As shown in the image i have added a class file called "productDetailsHelper"  and write the following code.

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;
using WebAPI.IBLL;
using WebAPI.Model;
using WebAPI.Data;
//using MyWebAPI;

namespace WebAPI.BLL
{
    public class ProductDetails : IProductDetails
    {
        APIContext _webcontext = new APIContext();

        public List<Products> showDetails()
        {
            List<Products> li = new List<Products>();
            var details = _webcontext.products.ToList();
            if (details != null)
            {
                Parallel.ForEach(details, x =>
                {
                    Products obj = new Products();
                    obj.slNO = x.slNO;
                    obj.ProductName = x.ProductName;
                    obj.Price = Convert.ToInt32(x.Price);
                    li.Add(obj);

                });
                return li;
            }
            else
            {
                return li;
            }

        }

        public bool SaveProducts(Products pobj)
        {
               _webcontext.products.Add(pobj);
             int m=  _webcontext.SaveChanges();
            if(m==1)
            {
                return true;
            }
            else
            {
                return false;
            }


            
        }

        public bool DeleteDetails(int id)
        {
            var Info = _webcontext.products.Where(m => m.productId == id).FirstOrDefault();
            _webcontext.products.Remove(Info);
            if (_webcontext.SaveChanges() == 0)
                return true;
            return false;
        }
    }


}

So if we see without  any WebAPI.IBLL Layer our project will be tightly coupled and be seen like this.




Why it is Tightly coupled???

Tightly coupled means two classes or two modules are fully dependent upon each other. Changing one class object may lead to change in several areas in the other class.When we are creating an object of a class and calling other class method by its object then we can say that the two classes are tightly coupled or dependent on each other.
So here in this scenario to decouple this Bussiness module and DataAccess Layer we have introduced a new layer called as "WebAPI.BLL Layer".

so a repository Layer acts like a intermediate layer between the Bussiness classes(it can be anythings like your api controller,classes,etc) and the data access logic(which mainly contain logic to connect with DB and doing CRUD opperation).
 A repository isolates all the data access code from bussiness logic.

After using this repository layer now our application looks in such way.

 
3.WebAPI.Data:This layer mainly contains your  Edmx files if you are adding your database entities using "Entity Data Model wizard". Here i have not added any edmx file rather then that i have manually added my own context.
So i have named it as "APIContext".

The class that is inherits from DbContext is called context class in entity framework.DbContext is an important part of Entity Framework. It act a bridge between your domain or entity classes and the database.
DbContext  performs 3 important functions these are-


  1. DbContext contains entity set (DbSet<TEntity>) for all the entities which is mapped to DB tables.
  2. DbContext converts LINQ-to-Entities queries to SQL query and send it to the database for CRUD opperation.
  3. DbContext converts raw table data into entity objects.
  4. DbContext constructor takes the connection string name to check the DB.If the DB is present it will work on it else it will create a new DB.
     

Here is my context class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;
using WebAPI.Model;

namespace WebAPI.Data
{
    public class APIContext:DbContext
    {
        public APIContext() : base("DefaultConnection")
        {

        }
        public DbSet<Products> products { get; set; }

      

    }
}

This APIContext class is inheritad from Dbcontext class and i am passing my connection string name in this constructor.
Note:In this Layer we need to add  entity frame DLL.

4.WebAPI.Model:
This contain all the models.Each models is considered as a table.so these models names are passed to the Dbset.

The properties of each models contains the vallidation attributes.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;



namespace WebAPI.Model
{

    [Table("products")]
    public class Products
    {
        [Key]
        public int slNO { get; set; }
       
        [Required(ErrorMessage = "Product Id is Needed.")]
        public Int64 ? productId { get; set; }
        [Required(ErrorMessage = "Product Name is Required.")]
        public string ProductName { get; set; }
        [Required(ErrorMessage = "Price is Required.")]
        public int Price { get; set; }
       

    }

    }

So in this way we can make  layering our project.Now lets see the archicture diagram of the project.

Here is the connection string in Web.config 

 <connectionStrings>
   
    <add name="DefaultConnection" connectionString="Data Source=*********; User Id=*****; Password=****;Initial Catalog=Temp" providerName="System.Data.SqlClient" />
  </connectionStrings>

Here is the Script for the Product table.

USE [Temp]
GO

/****** Object:  Table [dbo].[products]    Script Date: 20-06-2017 22:29:15 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[products](
    [ProductName] [nvarchar](50) NULL,
    [Price] [int] NULL,
    [slNO] [int] IDENTITY(1,1) NOT NULL,
    [productId] [bigint] NULL,
    [ImageUrl] [nvarchar](450) NULL,
 CONSTRAINT [PK_products] PRIMARY KEY CLUSTERED 
(
    [slNO] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

Now its seems to be every thing is set,now we will create an api controller and  perform our CRUD opperation.So here let me add my API controller.

So as per our flow-

  • We need to pass our data from api controller to WebAPI.BLL layer for any opperation.
  • so to pass our data from api controller to productDetail class methods we need to create object  of productDetail class and using that object we have to call its methods.
  • we will do something like this-

 

 [Route("addProduct")]
        [Authorize]
        public async Task<HttpResponseMessage> saveProductDetails(Products pod)
        {

            ProductDetails obj = new ProductDetails();
            obj.SaveProducts(pod)
         }

So as we are thinking to do like this it will make our application tightly couple.So if the object creating principle can  be removed and done by some other third party object at run time then it will be good for us and our application become loosly coupled.so to create object automatically during runtime we need to add some dll to our project.

So here i am adding Ninject to our application  which will mainly take care of this things and makes our application independent.This whole strategy is considered as dependency Injection. 

Dependency Injection:

Dependency injection is a technique to develop software in an independent way. Independent in the sense   no module will depend on another module in the software development.So the dependency injection technique will inject all dependencies of lower modules and provide these dependency to the higher module without direct contact.
Note:
High-level modules/classes implement business rules or logic in an application.
Low-level modules/classes deal with different operations; in other words, they mainly deal with writing  databases logics,communicating with external entities,etc,
There are three basic type of Dependency Injection

  1. Constructor Injection
  2. property injection
  3. Methods Injection

    Constructor injection is the most commonly used Dependency injection.To use this we need two things-
    1. Repository pattern archicture
    2. A DI container
     we have already implement our project in Repository pattern,so here we need a DI  Container.Here i am using Ninject for this.

 

After installing this you will find this NinjectWebCommon.cs in App_Start.
 

Double click this and  configure your dependency like this in RegisterServices like this.

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(MyProductAPI.App_Start.NinjectWebCommon), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethodAttribute(typeof(MyProductAPI.App_Start.NinjectWebCommon), "Stop")]

namespace MyProductAPI.App_Start
{
    using System;
    using System.Web;

    using Microsoft.Web.Infrastructure.DynamicModuleHelper;

    using Ninject;
    using Ninject.Web.Common;
    using WebAPI.IBLL;
    using WebAPI.BLL;
    using System.Web.Http;
    using WebApiContrib.IoC.Ninject;

    public static class NinjectWebCommon 
    {
        private static readonly Bootstrapper bootstrapper = new Bootstrapper();

        /// <summary>
        /// Starts the application
        /// </summary>
        public static void Start() 
        {
            DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
            DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
            bootstrapper.Initialize(CreateKernel);
        }
        
        /// <summary>
        /// Stops the application.
        /// </summary>
        public static void Stop()
        {
            bootstrapper.ShutDown();
        }
        
        /// <summary>
        /// Creates the kernel that will manage your application.
        /// </summary>
        /// <returns>The created kernel.</returns>
        private static IKernel CreateKernel()
        {
            var kernel = new StandardKernel();
            try
            {
                kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
                kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();

                RegisterServices(kernel);
                GlobalConfiguration.Configuration.DependencyResolver = new NinjectResolver(kernel);
                return kernel;
            }
            catch
            {
                kernel.Dispose();
                throw;
            }
        }

        /// <summary>
        /// Load your modules or register your services here!
        /// </summary>
        /// <param name="kernel">The kernel.</param>
        private static void RegisterServices(IKernel kernel)
        {
           
            kernel.Bind<IProductDetails>().To<ProductDetails>().InRequestScope();
        }        
    }
}

Note:Please note this is a autogenerated code  you need to take care only RegisterServices code.

Now go to your web api controller and create a constructor to inject the dependency like this.

[RoutePrefix("api/product")]
    public class ProductsController : ApiController
    {

       
        IProductDetails iprod;
        
        public ProductsController(IProductDetails _iprod)
        {
            iprod = _iprod;

        }

Now using this iprod i can call all my Bll methods and thus here i will create all my apis like this.

 

[HttpPost]
        [Route("addProduct")]
        [Authorize]
        public async Task<HttpResponseMessage> saveProductDetails(Products pod)
        {

            

            Dictionary<string, string> dict = new Dictionary<string, string>();
            bool res = false;
            string errordetails = "";
            var errors = new List<string>();
            if (ModelState.IsValid)
            {
                try
                {
                    res = iprod.SaveProducts(pod);

                }
                catch(Exception ex)
                {
                    throw ex;
                }
              

            }
            else
            {
               
               
                foreach (var state in ModelState)
                {
                    foreach (var error in state.Value.Errors)
                    {
                        string p = error.ErrorMessage;
                        errordetails = errordetails + error.ErrorMessage;

                    }
                }
           
                dict.Add("error", errordetails);
                return Request.CreateResponse(HttpStatusCode.BadRequest, dict);

            }
           
            if (res == true)
            {
                var showmessage = "Product Saved Successfully.";

                dict.Add("Message", showmessage);
                return Request.CreateResponse(HttpStatusCode.OK, dict);

            }
            else
            {
                var showmessage = "Product Not Saved Please try again.";

                dict.Add("Message", showmessage);
                return Request.CreateResponse(HttpStatusCode.BadRequest, dict);

            }
        }

        [Route("showproduct")]
        [HttpGet]
       
        public async Task<HttpResponseMessage> getAllProduct()
        {
            Dictionary<string, string> dict = new Dictionary<string, string>();
            var details = iprod.showDetails();
            if (details != null)
            {
                return Request.CreateResponse(HttpStatusCode.OK, details);

            }
            else
            {
                var showmessage = "No product found.";

                dict.Add("Message", showmessage);
                return Request.CreateResponse(HttpStatusCode.OK, details);

            }



        }


        }
    }

so now our project is ready lets check using PostMan client.I have shown you only for create a new product,similarly if anyone want can check for all operation.


If we check our table it will be added there.

So in this way we can work with web API.
 

Authorization and Authentication in WEB API
 

Here in the above context anyone can call the api can consume the data exposed by api and may be there is a chance of hack the sensitive information.So we have to protect our data and only give permission to our authenticated user.So to implement this we need to implement Token Based Authentication here.

                                            (A random image from google image)

 

In Web API  2.0 onword if you see the token based authentication has alresdy implement and we need to utilise in a correct manner. So give security to our application we have to do following steps.

  • Create some authenticate user for this application.(This process can be possible by create users in identity DB)
  • Before each request verify the user if it is authenticate user then only give the resources.(This process can be done by successfully signin and getting Token.)
    So the Authntication process works like this.

    So here finally we need a valid Token which is generated by successfully loggedin to the system.
     

To work on all these we have Identity concept.It provide database also.Please note also the code found in  Account Control is a part of that.Here i am showing the Registration Part only.

 // POST api/Account/Register
        [AllowAnonymous]
        [Route("Register")]
        public async Task<IHttpActionResult> Register(RegisterBindingModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };

            IdentityResult result = await UserManager.CreateAsync(user, model.Password);

            if (!result.Succeeded)
            {
                return GetErrorResult(result);
            }

            return Ok();
        }

And Model Related to this is found in Model Folder of "AccountBinding Model".Here is the model code.
 

 public class RegisterBindingModel
    {
        [Required]
        [Display(Name = "Email")]
        public string Email { get; set; }

        [Required]
        [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "Confirm password")]
        [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }


    }

Note that it has a seperate DBcontext called "ApplicationDbContext" which is found in IdentityModel.cs.

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;

namespace MyProductAPI.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit https://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager, string authenticationType)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
            // Add custom user claims here
            return userIdentity;
        }
    }

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection", throwIfV1Schema: false)
        {
        }
        
        public static ApplicationDbContext Create()
        {
            return new ApplicationDbContext();
        }
    }
}


//

// Any source code blocks look like this
//

NOTE:-For both the DBContext the Connection name is "DefaultConnection". As a result of this it will create the identity in Temp database.

Now run the application and try to create an user in PostMan.

So the identity related all tables are created in Temp Database and this user will be saved there.


Now if you check the ASPNETUsers then you will find the User.

Now we need to check the authenticity of a user and if it is a valid user we will grant our resources.so for this we need to call the SignIn api.
Here in Startup.auth.cs the SignIn Endpoint is exposed.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Google;
using Microsoft.Owin.Security.OAuth;
using Owin;
using MyProductAPI.Providers;
using MyProductAPI.Models;

namespace MyProductAPI
{
    public partial class Startup
    {
        public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }

        public static string PublicClientId { get; private set; }

        // For more information on configuring authentication, please visit https://go.microsoft.com/fwlink/?LinkId=301864
        public void ConfigureAuth(IAppBuilder app)
        {
            // Configure the db context and user manager to use a single instance per request
            app.CreatePerOwinContext(ApplicationDbContext.Create);
            app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

            // Enable the application to use a cookie to store information for the signed in user
            // and to use a cookie to temporarily store information about a user logging in with a third party login provider
            app.UseCookieAuthentication(new CookieAuthenticationOptions());
            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

            // Configure the application for OAuth based flow
            PublicClientId = "self";
            OAuthOptions = new OAuthAuthorizationServerOptions
            {
                TokenEndpointPath = new PathString("/Token"),
                Provider = new ApplicationOAuthProvider(PublicClientId),
                AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
                // In production mode set AllowInsecureHttp = false
                AllowInsecureHttp = true
            };

           
        }
    }
}

So here you can change the endPoint  as per your routing.Now it will poing like 

/token.

  • you can make it point like api/Token,etc as per your requirement.
  • The Token lifespan is bydefault  14 days .you can change as per your own  requirement. 

    Lets check the token generation in PostMan.

     

As this is a valid user so the token is generated.Now User the following attribute in the api method which you want to protact.In my show Product i have Used Authorize attribute so unauthincated user can  able to access this.lets try to access this without any access token.

 [Route("showproduct")]
        [HttpGet]
        [Authorize]
        public async Task<HttpResponseMessage> getAllProduct()
        {
            Dictionary<string, string> dict = new Dictionary<string, string>();
            var details = iprod.showDetails();
            if (details != null)
            {
                return Request.CreateResponse(HttpStatusCode.OK, details);

            }
            else
            {
                var showmessage = "No product found.";

                dict.Add("Message", showmessage);
                return Request.CreateResponse(HttpStatusCode.OK, details);

            }



        }



So here we seen that if an   API is marked as an Authorized apI,It cannot accessable by unknown or unauthorized User,It can only be accessed by  authorized user. 
Now  to check this pass the token in API Header  and try to access the GET API.

 

By Using the Token we are able to get all product Details.

NOTE: While passing token in Header we need to append the Token with Bearer as shown above.

Points of Interest

 

So in this way we can create a web api project and secure our API methods.If you have any doubt on this please comment or mail me in my mail Id.

In my next article i will show how to work on Password Change,Sending mail for conforming account.Hope you will like this article.

History

1.0.0.0

License

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

Share

About the Author

Debendra0256
Software Developer
India India

Achievements:

* C# Cornor MVP-2015
* C# Cornor MVP-2016
* C# Cornor MVP-2017 


You may also be interested in...

Comments and Discussions

 
QuestionGreat Article Sir Pin
Member 1341213015-Sep-17 7:07
memberMember 1341213015-Sep-17 7:07 
QuestionPlease provide source code Pin
Mou_kol4-Sep-17 22:51
memberMou_kol4-Sep-17 22:51 
QuestionUsing log4net with the current implementation of the WebAPI Pin
Member 1306159429-Aug-17 13:58
memberMember 1306159429-Aug-17 13:58 
AnswerRe: Using log4net with the current implementation of the WebAPI Pin
Debendra02562-Sep-17 6:30
memberDebendra02562-Sep-17 6:30 
PraiseRe: Using log4net with the current implementation of the WebAPI Pin
Member 130615944-Sep-17 10:06
memberMember 130615944-Sep-17 10:06 
QuestionGood Pin
Member 133507487-Aug-17 18:49
memberMember 133507487-Aug-17 18:49 
QuestionGreat writing. Pin
Member 132959905-Jul-17 22:46
memberMember 132959905-Jul-17 22:46 
QuestionExcellent Article Pin
Member 132908143-Jul-17 8:45
memberMember 132908143-Jul-17 8:45 
QuestionCan we access the source code? Pin
Member 1327713425-Jun-17 13:45
memberMember 1327713425-Jun-17 13:45 
AnswerRe: Can we access the source code? Pin
Debendra025626-Jun-17 4:22
memberDebendra025626-Jun-17 4:22 
QuestionImages not loading Pin
meezy23-Jun-17 17:45
membermeezy23-Jun-17 17:45 
AnswerRe: Images not loading Pin
Debendra025626-Jun-17 4:25
memberDebendra025626-Jun-17 4:25 

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

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

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.170915.1 | Last Updated 24 Jun 2017
Article Copyright 2017 by Debendra0256
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid