Introduction To Clean Architecture And Implementation With ASP.NET Core

In this article, we will have an overview of Clean architecture and then we will try to implement this architecture with ASP.NET Core.

Let's get started,

Good architecture is a key to building scalable, modular, maintainable applications. Different architectures may vary in their details, but they all have the same objectives which are Separation of concern. And they all try to achieve this Separation by dividing the application into layers.

Clean Architecture is a software architecture intended to keep the code under control without all tidiness that spooks anyone from touching a code after the release. The main concept of Clean Architecture is the application code/logic which is very unlikely to change, has to be written without any direct dependencies. So it means that if I change my framework, database, or UI, the core of the system(Business Rules/ Domain) should not be changed. It means external dependencies are completely replaceable.

Overview

In Clean architecture, the Domain and Application layers remain at the center of the design which is known as the Core of the system.

The domain layer contains enterprise logic and the Application layer contains business logic. Enterprise logic can be shared across many systems but the business logic will typically only be used within the system. The core will be independent of data access and other infrastructure concerns. And we can achieve this using interfaces and abstraction within the Core and implement them by other layers outside of the Core.

In Clean Architecture all dependencies flow inwards and Core has no dependency on any other layer. And Infrastructure and Presentation layer depends on Core.  

Benefits of Clean Architecture

  • Independent of Database and Frameworks.
  • Independent of the presentation layer. Anytime we can change the UI without changing the rest of the system and business logic.
  • Highly testable, especially the core domain model and its business rules are extremely testable.

Implementing Clean Architecture with ASP.NET Core

As now we have a general overview of Clean Architecture, let's implement Clean Architecture with ASP.NET Core application.

Having said that, let's create a blank solution in Visual Studio.

Introduction To Clean Architecture And Implementation With ASP.NET Core

After that let's add a Web API project and three class library projects inside the solution. So the project will look something like this.

Introduction To Clean Architecture And Implementation With ASP.NET Core

Then, we will add project references. The key concept of Clean Architecture is that the inner layer should not know about the outer layer but the outer layer should know about the inner layer. So, Core will have no project reference. But, infrastructure will know about Core. And, API will know about all three layers.

Now let's go to the Core project and create two folders named "Entities" and "Repositories". And inside the "Entities" folder create a class named "Employee.cs".

public class Employee {
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Int64 EmployeeId {
        get;
        set;
    }
    public string FirstName {
        get;
        set;
    }
    public string LastName {
        get;
        set;
    }
    public DateTime DateOfBirth {
        get;
        set;
    }
    public string PhoneNumber {
        get;
        set;
    }
    public string Email {
        get;
        set;
    }
}

Now inside the "Repositories" folder, we will create a folder "Base". And inside the "Base" we will create an interface "IRepository.cs".

public interface IRepository < T > where T: class {
    Task < IReadOnlyList < T >> GetAllAsync();
    Task < T > GetByIdAsync(int id);
    Task < T > AddAsync(T entity);
    Task UpdateAsync(T entity);
    Task DeleteAsync(T entity);
}

This is a generic CRUD signature. We will create another interface inheriting IRepository which will have some custom method specific to an Entity inside the "Repositories" folder and will name it "IEmployeeRepository.cs".

public interface IEmployeeRepository: IRepository < Employee.Core.Entities.Employee > {
    //custom operations here
    Task < IEnumerable < Employee.Core.Entities.Employee >> GetEmployeeByLastName(string lastname);
}

So the Core project structure will look like this,

Now we will implement the infrastructure layer. In the infrastructure project, we will add two folders "Data" and "Repositories". We will create EmployeeContext class in the "Data" folder.

EmployeeContext.cs

public class EmployeeContext: DbContext {
    public EmployeeContext(DbContextOptions < EmployeeContext > options): base(options) {}
    public DbSet < Employee.Core.Entities.Employee > Employees {
        get;
        set;
    }
}

After that in the "Repositories" folder create a folder "Base" and inside it, we will create a Repository class that implements the interface for generic CRUD.

Repository.cs

public class Repository < T > : IRepository < T > where T: class {
    protected readonly EmployeeContext _employeeContext;
    public Repository(EmployeeContext employeeContext) {
        _employeeContext = employeeContext;
    }
    public async Task < T > AddAsync(T entity) {
        await _employeeContext.Set < T > ().AddAsync(entity);
        await _employeeContext.SaveChangesAsync();
        return entity;
    }
    public async Task DeleteAsync(T entity) {
        _employeeContext.Set < T > ().Remove(entity);
        await _employeeContext.SaveChangesAsync();
    }
    public async Task < IReadOnlyList < T >> GetAllAsync() {
        return await _employeeContext.Set < T > ().ToListAsync();
    }
    public async Task < T > GetByIdAsync(int id) {
        return await _employeeContext.Set < T > ().FindAsync(id);
    }
    public Task UpdateAsync(T entity) {
        throw new NotImplementedException();
    }
}

For a custom operation that is specific to an entity, we will create another class EmployeeRepository inside the Repositories folder. We will implement Repository and IEmployeeRepositoy which will have access to generic and custom code as well.

EmployeeRepository.cs

public class EmployeeRepository: Repository < Employee.Core.Entities.Employee > , IEmployeeRepository {
    public EmployeeRepository(EmployeeContext employeeContext): base(employeeContext) {}
    public async Task < IEnumerable < Core.Entities.Employee >> GetEmployeeByLastName(string lastname) {
        return await _employeeContext.Employees.Where(m => m.LastName == lastname).ToListAsync();
    }
}

 Along with these changes, our Infrastructure layer looks like this.

