Onion Architecture In .Net 5

Onion Architecture In .Net 5

| Architecture - .Net 5.0

In this article, we are going to cover the Onion architecture in ASP.Net 5.0. As we all know, it’s a newly launched framework officially released in the month of November. Here I am sharing the link to install the SDK for .Net 5

What we are going to cover in this .NET 5 Onion Architecture?

  • What is Onion Architecture

  • Layers of Onion Architecture

  • Implementation of Onion Architecture

  • Pros and Cons

What is Onion Architecture?

A large portion of the customary design raises basic issues of tight coupling and partition of concerns. Onion Architecture was acquainted by Jeffrey Palermo with giving a superior method to construct applications in the context of better testability, practicality, and constancy. Onion Architecture tends to the difficulties confronted with 3-tier and n-tier architectures and gives an answer for normal issues. Onion design layers associate with one another by utilizing the Interfaces.

Layers of Onion Architecture

Basically, it uses the concept of Layers but they are different from 3-tier and N-tier Layers. Let’s deep dive into each of these layers.

Domain Layer

It exists at the center part of the Onion architecture where it consists of all application domain entities which are nothing but database models created by the code first approach. In this project, I have used Fluent API in creating the table schema using Entity Framework

Repository Layer

The repository layer acts as a middle layer between the services and Model objects and in this layer, we will maintain all the Database migrations and application Data context object and in this layer, we typically add interfaces that will consist of data access pattern of reading and write operations involving a database.

Services Layer

This layer is used to communicate between the Repository layer and Main Project where it consists of exposable APIs. The Service layer also could hold business logic for an entity. In this layer, the service interfaces are kept separate from their implementation for loose coupling and also the separation of concerns.

UI Layer

The UI is nothing but a front-end application that will communicate with this API.

Implementation of Onion Architecture

Create a New project

A1.png

After clicking on the Next button add the project name and solution name and click on create button

A2.png

Choose the ASP.Net Core 5.0 template in the drop-down and also make sure to check the Enable Open API Support for default Swagger implementation in your project

A3.png

Default project will be created and now we need 3 empty Class library(.Net Core) projects inside this application as

  • DomainLayer

  • RepositoryLayer

  • ServicesLayer

A4.png

We will start first with Domain Layer

Domain Layer

Packages used in this Layer

Microsoft.EntityFrameworkCore(5.0.3) Microsoft.EntityFrameworkCore.Relational(5.0.3)

Create a folder named Models and inside that create Customer Class and BaseEntity class where customer class invokes this base entity

BaseEntity.cs

using System;  
using System.Collections.Generic;  
using System.Text;  

namespace DomainLayer.Models  
{  
   public class BaseEntity  
    {  
        public int Id { get; set; }  
        public DateTime CreatedDate { get; set; }  
        public DateTime ModifiedDate { get; set; }  
        public bool IsActive { get; set; }  
    }  
}

Customer.cs

using System;  
using System.Collections.Generic;  
using System.Text;  

namespace DomainLayer.Models  
{  
   public class Customer : BaseEntity  
   {  
        public string CustomerName { get; set; }  
        public string PurchasesProduct { get; set; }  
        public string PaymentType { get; set; }  
   }  
}

So, now we will create the actual table creation with this customer & base entity class. For that create a separate folder, EntityMapper, where we will maintain all our table schemas inside this folder

CustomerMap.cs

using DomainLayer.Models;  
using Microsoft.EntityFrameworkCore;  
using Microsoft.EntityFrameworkCore.Metadata.Builders;  

