Clean Architecture for .NET Core Application

Introduction:

 There are different types of application architecture like All-in-one architecture, layered architecture, clean architecture, etc. In this article I will explain you how to implement the clean architecture for .NET Core application.

Why clean architecture is one of the best from the list?

Clean architecture 🎯 benefits,

  • Heavily based on the design principles.
  • Separation of concerns.
  • Loose coupling.
  • Create maintainable and testable application code.
  • It’s completely Independent of external influences like UI and database.

Circular Diagram:

 Different circles are used to represent different layers.

In clean architecture we should start from middle of the circle, in middle we have Application Core

  1. Abstraction (high-level)
  2. Interface and entities
  3. Business logic at the center of the application
  4. Has no dependency on external influences

Outer circle – infrastructure 🏫

  • Depend on core and
  • Implements interface from core

The UI depends on core.

The Dependencies are inverted which means its pointing inward.

To create this type of architecture we need to follow two important principles.

  1. Dependency inversion
  2. Mediator – It used to achieve high level of loose coupling by messaging between different objects.

What does the core project build up with?

  • Entites
  • Interfaces
    • Core
    • Infrastructure
  • Services
  • Exceptions

No dependency to any infrastructure related code

What does the infrastructure project build up with?

  • Data access (EF Core)
  • Logging
  • Identity
  • API Clients
  • File Access

Complete project structure as shown in the below figure

Application Core Layer🔗 :

Let’s start with Domain project

Add Hotel class under Entities folder

public class Hotel
    {
        public Guid HotelId { get; set; }
        public string Name { get; set; }
    }

Create IAsyncRespository class under Contract -> Persistence from application project

public interface IAsyncRespository<T> where T : class
    {
        Task<T> GetByIdAsync(int id);
        Task<T> GetByGuIdAsync(Guid guid);
        Task<IReadOnlyList<T>> ListAllAsync();
        Task<T> AddAsync(T entity);
        Task UpdateAsync(T entity);
        Task DeleteAsync(T entity);
    }

Install below packages for application project

  1. AutoMapper
  2. MediatR

Add three classes under Feature->Hotels->Queries->GetHotelList Folder

   public class HotelsListVM
    {
        public Guid Id { get; set; }
        public string Name { get; set; }

    }
   public class GetHotelsListQuery : IRequest<List<HotelsListVM>>
    {
    }

IRequest interface is used to represent a request with a response

    public class GetHotelsListQueryHandler : IRequestHandler<GetHotelsListQuery, List<HotelsListVM>>
    {
        private readonly IAsyncRespository<Hotel> _hotelRepository;
        private readonly IMapper _mapper;
        public GetHotelsListQueryHandler(IMapper mapper, IAsyncRespository<Hotel> hotelRespository)
        {
            _mapper = mapper;
            _hotelRepository = hotelRespository;
        }
        public async Task<List<HotelsListVM>> Handle(GetHotelsListQuery request, CancellationToken cancellationToken)
        {
            var allHotels = (await _hotelRepository.ListAllAsync()).OrderBy(x => x.Name);
            return _mapper.Map<List<HotelsListVM>>(allHotels);
        }
    } 

This handler will call the repository to fetch the hotel list from database. IRequestHandeler from MediatR defines a handler for a request.

Create MappingProfile.cs class under Profiles Folder for automapping the domain and view model classes

  public class MappingProfile : Profile
    {
        public MappingProfile()
        {
            CreateMap<Hotel, HotelsListVM>();
        }
    }

Create ApplicationServiceRegistration class to register this application service with service collections in Application startup.

   public static class ApplicationServiceRegistration
    {
        public static IServiceCollection AddApplicationServices(this IServiceCollection services)
        {
            services.AddAutoMapper(Assembly.GetExecutingAssembly());
            services.AddMediatR(Assembly.GetExecutingAssembly());
            return services;
        }
    }

Packages added for Application core layer.

Application core packages

Infrastructure Layer🏫 :

Create a DBContext class from Infrastructure Project

