Introduction
Onion Architecture is used to overcome both separation of concern and tightly coupling issues in our application.
Separation of Concern (Soc)
Wikipedia says “In computer science, separation of concerns (SoC) is a design principle for separating a computer program into distinct sections, such that each section addresses a separate concern. A concern is a set of information that affects the code of a computer program.”
SoC applies to web applications to separate the application into different layers, for example based on MVC architecture the application will be separated into Model, View and Controller.
Tightly Coupled
Tightly coupled object is an object that needs to know about another object. Tightly-coupled objects are highly dependant on each other’s interfaces. If we change one object, we need to change the other object as well.
Onion Architecture contains four layers,
- Domain Layer
- Repository Layer
- Service Layer
- UI/Presentation Layer
Domain/Data Access Layer
This is the core layer of the application, the classes in this layer are used to create a table in the database, basically this layer is used to build an entity.
Repository Layer
This is a generic repository layer which acts as an abstract layer between the data access and business layer of the application which makes a more loosely coupled approach to data access.
Service Layer
This layer is used communicate between UI and repository layers using Interface. We can call it as a business layer since it holds the business logic for an entity.
UI Layer
The UI layer can be Web API or Web application or any other UI application. This layer contains the implementation of DI (Dependency Inversion) principle to build a loosely coupled application.
Prerequisites
Software
Visual Studio 2019 16.3.4 & later
Onion Architecture in ASP.NET Core 3.0
To implement the onion architecture, we need to create four projects, three class library projects for Domain, Repository and service layer respectively and one ASP.NET Core API project for UI layer.
Project Structure
Step 1 - Create the project for Domain/Data Access Layer
Create a Class Library (.NET Core) Project using Visual Studio as shown in the below figure.

