NET Core Avoiding large Controllers — using CQRS with MediatR.
Having lightweight API controllers or regular view controllers is something good to strive for. Not having many dependencies and logic in the controller betters the application for long-term prospects and eases the process of adding features.
Today, I will show an example of an API controller that uses MediatR to communicate with our backend services.
LET’S BEGIN!!
Disclaimer:
In this article, I’m assuming you have prior knowledge in EF Core, WebApi controllers, and of course C#.
-What is CQRS
It’s the concept of separating your database commands from your database queries meaning; writing to a database are commands and reading from a database are queries.
This improves the application’s flexibility and security. This pattern can fasten the process of adding features that depend on the database.
-Domain Models
I have my domain models implemented in a separate NET standard library
- TestApplicant Entity
- TestInfo
- Shared Class
-Core Application
I have added another project for the application logic. this is where our CQRS implementation will happen. everything is implemented as an interface here (Repositories) and we will inject them with the concrete implementation later on.
- Adding Package references
- Adding Repository Interface
- Adding our Events
The events are the Commands and Queries we are going to send to the database using MediatR Requests and they will be picked up by RequestHandlers.
1-CreateApplicantCommand
This command will be sent using MediatR and the handler will be waiting to execute it
2-CreateApplicantValidator
using the FluentValidation framework, I have created a validator class to validate my CreateApplicantCommand before adding the entry to the database
3-CreateApplicantDto
Adding this Dto object, so it can be sent over the wire as part of the response
4-CreateApplicantResponse
5-CreateApplicantCommandHandler
As you can see, the request handler is implementing IRequestHandler and expects CreateApplicantCommand as the caller. CreateApplicantResposne is the response the handler will provide when the Handle method is Invoked.
As you can see, I’m validating the entry and if it’s not valid I throw a custom exception of type ModelValidationException. if all goes well, we convert the entity to a CreateApplicantDto and add it to our CreateApplicantResposne instance and retuning that as value.
I'm showing only one example of using MediatR and CQRS pattern here, if you want to see all the operations you can check the solution at GitHub.
6-Specyfing the Mapper profile
I'm using AutoMapper to map between my models and the various DTOs I have. I also have a class that inherits from Profile, and in the class CTOR, I specify the mappings between the objects.
7- registering our project dependencies to a service.
I'm registering AutoMapper and MediatR as we will need to register those services and call the static method in our API project.
-Application’s Infrastructure
I have added another NET standard project, in this project all of our concrete implementation of the repositories will be implemented here and all the database logic as well.
I'm separating the application into projects to follow better practices and less code coupling.
- Package References
- BaseRepository
I have implemented IAsyncReposiotry methods.
- Entites Configurations
1-TestAppicant Configuration
2- TestInfo Configuration
I have separated my constraints from my Entity class, as I didn’t want to clutter my classes with data annotations.
- Database Context
I have overridden the OnModelCreating, in here I'm applying the configurations of my entities and specified a couple of test data to be initialized when I create the migration.
I have also overridden the OnSaveChnagesAsync to update my entity's record of tracking automatically.
- registering our project dependencies to a service.
I have added my DbContext and specified my dependencies, as we will need to register those services and call the static method in our API project.
Application’s API
- Package references
- Registering our other projects services in Startup.cs class.
After I added the project references to my API project, I call the service registration methods to inject our dependencies and register our services.
- Appsettings.Json
- TestApplicant Controller
As you can see we have only one dependency that gets injected “MediatR”, and the controller code is just sending the command or the queries to the corresponding Handler.
See how small this controller is! we don't have any database logic nor business logic or any other injected objects.
We have a complete separation between our models, business logic, data access methods. everything is split in its corresponding domain.
And now, if you want to add a UI, it will also be completely agnostic of the backend logic and that’s what we want to achieve, lightweight controllers, decoupled code, and as much fewer dependencies as possible.
Conclusion
We can see how smaller API controllers can make the development process easier. controllers, ideally shouldn’t carry any heavy logic or do anything that it’s not under its responsibility (Single Responsibility Principle). simply we need those controllers to forward our requests and that was done with the help of MediatR.