ASP.NET Core CRUD with Onion Architecture


Introduction

In this article, we will build an Asp.net Core MVC crud application using onion architecture.


What is Onion Architecture?

The Onion Architecture was created by Jeffrey Palermoin in 2008. Onion Architecture is to place the Domain and Services Layers at the center of your application. And externalize the Presentation and Infrastructure.

Why Use Onion Architecture

Onion Architecture provides a better way to build applications in perspective of better testability, maintainability, and dependability. Onion Architecture addresses the challenges faced with 3-tier and n-tier architectures, and to provide a solution for common problems. Onion architecture layers interact with each other by using the Interfaces.

Project Structure

We will develop an asp.net core 2.2 crud application using onion architecture. Let’s see our project structure.


Implementation 

At first, create six projects as mentioned before. Let’s have look again

 

Project Diagram

We Draw a diagram where we can see how our six projects communicate with each other.



Onion Architecture Diagram

Here we draw an onion diagram. We are showing here how our project working as Onion Architecture perspective.

 


Onion.Infrastructure

Let's talk about Onion.Infrastructure project. Here we will create our system Infrastructure. This is a fully independent project. It will help us to connect between to project. Here it will help us to catch data from the presentation layer to the business layer. 

At first, create a new class library project and name it Onion.Infrastructure.


Next, create a new class inside the Onion.Infrastructure project and name it UserDM and put the following code in it.

namespace Onion.Infrastructure
{
    public class UserDM
    {
        public long Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string MiddleName { get; set; }

    }
}


Onion.repository

let’s talk about Onion.Repository project. Here we will create our context class and repositories. At first, create a class library project and name it Onion.Repository.  


Now go to your Package Manager Console and execute these three commands

Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools
Install-Package Microsoft.EntityFrameworkCore.Design 

Here we will use the Database first approach. Go to your SQL Server Management Studio. Create a database and name it OnionDB and execute the following SQL scripts

