Simplified ASP.NET Core Web API with Clean Architecture and Chain of Responsibility

Introduction

In the ever-evolving landscape of web development, creating a robust and maintainable API is crucial. ASP.NET Core, with its versatility and performance, provides an excellent foundation for building web APIs. Clean Architecture, a software design philosophy, promotes separation of concerns and maintainability. In this article, we'll explore the combination of ASP.NET Core, Clean Architecture, and the Chain of Responsibility pattern to implement a straightforward yet powerful solution for CarCompany CRUD operations. Follow along as we break down the steps to construct a simplified and efficient ASP.NET Core Web API.

Implementing a complete ASP.NET Core Web API with Clean Architecture and applying the Chain of Responsibility Pattern for a CarCompany CRUD operation involves several steps. Below, I'll guide you through the process, providing code snippets for each layer.

1. Define the Model

// Sardar Mudassar Ali Khan
// Core/Entities/CarCompany.cs
namespace Core.Entities
{
    public class CarCompany
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

2. Create Data Access Layer

// Sardar Mudassar Ali Khan
// Infrastructure/Data/CarCompanyRepository.cs
using Core.Entities;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Infrastructure.Data
{
    public interface ICarCompanyRepository
    {
        Task<CarCompany> GetByIdAsync(int id);
        Task<List<CarCompany>> GetAllAsync();
        Task AddAsync(CarCompany carCompany);
        Task UpdateAsync(CarCompany carCompany);
        Task DeleteAsync(int id);
    }

    public class CarCompanyRepository: ICarCompanyRepository
    {
        private readonly ApplicationDbContext _context;

        public CarCompanyRepository(ApplicationDbContext context)
        {
            _context = context;
        }

        public async Task<CarCompany> GetByIdAsync(int id)
        {
            return await _context.CarCompanies.FindAsync(id);
        }

        public async Task<List<CarCompany>> GetAllAsync()
        {
            return await _context.CarCompanies.ToListAsync();
        }

        public async Task AddAsync(CarCompany carCompany)
        {
            _context.CarCompanies.Add(carCompany);
            await _context.SaveChangesAsync();
        }

        public async Task UpdateAsync(CarCompany carCompany)
        {
            _context.CarCompanies.Update(carCompany);
            await _context.SaveChangesAsync();
        }

        public async Task DeleteAsync(int id)
        {
            var carCompany = await GetByIdAsync(id);
            if (carCompany != null)
            {
                _context.CarCompanies.Remove(carCompany);
                await _context.SaveChangesAsync();
            }
        }
    }
}

3. Implement a Chain of Responsibility Pattern

// Sardar Mudassar Ali Khan
using System.Threading.Tasks;
using Core.Entities;

namespace Application
{
    public interface ICarCompanyHandler
    {
        Task HandleAsync(CarCompany carCompany);
    }

    public class CreateCarCompanyHandler: ICarCompanyHandler
    {
        private readonly ICarCompanyRepository _repository;
        private readonly ICarCompanyHandler _nextHandler;

        public CreateCarCompanyHandler(ICarCompanyRepository repository, ICarCompanyHandler nextHandler = null)
        {
            _repository = repository;
            _nextHandler = nextHandler;
        }

        public async Task HandleAsync(CarCompany carCompany)
        {
            await _repository.AddAsync(carCompany);

            if (_nextHandler != null)
                await _nextHandler.HandleAsync(carCompany);
        }
    }

    public class UpdateCarCompanyHandler: ICarCompanyHandler
    {
        private readonly ICarCompanyRepository _repository;
        private readonly ICarCompanyHandler _nextHandler;

        public UpdateCarCompanyHandler(ICarCompanyRepository repository, ICarCompanyHandler nextHandler = null)
        {
            _repository = repository;
            _nextHandler = nextHandler;
        }

        public async Task HandleAsync(CarCompany carCompany)
        {
            await _repository.UpdateAsync(carCompany);

            if (_nextHandler != null)
                await _nextHandler.HandleAsync(carCompany);
        }
    }

    public class DeleteCarCompanyHandler: ICarCompanyHandler
    {
        private readonly ICarCompanyRepository _repository;
        private readonly ICarCompanyHandler _nextHandler;

        public DeleteCarCompanyHandler(ICarCompanyRepository repository, ICarCompanyHandler nextHandler = null)
        {
            _repository = repository;
            _nextHandler = nextHandler;
        }

        public async Task HandleAsync(CarCompany carCompany)
        {
            await _repository.DeleteAsync(carCompany.Id);

            if (_nextHandler != null)
                await _nextHandler.HandleAsync(carCompany);
        }
    }
}

4. Implement Web API Controller

// Sardar Mudassar Ali Khan
using System.Threading.Tasks;
using Application;
using Core.Entities;
using Microsoft.AspNetCore.Mvc;

namespace Web.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class CarCompanyController : ControllerBase
    {
        private readonly ICarCompanyHandler _createHandler;
        private readonly ICarCompanyHandler _updateHandler;
        private readonly ICarCompanyHandler _deleteHandler;

        public CarCompanyController(ICarCompanyHandler createHandler, ICarCompanyHandler updateHandler, ICarCompanyHandler deleteHandler)
        {
            _createHandler = createHandler;
            _updateHandler = updateHandler;
            _deleteHandler = deleteHandler;
        }

        [HttpPost]
        public async Task<IActionResult> Create([FromBody] CarCompany carCompany)
        {
            await _createHandler.HandleAsync(carCompany);
            return Ok();
        }

        [HttpPut("{id}")]
        public async Task<IActionResult> Update(int id, [FromBody] CarCompany carCompany)
        {
            carCompany.Id = id;
            await _updateHandler.HandleAsync(carCompany);
            return Ok();
        }

        [HttpDelete("{id}")]
        public async Task<IActionResult> Delete(int id)
        {
            await _deleteHandler.HandleAsync(new CarCompany { Id = id });
            return Ok();
        }
    }
}

5. Configure Dependency Injection

// Sardar Mudassar Ali Khan
// Web/Startup.cs
using Application;
using Infrastructure.Data;

public void ConfigureServices(IServiceCollection services)
{

    services.AddScoped<ICarCompanyRepository, CarCompanyRepository>();

    services.AddScoped<ICarCompanyHandler>(sp =>
        new CreateCarCompanyHandler(sp.GetRequiredService<ICarCompanyRepository>())
    );
    services.AddControllers();
}

This example provides a basic structure for a complete ASP.NET Core Web API with Clean Architecture, incorporating the Chain of Responsibility Pattern for the CarCompany CRUD operations. Make sure to customize it based on your specific requirements and extend it for other CRUD actions.

Conclusion

This article delved into the construction of a robust ASP.NET Core Web API using the principles of Clean Architecture and integrating the Chain of Responsibility pattern for seamless CarCompany CRUD operations. The Clean Architecture approach fosters maintainability and separation of concerns, while the Chain of Responsibility pattern facilitates a modular and extensible design for handling various aspects of the application's logic.

By implementing distinct layers, from the core entities to the application services and web controllers, we established a structured and scalable architecture. The provided code snippets offer a practical guide for creating, updating, and deleting CarCompany entities, showcasing how each handler in the chain contributes to the overall functionality. Adopting such a design not only enhances code readability and maintainability but also lays a solid foundation for future expansion and modification of the API. Through this exploration, developers are empowered to build well-organized and adaptable web APIs, aligning with best practices in modern software development.