In my case I named it as OA_DataAccess.
OA_DataAccess class contains three entities, the BaseEntity which holds a common property and the other two entities, which are Product and Product Details.
- public class BaseEntity
- {
- public int ProductId { get; set; }
- }
Product.cs
- public class Product : BaseEntity
- {
- public string ProductName { get; set; }
- public virtual ProductDetails ProductDetails { get; set; }
- }
This class inherits from BaseEntity.
ProductMap.cs
- public class ProductMap
- {
- public ProductMap(EntityTypeBuilder<Product> entityBuilder)
- {
- entityBuilder.HasKey(p => p.ProductId);
- entityBuilder.HasOne(p => p.ProductDetails).WithOne(p => p.Product).HasForeignKey<ProductDetails>(x => x.ProductId);
- }
This class will hold the configuration of Product Entity which is used to create a database table.
Make sure Microsoft.EntityFrameworkCore package is added using NuGet package manager.
ProductDetail.cs
- public class ProductDetails : BaseEntity
- {
- public int StockAvailable { get; set; }
- public decimal Price { get; set; }
- public virtual Product Product{get;set;}
- }
This class Inherits from BaseEntity.
ProductDetailMap.cs
- public class ProductDetailMap
- {
- public ProductDetailMap(EntityTypeBuilder<ProductDetails> entityBuilder)
- {
- entityBuilder.HasKey(p => p.ProductId);
- entityBuilder.Property(p => p.StockAvailable).IsRequired();
- entityBuilder.Property(p => p.Price);
- }
- }
This class will hold the configuration of ProductDetail Entity which Is used to create a database table for the entity.
Step 2 - Create the project for Repository Layer
Create one more class library project, in my case I named it as OA_Repository. This layer contains a Datacontext class. The ADO.NET Entity Framework code first approach is used to create a data access context class.
Create a class file and name it as ApplicationContext.cs.
- public class ApplicationContext :DbContext
- {
- public ApplicationContext(DbContextOptions<ApplicationContext> options) : base(options)
- {
- }
- protected override void OnModelCreating(ModelBuilder modelBuilder)
- {
- base.OnModelCreating(modelBuilder);
- new ProductMap(modelBuilder.Entity<Product>());
- new ProductDetailMap(modelBuilder.Entity<ProductDetails>());
- }
- }
Make sure Microsoft.EntityFrameworkCore package is added using NuGet package manager.
Now create an Interface and name it as IRepository.cs.
- public interface IRepository<T> where T : BaseEntity
- {
- T Get(int id);
- IEnumerable<T> GetAll();
- }
- public class Repository<T> : IRepository<T> where T : BaseEntity
- {
- private readonly ApplicationContext context;
- private readonly DbSet<T> entities;
- public Repository(ApplicationContext context)
- {
- this.context = context;
- entities = context.Set<T>();
- }
- public IEnumerable<T> GetAll()
- {
- return entities.AsEnumerable();
- }
- public T Get(int id)
- {
- return entities.SingleOrDefault(p =>p.ProductId == id);
- }
- }
Step 3 - Create the project for Service Layer
This layer communicates between repository and presentation layer. Let’s create one more class project and name it as OA_Service. This project contains an interface and classes which implement the interface.
Create a new interface in OA_Service project and name it as IProductService.cs
- public interface IProductService
- {
- IEnumerable<OA_DataAccess.Product> GetProduct();
- OA_DataAccess.Product GetProduct(int id);
- }
Create a new interface in OA_Service project and name it as IProductDetailService.cs
- public interface IProductDetailsService
- {
- OA_DataAccess.ProductDetails GetProductDetail(int id);
- }
Create a new class and name it as ProductService.cs to Implement IProductService.cs.
- public class ProductService: IProductService
- {
- private IRepository<Product> productRepository;
- private IRepository<ProductDetails> productDetailRepository;
- public ProductService(IRepository<Product> productRepository, IRepository<ProductDetails> productDetailRepository)
- {
- this.productRepository = productRepository;
- this.productDetailRepository = productDetailRepository;
- }
- public IEnumerable<Product> GetProduct()
- {
- return productRepository.GetAll();
- }
- public Product GetProduct(int id)
- {
- return productRepository.Get(id);
- }
- }
You can notice we have parameterized constructor which is used to inject dependencies in the object.
GetProduct() method is used to get a ProductList from Product Repository.
GetProduct(int id) method is used to get a record from Product Repository based on ID.
Create a new class and name it as ProductDetailService.cs to Implement IProductDetailService.cs.
- public class ProductDetailsService: IProductDetailsService
- {
- private IRepository<ProductDetails> productDetailsRepository;
- public ProductDetailsService(IRepository<ProductDetails> productDetailsRepository)
- {
- this.productDetailsRepository = productDetailsRepository;
- }
- public ProductDetails GetProductDetail(int id)
- {
- return productDetailsRepository.Get(id);
- }
- }
This is an external layer, in our case it is a ASP.NET CORE WEB API project where we are going to create a REST service which is exposed to external clients.
Right Click on Solution Add->Project->New Project
Choose the template ASP.NET Core Web Application as shown in the below figure.



Akbar KhanPosted Aug 12, 2021, 9:11 AM
Could not load assembly 'OA_Repository'. Ensure it is referenced by the startup project 'OA_DataAccess'. ANY SOLUTION?? ANYONE???
Somendra Pratap SinghPosted Jul 29, 2021, 8:08 PM
Nice Article
Peten NemtomPosted Jan 6, 2021, 2:12 PM
Excuse me, this example is pretty good. But.... I don't think that Web API is an UI....
Omer WaheedPosted Jul 14, 2020, 1:38 PM
Looks good. I have one question that for DI we need to reference Repository project in API project. Is there any other way to do it? Without referencing?
Spyros AronisPosted Jun 4, 2020, 5:16 AM
I'm following your example but I can't see ProductName inside the foreach loop on the controller. ProductDetails details = new ProductDetails { Id = product.Id, ProductName = product.ProductName }; does not work but if I write details.Product.ProductName I can assign the value.
Jose Carlos MacorattiPosted May 29, 2020, 3:20 PM
Is it correct that the domain layer has a reference to ef core ?
Sundaram SubramanianPosted Feb 25, 2020, 4:56 AM
Still, we need Onion architecture?, Just a doubt. Please clarify me.