GO
CREATE TABLE [dbo].[tblUser](
 [Id] [bigint] IDENTITY(1,1) NOT NULL,
 [FirstName] [varchar](50) NULL,
 [lastName] [varchar](50) NULL,
 [MiddleName] [varchar](50) NULL,
 CONSTRAINT [PK_tblUser] PRIMARY KEY CLUSTERED 
(
 [Id] 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 go back to Onion.Repository project. Create a folder and name it DB then go to your Package Manager Console and execute these commands. Before exection please replace your server name, user id and password field.

Scaffold-DbContext "Server= YourServerName;Database=OnionDB;user id= YourUserId;password= YourPassword;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir DB

Now we can see our Context class and database table model created inside the DB folder. Next, create an interface and name it IUserRepository and put the following code in it.

using Onion.Repository.DB;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Onion.Repository
{
    public interface IUserRepository
    {
         Task<bool> Add(TblUser user);
         Task<bool> Update(TblUser user);
         Task<List<TblUser>> GetAll();
         Task<TblUser> GetById(long id);
         Task<bool> Delete(long id);
    }
}

Next, create a new class and name it UserRepository and put the following code in it.

using Microsoft.EntityFrameworkCore;
using Onion.Repository.DB;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Onion.Repository
{
    public class UserRepository : IUserRepository
    {
        private readonly OnionDBContext _context;
        public UserRepository(OnionDBContext context)
        {
            _context = context;
        }
        public async Task<bool> Add(TblUser user)
        {
            try
            {
               await _context.TblUser.AddAsync(user);
               await _context.SaveChangesAsync();
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }

        }

        public async Task<bool> Delete(long id)
        {
            try
            {
                var result = await _context.TblUser.Where(e => e.Id == id).FirstOrDefaultAsync();
                if (result != null)
                {
                    _context.TblUser.Remove(result);
                    await _context.SaveChangesAsync();
                }
                return true;
            }
            catch (Exception ex)
            {

                return false;
            }
        }

        public async Task<List<TblUser>> GetAll()
        {
            var result =await _context.TblUser.ToListAsync();
            return result;
        }

        public async Task<TblUser> GetById(long id)
        {
            var result = await _context.TblUser.Where(e => e.Id == id).FirstOrDefaultAsync();
            return result;
        }

        public async Task<bool> Update(TblUser user)
        {
            try
            {
                _context.TblUser.Update(user);
                await _context.SaveChangesAsync();
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
         
        }
    }
}

Onion.Service

let’s talk about Onion.Service project. Here we will create services and there represented interfaces. At first, create a new class library project and name it Onion.Service

 

Next, create a new Interface inside the Onion.Service project and name it IUserService and put the following code in it.

using Onion.Infrastructure;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Onion.Service
{
    public interface IUserService
    {
        Task<bool> AddAsync(UserDM userDM);
        Task<bool> UpdateAsync(UserDM userDM);
        Task<List<UserDM>> GetAllAsync();
        Task<UserDM> GetByIdAsync(long id);
        Task<bool> DeleteAsync(long id);
    }
}

Next, create a new Class inside the Onion.Service project and name it UserService and put the following code in it.

using Onion.Infrastructure;
using Onion.Repository;
using Onion.Repository.DB;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Onion.Service
{
      public class UserService : IUserService
    {
        private readonly IUserRepository _userRepository;
        public UserService(IUserRepository userRepository)
        {
            _userRepository = userRepository;
        }
        public async Task<bool> AddAsync(UserDM userDM)
        {
            try
            {
                var obj = new TblUser();
                obj.FirstName = userDM.FirstName;
                obj.MiddleName = userDM.MiddleName;
                obj.LastName = userDM.LastName;
                var result = await _userRepository.Add(obj);
                return result;
            }
            catch (Exception ex)
            {

                return false;
            }


         
        }
        public async Task<bool> DeleteAsync(long id)
        {
            try
            {
                var result = await _userRepository.Delete(id);
                return result;
            }
            catch (Exception ex)
            {

               return false;
            }
      
        }

        public async Task<List<UserDM>> GetAllAsync()
        {
            var userList = new List<UserDM>();
            var result = await _userRepository.GetAll();
            foreach (var item in result)
            {
                userList.Add(new UserDM
                {
                    Id = item.Id,
                    FirstName = item.FirstName,
                    MiddleName = item.MiddleName,
                    LastName = item.LastName
                });
            }
            return userList;
        }

        public async Task<UserDM> GetByIdAsync(long id)
        {
            var result = await _userRepository.GetById(id);
            var userDM = new UserDM();
            userDM.Id = result.Id;
            userDM.FirstName = result.FirstName;
            userDM.MiddleName = result.MiddleName;
            userDM.LastName = result.LastName;
            return userDM;
        }

        public async Task<bool> UpdateAsync(UserDM userDM)
        {
            try
            {
                var obj = new TblUser();
                obj.Id = userDM.Id;
                obj.FirstName = userDM.FirstName;
                obj.MiddleName = userDM.MiddleName;
                obj.LastName = userDM.LastName;
                var result = await _userRepository.Update(obj);
                return result;
            }
            catch (Exception ex)
            {

                return false;
            }
           
        }
    }

}

Onion.Ioc

In the Onion.Ioc project we will create our IOC container. For Implementation  At first, create a new class library project and name it Onion.Ioc .


Now create a new class inside the Onion.Ioc and name it IocContainer and put the following code in it.

using Microsoft.Extensions.DependencyInjection;
using Onion.Repository.DB;
using Onion.Repository;
using Onion.Service;

namespace Onion.Ioc
{
    public static class IocContainer
    {
        public static void ConfigureIOC(this IServiceCollection services)
        {
            services.AddTransient<IUserRepository, UserRepository>();
            services.AddTransient<IUserService, UserService>();;
            services.AddDbContext<OnionDBContext>();
        }
    }
}

Onion.Test

In the Onion.Test project we will test services. For implementation at first create NUnit Test project. Here we are using NUnit framework.



Now create a new class inside the Onion.Test project and name it Startup and put the following code in it.

using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Onion.Ioc;
using System;

namespace Onion.Test
{
    [SetUpFixture]
    public class Startup
    {
        internal static IServiceProvider ServiceProvider { get; set; }
        [OneTimeSetUp]
        public void RunBeforeAnyTests()
        {
            ServiceProvider = ContainerBuilder();
        }
        [OneTimeTearDown]
        public void RunAfterAnyTests()
        {

        }
        public IServiceProvider ContainerBuilder()
        {
            IServiceCollection services = new ServiceCollection();
            services.ConfigureIOC();
            return services.BuildServiceProvider();
        }
    }
}

Next, add the following code to the UnitTest1.cs class.

using NUnit.Framework;
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
using Onion.Infrastructure;
using Onion.Service;

namespace Onion.Test
{
    public class Tests
    {
        private  IUserService _userService;
        [SetUp]
        public void Setup()
        {
            var serviceProvider = Startup.ServiceProvider;
            if (serviceProvider != null)
            {
                _userService = serviceProvider.GetService();
            }
        }

        [Test]
        public async Task UserServiceAdd_TestAsync()
        {
            var user = new UserDM {
                FirstName = "Mohammed",
                MiddleName = "Tanbir",
                LastName = "Hossain"
            };
            var actualResult = await _userService.AddAsync(user);
            var expectedResult = true;
            Assert.AreEqual(expectedResult,actualResult);
        }
    }
}

To more know about unit test implementation  follow this article Click Here

Now right click on the Onion.Test project and click Run Tests. After executing test see the result below


Our unit test case passed successfully.

Onion.Web

Onion.web our UI part also its a presentation layer. It can be Web API, Desktop and web application . Here we choose Web application. At first, create an Asp.Net core 2.2 MVC  project and name it Onion.Web .


Now go to Startup class. Here we will register our IOC container. Inside the startup class, we can see a method name ConfigureServices. Inside the CofigureServices method paste the following code.

services.ConfigureIOC();

After that ConfigureServices method like this

// This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            services.ConfigureIOC(); //  Register IOC
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

Next, create a controller and name it UsersController. Add the following code to this controller.

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Onion.Infrastructure;
using Onion.Service;

namespace Onion.Web.Controllers
{

    public class UsersController : Controller
    {
        private readonly IUserService _userService;

        public UsersController(IUserService userService)
        {
            _userService = userService;
        }

        // GET: Users
        public async Task<IActionResult> Index()
        {
            return View(await _userService.GetAllAsync());
        }

        // GET: Users/Details/5
        public async Task<IActionResult> Details(long? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var user = await _userService.GetByIdAsync(id.Value);
            if (user == null)
            {
                return NotFound();
            }

            return View(user);
        }

        // GET: Users/Create
        public IActionResult Create()
        {
            return View();
        }

        // POST: Users/Create
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Create([Bind("Id,FirstName,LastName,MiddleName")] UserDM user)
        {
            if (ModelState.IsValid)
            {
                await _userService.AddAsync(user);

                return RedirectToAction(nameof(Index));
            }
            return View(user);
        }

        // GET: Users/Edit/5
        public async Task<IActionResult> Edit(long? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var user = await _userService.GetByIdAsync(id.Value);
            if (user == null)
            {
                return NotFound();
            }
            return View(user);
        }

        // POST: Users/Edit/5
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Edit(long id, [Bind("Id,FirstName,LastName,MiddleName")] UserDM user)
        {
            if (id != user.Id)
            {
                return NotFound();
            }

            if (ModelState.IsValid)
            {

                await _userService.UpdateAsync(user);


                return RedirectToAction(nameof(Index));
            }
            return View(user);
        }

        // GET: Users/Delete/5
        public async Task<IActionResult> Delete(long? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var user = await _userService.GetByIdAsync(id.Value);

            if (user == null)
            {
                return NotFound();
            }

            return View(user);
        }

        // POST: Users/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> DeleteConfirmed(long id)
        {
            var user = await _userService.DeleteAsync(id);
            return RedirectToAction(nameof(Index));
        }

    }

}

Next, we will create our views. Inside the Views folder create a folder and name it Users then inside the Users folder create a new view and name it Create. Add the following code to this view.





Next, create a new view inside the Users folder and name it Edit. Add the following code to this view.



again, create a new view inside the Users folder and name it Index. Add the following code to this view.



again, create a new view inside the Users folder and name it Delete. Add the following code to this view.



again, create a new view inside the Users folder and name it Details. Add the following code to this view.



Next, inside _Layout.cshtml file add this line



Now run the Onion.Web project and it will go to the browser. Now fill the text box and press Create button.

 




We can see our crud application working successfully.

Conclusion 

We are successfully created an ASP.NET CORE CRUD application using Onion Architecture. There is lots of ways you can control your application throw onion architecture. It will give you more flexible when your application size will increase. 


Source Code

Get the source code from GitHub.

Post a Comment

3 Comments

  1. Tanbir ! Thank you so much. My Dear ! SUPERB Article ! Again a basket of thanks

    ReplyDelete
  2. Hello, why did you create 2 identical classes, Userdb and Tbl User? Their attributes are the same and for example one is used in GetByIdAsync and the other in UpdateAsync. Why not create one and use it everywhere?

    ReplyDelete
    Replies
    1. Hello. Here I have used UserDM which is define as User Domain Model , you can also use as a View Model and TblUser is Entity Data Model. As a Onion Architecture , presentation layer cannot access data layer( Repository ) properties directly. From repository you will get data throw TblUser , after that map to UserDM model in the business layer
      and send to the presentation layer (Controller) . Remember , view model or domain model can be change based on your business logic but you cannot can change your Entity Model every time.

      Delete