Applications architectures can be divided into several categories, such as All-in-one architectures, layered architectures, clean architectures, etc. This article discusses how to create clean architecture application in Asp.net Core
What makes clean architecture the best?
Benefits of clean architecture,
- Heavily influenced by design principles.
- The distinction of concerns.
- It is loosely coupled.
- Ensure that the application code is testable and maintainable.
- A completely independent application that does not depend on any external influences such as the UI or database.
Circular Diagram for clean application architecture:
Layers are represented by different circles.
The middle of the circle should be the center of a clean architecture, where application cores are located
- A high-level abstraction
- Entities and interfaces
- A central component of the application is the business logic
- Does not depend on external factors
Infrastructural circle – the outer circle
- It depends on the core
- The interface is implemented from the core
The core is the foundation of the UI
Inverted Dependencies point inward, which means it is inverted.
Two important principles must be followed when creating this type of architecture.
- Inversion of dependencies
- A mediator is a system that facilitates messaging between objects to achieve a high level of loose coupling.
What are the core components of the core project?
- Groups of entities
- Using interfaces
- Core
- Infrastructure
- The services
- The exceptions
Code does not depend on infrastructure
The infrastructure project – what does it entail?
- Data access using Entity Framework Core
- Logging Activities and Exceptions
- Identity (Microsoft)r any)
- API Clients
- Access to the file system
The figure below shows a complete project structure
It consists of the following Application layers:
Getting started with the Domain project is the first step.
Under the Entities folder, add a Hotel class.
public class Hotel
{
public Guid HotelId { get; set; }
public string Name { get; set; }
}
Under Contract -> Persistence, create the IAsyncRepository class
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);
}
For the application project, install the following packages
- Using AutoMapper
- The MediatR
Under the folder Feature->Hotels->Queries->GetHotelList, add three classes
public class HotelsListVM
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class GetHotelsListQuery : IRequest<List<HotelsListVM>>
{
}
In the IRequest interface, a request and a response are represented as one object.
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);
}
}
In this handler, the repository will be contacted to retrieve the hotel list from the database. The request handler defined by MediatR is called IRequestHandeler.
Automap domain and view model classes by creating MappingProfile.cs under Profiles Folder.
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Hotel, HotelsListVM>();
}
}
Registration of this application service with service collections can be accomplished using the ApplicationServiceRegistration class.
public static class ApplicationServiceRegistration
{
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
{
services.AddAutoMapper(Assembly.GetExecutingAssembly());
services.AddMediatR(Assembly.GetExecutingAssembly());
return services;
}
}
Here are the packages that I added for the core layer of the application.
Infrastructure Layer :
We have to create DBContext class to build the infrastructure layer.
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" });
}
}
The IAsyncRepository needs to be implemented
From the Infrastructure folder, create a class called BasicRepository.
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();
}
}
The persistence service should be registered at the application startup using the PersistenceServiceRegistration class.
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;
}
}
I have added the below-mentioned packages for the Infrastructure layer.
API Layer:
Make an ASP.NET Core Web API project in the API folder.
You need to 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);
}
}
Add PersistanceServiceRegistration and ApplicationServiceRegistration to Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationServices();
services.AddPersistenceServices(Configuration);
services.AddControllers();
}
Make sure appsettings.json contains your database connection string.
"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 I have added for API Project.
To apply a code-first approach to data persistence with SQL Server, switch to the persistence project in the package manager console, and run the command below.
Add-Migration InitalCommit
Update-database
Make sure the database is correct
There has been an addition to the record
Let’s run the application, check out the API
POSTMAN can be used to test the API
Closing Thoughts:
ASP.NET Core applications are implemented using clean architecture. We have seen what clean architecture is and the benefits of using it. The next article in this series will show how we can create a unit test method for this application with a clean architecture.