Clean Architecture ASP.NET Core Web API Proxy

Introduction

In the realm of modern web development, constructing a robust and scalable solution is paramount. This journey often involves harmonizing architectural principles and design patterns to enhance maintainability and flexibility. Our venture explores the creation of an ASP.NET Core Web API utilizing the Clean Architecture paradigm and harnessing the power of the Proxy Pattern. Focused on the dynamic domain of CarCompany management, our implementation establishes a structured and modular approach to CRUD (Create, Read, Update, Delete) operations.

Through the systematic use of interfaces, repositories, and dependency injection, we aim to not only facilitate seamless data access but also introduce a Proxy layer, offering a versatile gateway for implementing additional functionalities such as caching, logging, and validation. This amalgamation of industry-best practices forms the foundation for a resilient and extensible web API tailored for CarCompany data management.

 Proxy Pattern in an ASP.NET Core Web API with Clean Architecture. The example will focus on a CarCompany CRUD (Create, Read, Update, Delete) application.

1. Define the Domain Model

// Sardar Mudadssar Ali Khan
// Domain Layer
public class CarCompany
{
    public int Id { get; set; }
    public string Name { get; set; }
}

2. Create an Interface for the Repository

// Sardar Mudassar Ali Khan
// Data Access Layer
public interface ICarCompanyRepository
{
    Task<CarCompany> GetByIdAsync(int id);
    Task<List<CarCompany>> GetAllAsync();
    Task<int> CreateAsync(CarCompany carCompany);
    Task UpdateAsync(CarCompany carCompany);
    Task DeleteAsync(int id);
}

3. Implement the Repository

// Sardar Mudassar Ali Khan
// Data Access Layer
public class CarCompanyRepository: ICarCompanyRepository
{
    private readonly ApplicationDbContext _dbContext;

    public CarCompanyRepository(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
    }

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

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

    public async Task<int> CreateAsync(CarCompany carCompany)
    {
        _dbContext.CarCompanies.Add(carCompany);
        await _dbContext.SaveChangesAsync();
        return carCompany.Id;
    }

    public async Task UpdateAsync(CarCompany carCompany)
    {
        _dbContext.Entry(carCompany).State = EntityState.Modified;
        await _dbContext.SaveChangesAsync();
    }

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

4. Create a Proxy for the Repository

// Sardar Mudassar Ali Khan
// Application Layer
public class CarCompanyRepositoryProxy : ICarCompanyRepository
{
    private readonly ICarCompanyRepository _realRepository;

    public CarCompanyRepositoryProxy(ICarCompanyRepository realRepository)
    {
        _realRepository = realRepository;
    }

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

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

    public async Task<int> CreateAsync(CarCompany carCompany)
    {
        return await _realRepository.CreateAsync(carCompany);
    }

    public async Task UpdateAsync(CarCompany carCompany)
    {
        await _realRepository.UpdateAsync(carCompany);
    }

    public async Task DeleteAsync(int id)
    {
        await _realRepository.DeleteAsync(id);
    }
}

5. Dependency Injection in Startup.cs

// Sardar Mudassar Ali Khan
// In Startup.cs ConfigureServices method
services.AddScoped<ICarCompanyRepository, CarCompanyRepository>();
services.AddScoped<ICarCompanyRepository, CarCompanyRepositoryProxy>();

6. Use the Proxy in Your Controller

// Sardar Mudassar Ali Khan
// API Layer
[ApiController]
[Route("api/carcompanies")]
public class CarCompanyController: ControllerBase
{
    private readonly ICarCompanyRepository _repository;

    public CarCompanyController(ICarCompanyRepository repository)
    {
        _repository = repository;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetById(int id)
    {
        try
        {
            var carCompany = await _repository.GetByIdAsync(id);

            if (carCompany == null)
                return NotFound();

            return Ok(carCompany);
        }
        catch (Exception ex)
        {
            // Log the exception
            return StatusCode(500, "Internal Server Error");
        }
    }

    [HttpGet]
    public async Task<IActionResult> GetAll()
    {
        try
        {
            var carCompanies = await _repository.GetAllAsync();
            return Ok(carCompanies);
        }
        catch (Exception ex)
        {
            // Log the exception
            return StatusCode(500, "Internal Server Error");
        }
    }

    [HttpPost]
    public async Task<IActionResult> Create([FromBody] CarCompany carCompany)
    {
        try
        {
            if (carCompany == null)
                return BadRequest("Invalid data");

            var id = await _repository.CreateAsync(carCompany);
            return CreatedAtAction(nameof(GetById), new { id }, carCompany);
        }
        catch (Exception ex)
        {
            // Log the exception
            return StatusCode(500, "Internal Server Error");
        }
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> Update(int id, [FromBody] CarCompany carCompany)
    {
        try
        {
            if (carCompany == null || id != carCompany.Id)
                return BadRequest("Invalid data");

            await _repository.UpdateAsync(carCompany);
            return NoContent();
        }
        catch (Exception ex)
        {
            // Log the exception
            return StatusCode(500, "Internal Server Error");
        }
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> Delete(int id)
    {
        try
        {
            await _repository.DeleteAsync(id);
            return NoContent();
        }
        catch (Exception ex)
        {
            // Log the exception
            return StatusCode(500, "Internal Server Error");
        }
    }
}

The provided code outlines the implementation of the Proxy Pattern in an ASP.NET Core Web API with Clean Architecture for a CarCompany CRUD application. Here are the key takeaways.

  • Domain Model: The carCompany class in the Domain Layer represents the main entity.
  • Repository Interface: ICarCompanyRepository in the Data Access Layer defines the contract for data operations.
  • Repository Implementation: CarCompanyRepository implements the repository interface, providing basic CRUD operations.
  • Proxy for Repository: CarCompanyRepositoryProxy acts as a proxy for the real repository, allowing additional logic to be executed before or after invoking the real repository.
  • Dependency Injection: In Startup. cs, the dependencies are configured using the built-in dependency injection system of ASP.NET Core.
  • Controller: The CarCompanyController in the API Layer uses the proxy repository to handle HTTP requests, performing CRUD operations on the CarCompany entity.
  • Error Handling: Basic error handling is included in each action of the controller. Exceptions are caught and logged, and appropriate HTTP status codes are returned.

Conclusion

The Proxy Pattern is employed to introduce a layer of abstraction around the repository, enabling additional functionality (caching, logging, validation) without modifying the real repository's code.

Remember, this is a simplified example, and in a real-world scenario, you might need to consider more advanced features like validation, authentication, and authorization, as well as optimizing error handling and logging based on your application's needs.