public class HoteBookingDbContext : DbContext
    {
        public HoteBookingDbContext(DbContextOptions<HoteBookingDbContext> options) : base(options)
        {

        }
        public DbSet<Hotel> Hotels { get; set; }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.ApplyConfigurationsFromAssembly(typeof(HoteBookingDbContext).Assembly);

            modelBuilder.Entity<Hotel>().HasData(new Hotel { HotelId = Guid.NewGuid(), Name = "Shaaniya" });
        }
    }

 Implement the IAsyncRepository

Create BasicRepository class under Repository folder from Infrastructure folder.

public class BaseRepository<T> : IAsyncRespository<T> where T : class
    {
        protected readonly HoteBookingDbContext _dbContext;
        public BaseRepository(HoteBookingDbContext dbContext)
        {
            _dbContext = dbContext;
        }
        public virtual async Task<T> GetByIdAsync(int id)
        {
            return await _dbContext.Set<T>().FindAsync(id);
        }
        public virtual async Task<T> GetByGuIdAsync(Guid guid)
        {
            return await _dbContext.Set<T>().FindAsync(guid);
        }

        public async Task<IReadOnlyList<T>> ListAllAsync()
        {
            return await _dbContext.Set<T>().ToListAsync();
        }
        public async Task<T> AddAsync(T entity)
        {
            try
            {
                await _dbContext.Set<T>().AddAsync(entity);
                await _dbContext.SaveChangesAsync();
            }
            catch (Exception ex)
            {

            }
            return entity;
        }
        public async Task UpdateAsync(T entity)
        {
            try
            {
                _dbContext.Entry(entity).State = EntityState.Modified;
                await _dbContext.SaveChangesAsync();
            }
            catch (Exception ex)
            {

            }
        }
        public async Task DeleteAsync(T entity)
        {
            _dbContext.Set<T>().Remove(entity);
            await _dbContext.SaveChangesAsync();
        }
    }

 Create PersistenceServiceRegistration class to register this persistence service in Application startup.

public static class PersistenceServiceRegistration
    {
        public static IServiceCollection AddPersistenceServices(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddDbContext<HoteBookingDbContext>(options =>
                options.UseSqlServer(configuration.GetConnectionString("HotelConnectionString")));
            services.AddScoped(typeof(IAsyncRespository<>), typeof(BaseRepository<>));
            return services;
        }
    }

Packages add for Infrastructure layer.

Infrastructure packages

API Layer 🕸:

Create a ASP.NET Core Web API project under API folder.

 Add HotelsController.cs

   [Route("api/[controller]")]
    [ApiController]
    public class HotelsController : ControllerBase
    {
        private readonly IMediator _mediator;
        public HotelsController(IMediator mediator)
        {
            _mediator = mediator;
        }

        [HttpGet]
        [ProducesResponseType(StatusCodes.Status200OK)]

        public async Task<ActionResult<List<HotelsListVM>>> GetAllHotels()
        {
            var dtos = await _mediator.Send(new GetHotelsListQuery());
            return Ok(dtos);
        }
    }

In Startup.cs, add ApplicationServiceRegistration and PersistanceServiceRegistration in service collection

public void ConfigureServices(IServiceCollection services)
        {
            services.AddApplicationServices();
            services.AddPersistenceServices(Configuration);
            services.AddControllers();
        } 

Add your database connection string in appsettings.json

"ConnectionStrings": {
    "HotelConnectionString": "Server=your server name ;Database= your database name;Integrated Security=False;Persist Security Info=False;User ID= your user id;Password=your password"
  },

Packages added for API Project.

API Layer Packages

Switch to persistence project in package manager console, user below command to apply code first approch for data persistance with SQL Server database.

Add-Migration InitalCommit
Update-database 
Package Manager console

Check the database

New Record has been inserted

Let’s check the API, run the application

Test the API using POSTMAN

Summary:

  We have seen what is clean architecture, what are the benefits of using the clean architecture and its implementation with ASP.NET Core application. We will see how to write the unit test method for this application with clean architecture in my next article.   

Source code – GitHub

Happy Coding 👍

gowthamk91

2 thoughts on “Clean Architecture for .NET Core Application

Leave a Reply

%d bloggers like this: