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
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
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
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();
}
}
}
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
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.
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.
3 Comments
Tanbir ! Thank you so much. My Dear ! SUPERB Article ! Again a basket of thanks
ReplyDeleteHello, 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?
ReplyDeleteHello. 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
Deleteand 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.