Now we will go to the "Application layer". Here in the application layer, we will segregate queries from the command so we will implement CQRS here. Hence we will create some folders inside the Application Layer project. And the folders are,

  • Commands
  • Handlers
  • Mappers
  • Queries
  • Responses

we will add our first command "CreateEmployeeCommand". So inside the Commands folder, we will add a new class "CreateEmployeeCommand.cs". This will issue a command to the infrastructure to create a new record. So this is going to implement IRequest and MediatR library

public class CreateEmployeeCommand: IRequest < EmployeeResponse > {
    public string FirstName {
        get;
        set;
    }
    public string LastName {
        get;
        set;
    }
    public DateTime DateOfBirth {
        get;
        set;
    }
    public string PhoneNumber {
        get;
        set;
    }
    public string Email {
        get;
        set;
    }
}

Here inside the IRequest<> we define the response type as EmplooyeeResponse. So we will create an EmployeeResponse.cs inside the Responses Folder.

public class EmployeeResponse {
    public int EmployeeId {
        get;
        set;
    }
    public string FirstName {
        get;
        set;
    }
    public string LastName {
        get;
        set;
    }
    public DateTime DateOfBirth {
        get;
        set;
    }
    public string PhoneNumber {
        get;
        set;
    }
    public string Email {
        get;
        set;
    }
}

Next, we will create a handler for CreateEmployeeCommand. We will be creating this in the Handlers folder. CreateEmployeeHandler will look like this.

public class CreateEmployeeHandler: IRequestHandler < CreateEmployeeCommand, EmployeeResponse > {
    private readonly IEmployeeRepository _employeeRepo;
    public CreateEmployeeHandler(IEmployeeRepository employeeRepository) {
        _employeeRepo = employeeRepository;
    }
    public async Task < EmployeeResponse > Handle(CreateEmployeeCommand request, CancellationToken cancellationToken) {
        var employeeEntitiy = EmployeeMapper.Mapper.Map < Employee.Core.Entities.Employee > (request);
        if (employeeEntitiy is null) {
            throw new ApplicationException("Issue with mapper");
        }
        var newEmployee = await _employeeRepo.AddAsync(employeeEntitiy);
        var employeeResponse = EmployeeMapper.Mapper.Map < EmployeeResponse > (newEmployee);
        return employeeResponse;
    }
}

Here we supplied IRequestHandler, which takes CreateEmployeeCommand and MovieResponse.

Line 11: We mapped Employee Entity with the request object.

Line 16: Added the employeeEntity to the database.

Line 17: Mapping back to MovieResponse to fetch EmployeeId from the object and after that returning the response.

Here all this Mapping stuffs is implemented with AtuoMapper. Hence, we will create EmployeeMapper.cs class inside the Mapper folder.

public class EmployeeMapper {
    private static readonly Lazy < IMapper > Lazy = new Lazy < IMapper > (() => {
        var config = new MapperConfiguration(cfg => {
            cfg.ShouldMapProperty = p => p.GetMethod.IsPublic || p.GetMethod.IsAssembly;
            cfg.AddProfile < EmployeeMappingProfile > ();
        });
        var mapper = config.CreateMapper();
        return mapper;
    });
    public static IMapper Mapper => Lazy.Value;
}

Now we need to create a Mapping profile named "EmployeeMappingProfile" inside the "Mapper" folder as we provided in line 8.

public class EmployeeMappingProfile: Profile {
    public EmployeeMappingProfile() {
        CreateMap < Employee.Core.Entities.Employee, EmployeeResponse > ().ReverseMap();
        CreateMap < Employee.Core.Entities.Employee, CreateEmployeeCommand > ().ReverseMap();
    }
}

So like the command, we can also create some queries in our project. But now I am skipping this. But we can find Query creating example in the attached source code in this article.

So after all these changes, our Application Layer looks like this.

Now we will dive inside API implementation. In the API Project, we will add dependencies like Automapper, swagger, MediatR in the startup.cs file. 

public void ConfigureServices(IServiceCollection services) {
    services.AddControllers();
    services.AddDbContext < EmployeeContext > (m => m.UseSqlServer(Configuration.GetConnectionString("EmployeeDB")), ServiceLifetime.Singleton);
    services.AddSwaggerGen(c => {
        c.SwaggerDoc("v1", new OpenApiInfo {
            Title = "Employee.API", Version = "v1"
        });
    });
    services.AddAutoMapper(typeof(Startup));
    services.AddMediatR(typeof(CreateEmployeeHandler).GetTypeInfo().Assembly);
    services.AddScoped(typeof(IRepository < > ), typeof(Repository < > ));
    services.AddTransient < IEmployeeRepository, EmployeeRepository > ();
}

In the appsettings.json file, we will create connection strings.

{
  "ConnectionStrings": {
    "EmployeeDB": "Data Source=.;Initial Catalog=EmployeeDB;Integrated Security=True"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Later we will create a new controller EmployeeController as shown below,

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

        [HttpGet]
        [ProducesResponseType(StatusCodes.Status200OK)]
        public async Task<List<Employee.Core.Entities.Employee>> Get()
        {
            return await _mediator.Send(new GetAllEmployeeQuery());
        }
        [HttpPost]
        [ProducesResponseType(StatusCodes.Status200OK)]
        public async Task<ActionResult<EmployeeResponse>> CreateEmployee([FromBody] CreateEmployeeCommand command)
        {
            var result = await _mediator.Send(command);
            return Ok(result);
        }
    }

After all these changes our API project will look like this,

Now if we run our application it will redirect us to localhost:port/swagger/index.html and from there we can test our endpoint.

With this, we can conclude our Clean Architecture tour with ASP.NET Core.

Happy coding!!!