namespace DomainLayer.EntityMapper  
{  
    public class CustomerMap : IEntityTypeConfiguration<Customer>  
    {  
        public void Configure(EntityTypeBuilder<Customer> builder)  
        {  
            builder.HasKey(x => x.Id)  
                .HasName("pk_customerid");  

            builder.Property(x => x.Id).ValueGeneratedOnAdd()  
                .HasColumnName("id")  
                   .HasColumnType("INT");  
            builder.Property(x => x.PurchasesProduct)  
                .HasColumnName("purchased_product")  
                   .HasColumnType("NVARCHAR(100)")  
                   .IsRequired();  
            builder.Property(x => x.PaymentType)  
              .HasColumnName("payment_type")  
                 .HasColumnType("NVARCHAR(50)")  
                 .IsRequired();  
            builder.Property(x => x.CreatedDate)  
              .HasColumnName("created_date")  
                 .HasColumnType("datetime");  
            builder.Property(x => x.ModifiedDate)  
              .HasColumnName("modified_date")  
                 .HasColumnType("datetime");  
            builder.Property(x => x.IsActive)  
              .HasColumnName("is_active")  
                 .HasColumnType("bit");  
        }  
    }  
}

Domain Layer Structure

A5.png

Repository Layer

Packages used in this Layer

Microsoft.EntityFrameworkCore(5.0.3) Microsoft.EntityFrameworkCore.Design(5.0.3) Microsoft.EntityFrameworkCore.SqlServer(5.0.3) Microsoft.EntityFrameworkCore.Tools(5.0.3)

So far we designed our table in the Domain layer now will create the same table using migration commands in SQL DB. let’s create the connection string in our main project.

appsettings.json

{  
  "Logging": {  
    "LogLevel": {  
      "Default": "Information",  
      "Microsoft": "Warning",  
      "Microsoft.Hosting.Lifetime": "Information"  
    }  
  },  
  "AllowedHosts": "*",  
  "ConnectionStrings": {  
    "myconn": "server=YOUR Server Name; database=onionarcDb;Trusted_Connection=True;"  
  }  
}

Setup the connection in startup.cs file under the ConfigureMethod

Startup.cs

public void ConfigureServices(IServiceCollection services)  
        {  

            services.AddControllers();  
            services.AddSwaggerGen(c =>  
            {  
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "OnionArchitecture", Version = "v1" });  
            });  

            #region Connection String  
            services.AddDbContext<ApplicationDbContext>(item => item.UseSqlServer(Configuration.GetConnectionString("myconn")));  
            #endregion  

        }

Now switch back to Repository Layer and create a DataContext file where it represents a session with the database and can be used to query and save instances of your entities

ApplicationDbContext.cs

using DomainLayer.EntityMapper;  
using DomainLayer.Models;  
using Microsoft.EntityFrameworkCore;  
using System;  


namespace RepositoryLayer  
{  
    public partial class ApplicationDbContext : DbContext  
    {  
        public ApplicationDbContext(DbContextOptions options) : base(options)  
        {  
        }  

        protected override void OnModelCreating(ModelBuilder modelBuilder)  
        {  
            modelBuilder.ApplyConfiguration(new CustomerMap());  

            base.OnModelCreating(modelBuilder);  
        }  
    }  
}

Let’s create the table in SQL using the migration commands. Open the package manager console and switch the default project to Repositorylayer and execute the below commands one after another.

A6.png

Commands to execute

Add-Migration ‘CreateCustomerTable’ Update-database

ASP.Net Core is designed in such a way to support dependency injection. Now we create a generic repository interface for the entity operations so that we can see the loosely coupled application. Below is the code snippet

IRepository.cs

using DomainLayer.Models;  
using System;  
using System.Collections.Generic;  
using System.Text;  

namespace RepositoryLayer.RespositoryPattern  
{  
   public interface IRepository<T> where T : BaseEntity  
    {  
        IEnumerable<T> GetAll();  
        T Get(int Id);  
        void Insert(T entity);  
        void Update(T entity);  
        void Delete(T entity);  
        void Remove(T entity);  
        void SaveChanges();  
    }  
}

Create the repository class to perform the database operations which inherit the IRepository interface.

Repository.cs

using DomainLayer.Models;  
using Microsoft.EntityFrameworkCore;  
using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  

namespace RepositoryLayer.RespositoryPattern  
{  
   public class Repository<T> : IRepository<T> where T: BaseEntity  
    {  
        #region property  
        private readonly ApplicationDbContext _applicationDbContext;  
        private DbSet<T> entities;  
        #endregion  

        #region Constructor  
        public Repository(ApplicationDbContext applicationDbContext)  
        {  
            _applicationDbContext = applicationDbContext;  
            entities = _applicationDbContext.Set<T>();  
        }  
        #endregion  

        public void Delete(T entity)  
        {  
            if (entity == null)  
            {  
                throw new ArgumentNullException("entity");  
            }  
            entities.Remove(entity);  
            _applicationDbContext.SaveChanges();  
        }  

        public T Get(int Id)  
        {  
            return entities.SingleOrDefault(c => c.Id == Id);  
        }  

        public IEnumerable<T> GetAll()  
        {  
            return entities.AsEnumerable();  
        }  

        public void Insert(T entity)  
        {  
            if (entity == null)  
            {  
                throw new ArgumentNullException("entity");  
            }  
            entities.Add(entity);  
            _applicationDbContext.SaveChanges();  
        }  

        public void Remove(T entity)  
        {  
            if (entity == null)  
            {  
                throw new ArgumentNullException("entity");  
            }  
            entities.Remove(entity);  
        }  

        public void SaveChanges()  
        {  
            _applicationDbContext.SaveChanges();  
        }  

        public void Update(T entity)  
        {  
            if (entity == null)  
            {  
                throw new ArgumentNullException("entity");  
            }  
            entities.Update(entity);  
            _applicationDbContext.SaveChanges();  
        }  

    }  
}

Repository Layer Structure

A7.png

Service Layer

This contains the Core Business Logic as part of our project which acts as a layer between the Repositorylayer and Controller.

ICustomerService.cs

using DomainLayer.Models;  
using System.Collections.Generic;  

namespace ServicesLayer.CustomerService  
{  
   public interface ICustomerService  
    {  
        IEnumerable<Customer> GetAllCustomers();  
        Customer GetCustomer(int id);  
        void InsertCustomer(Customer customer);  
        void UpdateCustomer(Customer customer);  
        void DeleteCustomer(int id);  
    }  
}

CustomerService.cs

using DomainLayer.Models;  
using RepositoryLayer.RespositoryPattern;  
using System;  
using System.Collections.Generic;  
using System.Text;  

namespace ServicesLayer.CustomerService  
{  
    public class CustomerService : ICustomerService  
    {  
        #region Property  
        private IRepository<Customer> _repository;  
        #endregion  

        #region Constructor  
        public CustomerService(IRepository<Customer> repository)  
        {  
            _repository = repository;  
        }  
        #endregion  

        public IEnumerable<Customer> GetAllCustomers()  
        {  
            return _repository.GetAll();  
        }  

        public Customer GetCustomer(int id)  
        {  
            return _repository.Get(id);  
        }  

        public void InsertCustomer(Customer customer)  
        {  
            _repository.Insert(customer);  
        }  
        public void UpdateCustomer(Customer customer)  
        {  
            _repository.Update(customer);  
        }  

        public void DeleteCustomer(int id)  
        {  
            Customer customer = GetCustomer(id);  
            _repository.Remove(customer);  
            _repository.SaveChanges();  
        }  
    }  
}

Configure these services in the startup.cs file

Startup.cs

using Microsoft.AspNetCore.Builder;  
using Microsoft.AspNetCore.Hosting;  
using Microsoft.AspNetCore.HttpsPolicy;  
using Microsoft.AspNetCore.Mvc;  
using Microsoft.EntityFrameworkCore;  
using Microsoft.Extensions.Configuration;  
using Microsoft.Extensions.DependencyInjection;  
using Microsoft.Extensions.Hosting;  
using Microsoft.Extensions.Logging;  
using Microsoft.OpenApi.Models;  
using RepositoryLayer;  
using RepositoryLayer.RespositoryPattern;  
using ServicesLayer.CustomerService;  
namespace OnionArchitecture  
{  
    public class Startup  
    {  
        public Startup(IConfiguration configuration)  
        {  
            Configuration = configuration;  
        }  

        public IConfiguration Configuration { get; }  

        // This method gets called by the runtime. Use this method to add services to the container.  
        public void ConfigureServices(IServiceCollection services)  
        {  

            services.AddControllers();  
            services.AddSwaggerGen(c =>  
            {  
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "OnionArchitecture", Version = "v1" });  
            });  

            #region Connection String  
            services.AddDbContext<ApplicationDbContext>(item => item.UseSqlServer(Configuration.GetConnectionString("myconn")));  
            #endregion  

            #region Services Injected  
            services.AddScoped(typeof(IRepository<>),typeof(Repository<>));  
            services.AddTransient<ICustomerService, CustomerService>();  
            #endregion  
        }  

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.  
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  
        {  
            if (env.IsDevelopment())  
            {  
                app.UseDeveloperExceptionPage();  
                app.UseSwagger();  
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "OnionArchitecture v1"));  
            }  

            app.UseHttpsRedirection();  

            app.UseRouting();  

            app.UseAuthorization();  

            app.UseEndpoints(endpoints =>  
            {  
                endpoints.MapControllers();  
            });  
        }  
    }  
}

Create the API Methods in the customer controller which are exposable to UI (Front end app)

CustomerController.cs

using DomainLayer.Models;  
using Microsoft.AspNetCore.Http;  
using Microsoft.AspNetCore.Mvc;  
using ServicesLayer;  
using ServicesLayer.CustomerService;  
using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Threading.Tasks;  

namespace OnionArchitecture.Controllers  
{  
    [Route("api/[controller]")]  
    [ApiController]  
    public class CustomerController : ControllerBase  
    {  
        #region Property  
        private readonly ICustomerService _customerService;  
        #endregion  

        #region Constructor  
        public CustomerController(ICustomerService customerService)  
        {  
            _customerService = customerService;  
        }  
        #endregion  

        [HttpGet(nameof(GetCustomer))]  
        public IActionResult GetCustomer(int id)  
        {  
            var result = _customerService.GetCustomer(id);  
            if(result is not null)  
            {  
                return Ok(result);  
            }  
            return BadRequest("No records found");  

        }  
        [HttpGet(nameof(GetAllCustomer))]  
        public IActionResult GetAllCustomer()  
        {  
            var result = _customerService.GetAllCustomers();  
            if (result is not null)  
            {  
                return Ok(result);  
            }  
            return BadRequest("No records found");  

        }  
        [HttpPost(nameof(InsertCustomer))]  
        public IActionResult InsertCustomer(Customer customer)  
        {  
           _customerService.InsertCustomer(customer);  
            return Ok("Data inserted");  

        }  
        [HttpPut(nameof(UpdateCustomer))]  
        public IActionResult UpdateCustomer(Customer customer)  
        {  
            _customerService.UpdateCustomer(customer);  
            return Ok("Updation done");  

        }  
        [HttpDelete(nameof(DeleteCustomer))]  
        public IActionResult DeleteCustomer(int Id)  
        {  
            _customerService.DeleteCustomer(Id);  
            return Ok("Data Deleted");  

        }  
    }  
}

Onion Architecture Project Structure

A8.png

Let’s run and test this application to check the output in swagger or postman.

A9.png

As I have already inserted one record in the database by using InserCustomer API we will see the data by executing Get ALL Customers API.

A10.png

Pros and Cons in Onion Architecture

Following are the advantages of actualizing Onion Architecture:

  • Onion Architecture layers are associated through interfaces. Implantations are given during run time.

  • Application engineering is based on top of an area model.

  • All outer reliance, similar to data set admittance and administration calls, are addressed in outside layers.

  • No conditions of the Internal layer with outer layers.

  • Couplings are towards the middle.

  • Adaptable and feasible and convenient design.

  • No compelling reason to make normal and shared activities.

  • Can be immediately tried in light of the fact that the application center doesn’t rely upon anything.

A couple of disadvantages of Onion Architecture as follows:

  • Difficult to comprehend for amateurs, expectation to absorb information included. Modelers generally jumble up parting obligations between layers.

  • Intensely utilized interfaces

Hope this article helps you in a clear understanding of Onion Architecture. Please find the Source Code here

Support me bmc-button.png

…. Keep Learning !!!