In computer programming we generally use a design pattern which will help us write codes to solve problems efficiently. There are many design patterns and one of the most commonly used once are CQRS and Mediator Patterns. The CQRS and Mediator patterns will work together to create a loosely coupled object that will not call one another explicitly.
Download the complete source code of this tutorial from my GitHub Repository.
Page Contents
In this tutorial we will be creatin an ASP.NET Core CRUD operations using both CQRS and Mediator patterns. Before we dig into the implementation part, let us understand these patterns one by one.
CQRS stands for Command Query Responsibility Segregation, this pattern separates read and write operations for a data source. Each method should either be a Command or a Query but not both. In CQRS “Commands” is known for database saves and “Queries” is known for reading from the database.
The idea behind CQRS is to have different models for different operations. So for a CRUD operation we will have 4 models which are:
See the below image where I have illustrated the CQRS architecture.
The Mediator pattern defines an object called Mediator, this Mediator encapsulates how a set of objects interact with one another. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.
The Mediator pattern ensures that components don’t call each other explicitly, but instead do so through calls to a mediator.
Check the below image which illustrates the Mediator pattern:
Some important pros of using CQRS and Mediator patterns are not limited to:
We do not have complex models but instead there are separate models per data operation. This provides highly optimized designs. We can also scale our apps easily without having to worry about dealing with the complexities.
The database operations become faster by using CQRS and Mediator. It also provides better parallel operations since there are “Commands” and “Queries”.
Mediator pattern provided Loosely coupled architecture to our apps, it is lean, with a single responsibility, without many dependencies, allowing teams to work independently, deploy & scale independently, and increases business responsiveness.
MediatR is a .NET library which helps in building apps based on CQRS and Mediator patterns. All communication between the user interface and the data store happens via MediatR. The MediatR library is available in NuGet and we will shortly install it to our app.
First thing you have to do is create a new ASP.NET Core Web APP (Model-View-Controller) in Visual Studio.
Name the app as CQRSMediator. Then select the latest version of .NET which is 8.0 currently.
Next, select Tools ➤ NuGet Package Manager ➤ Manage NuGet Packages for Solution in your Visual Studio, and install the MediatR package.
Now open up the Program.cs and configured MediatR by adding the following code line:
builder.Services.AddMediatR(a => a.RegisterServicesFromAssembly(typeof(Program).Assembly));
Now MediatR is configured and ready to go.
We will now install and configure Entity Framework Core in our app. Entity Framework Core will work with CQRS and Mediator to perform database operations. So first install following packages to your app.
Next, go to appsettings.json file and add the connection string for the database. I am using LocalDb connection.
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ProductDB;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
Next, add the Product.cs class inside the Models folder. This class serves as the Model. I will be doing CRUD operations on the Product records. The code of the Product.cs class is given below:
namespace CQRSMediator.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
}
Next, we need to add DbContext file for Entity Framework Core. So, add a new class called ProductContext.cs inside the same “Models” folder with the following code.
using Microsoft.EntityFrameworkCore;
namespace CQRSMediator.Models
{
public class ProductContext : DbContext
{
public ProductContext(DbContextOptions<ProductContext> options) : base(options)
{
}
public DbSet<Product> Product { get; set; }
}
}
Next, we need to add Entity Framework Core to the IServiceCollection. This is done on the Program.cs.
builder.Services.AddDbContext<ProductContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
The above code says to get the Connection String that is named as DefaultConnection. Recall we have defined this in the appsettings.json file.
The final thing is to perform Entity Framework Core migrations. This is done by running the following 2 commands that are given below, run them on the Tools ➤ NuGet Package Manager ➤ Packages Manager Console window of Visual studio.
add-migration Migration1
Update-Database
Once the migration completes open the View ➤ SQL Server Object Explorer window. Then click on (localdb)\MSSQLLocalDB and select “Refresh” you will now see ProductDB database is created.
This completes the configuration of Entity Framework Core. We are now ready to start building our CRUD operations for the Products in the Controller.
I will now create CQRS Command that will Create a New Product. So, create CQRS ➤ Commands folder on the root folder of the app. Then add a new class called CreateProductCommand.cs to the Commands folder. See the below image where I have shown this thing.
We already discussed the Commands and Queries of CQRS. To separate these two, we need to create 2 separate folders, the first folder “Commands” is already created inside the CQRS folder. The Commands folder will contain classes that will deal with database saves. We already added one such class CreateProductCommand.cs.
Now also add “Queries” folder inside the “CQRS” folder. This folder will contain classes that will be dealing with database reads. We will be adding these classes during the course of the tutorial.
Now add the following code to the CreateProductCommand.cs class.
using CQRSMediator.Models;
using MediatR;
namespace CQRSMediator.CQRS.Commands
{
public class CreateProductCommand : IRequest<int>
{
public string Name { get; set; }
public decimal Price { get; set; }
public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, int>
{
private ProductContext context;
public CreateProductCommandHandler(ProductContext context)
{
this.context = context;
}
public async Task<int> Handle(CreateProductCommand command, CancellationToken cancellationToken)
{
var product = new Product();
product.Name = command.Name;
product.Price = command.Price;
context.Product.Add(product);
await context.SaveChangesAsync();
return product.Id;
}
}
}
}
Let’s explain things:
Now create the controller from where the http request to create the product is made. So, inside the “Controllers” folder create a new controller by the name of ProductController.cs. To this controller’s constructor make a dependency for IMediator type.
Then add a new Http Post Type method called “Create” which will be making a call to the CQRS command class CreateProductCommand.cs which we just made.
using CQRSMediator.CQRS.Commands;
using MediatR;
using Microsoft.AspNetCore.Mvc;
namespace CQRSMediator.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ProductController : ControllerBase
{
private IMediator mediator;
public ProductController(IMediator mediator)
{
this.mediator = mediator;
}
[HttpPost]
public async Task<IActionResult> Create(CreateProductCommand command)
{
return Ok(await mediator.Send(command));
}
}
}
Note that the Create method has the parameter of CreateProductCommand type and it is the responsibility of Mediator patter (MediatR library) to make a call to this class when mediator.Send() method is executed.
Let us now test if the CQRS is working properly. So, add a breakpoint on the Handle method of the CreateProductCommand.cs class. Now run the app on visual studio. Now open Postman to make HTTP POST type request to the app. You can also use Swagger instead of Postman.
In the Postman select:
{
"name": "Shirts",
"price": 49
}
Click the send Button to make the request. See the below image where I have shown this.
Your breakpoint will hit, check the value of the “command” parameter, you will see the values which you filled in the postman for the product has received there. Check the below breakpoint image:
The method returns the id of the product which is created on the database. This id will be 1 and as you keep on creating new products the id will become 2, 3, …. and so on.
Postman will show this id inside the response Body field.
Congrats, we just created our first Command with CQRS and Mediator patterns. Next, we are going to create the remaining operations – Read, Update & Delete.
There will be 2 Read Operations – One for reading all the products at the same time while other for reading products by id.
Create the Queries folder inside the “CQRS” folder. The classes that will be reading the database will be kept inside this Queries folder.
So, create a new class called GetAllProductQuery.cs to the Queries folder. The full code of this class is given below:
using CQRSMediator.Models;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CQRSMediator.CQRS.Queries
{
public class GetAllProductQuery : IRequest<IEnumerable<Product>>
{
public class GetAllProductQueryHandler : IRequestHandler<GetAllProductQuery, IEnumerable<Product>>
{
private ProductContext context;
public GetAllProductQueryHandler(ProductContext context)
{
this.context = context;
}
public async Task<IEnumerable<Product>> Handle(GetAllProductQuery query, CancellationToken cancellationToken)
{
var productList = await context.Product.ToListAsync();
return productList;
}
}
}
}
Let’s explain things:
Next, we need to add a method to the controller which will handle an HTTP Type GET request and will be calling the Query class. So, add GetAll() method to the ProductController.cs and it’s code is given below.
[HttpGet]
public async Task<IActionResult> GetAll()
{
return Ok(await mediator.Send(new GetAllProductQuery()));
}
Now open Postman and make HTTP GET request to the url – https://localhost:44378/api/Product. The port will be different in your case.
The postman will receive all the products in json and they will be displayed on the response body section. See the below image where I have shown this.
If you have more than one product in the database then the json will contain their details to, example as shown below:
{
"id": 1,
"name": "Shirts",
"price": 49.00
},
{
"id": 2,
"name": "Pants",
"price": 79.00
}
Let’s create another query class whose work will be to fetch a product by it’s id. So create a new class called GetProductByIdQuery.cs inside the Queries folder and add the following code to it.
using CQRSMediator.Models;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CQRSMediator.CQRS.Queries
{
public class GetProductByIdQuery : IRequest<Product>
{
public int Id { get; set; }
public class GetProductByIdQueryHandler : IRequestHandler<GetProductByIdQuery, Product>
{
private ProductContext context;
public GetProductByIdQueryHandler(ProductContext context)
{
this.context = context;
}
public async Task<Product> Handle(GetProductByIdQuery query, CancellationToken cancellationToken)
{
var product = await context.Product.Where(a => a.Id == query.Id).FirstOrDefaultAsync();
return product;
}
}
}
}
The code of this class is very similar to the previous class. The GetProductByIdQuery class inherits from IRequest<Product>. The type of IRequest is Product since the class is going to return a single product only.
Now see the hander class GetProductByIdQueryHandler’s Handle() method. It employs where clause of LINQ to fetch a product whose Id is provided through the controller.
var product = await context.Product.Where(a => a.Id == query.Id).FirstOrDefaultAsync();
Next, go to the ProductController.cs and add the GetById() of type HTTP GET will be called when HTTP GET type of request is made to the URL – https://localhost:44378/api/Product/{id}. Here replace {id} with the id of the product like:
https://localhost:44378/api/Product/1
https://localhost:44378/api/Product/2
https://localhost:44378/api/Product/3
This method’s code is given below:
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
return Ok(await mediator.Send(new GetProductByIdQuery { Id = id }));
}
Now open Postman and send HTTP GET request to the URL – https://localhost:44378/api/Product/1. The Postman will show you the JSON of the product having id 1. I have shown this in the below image:
Let us now create a CQRS command to update a product. Inside the CQRS ➤ Commands folder, add a new class called UpdateProductCommand.cs. It’s code is given below:
using CQRSMediator.Models;
using MediatR;
namespace CQRSMediator.CQRS.Commands
{
public class UpdateProductCommand : IRequest<int>
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public class UpdateProductCommandHandler : IRequestHandler<UpdateProductCommand, int>
{
private ProductContext context;
public UpdateProductCommandHandler(ProductContext context)
{
this.context = context;
}
public async Task<int> Handle(UpdateProductCommand command, CancellationToken cancellationToken)
{
var product = context.Product.Where(a => a.Id == command.Id).FirstOrDefault();
if (product == null)
{
return default;
}
else
{
product.Name = command.Name;
product.Price = command.Price;
await context.SaveChangesAsync();
return product.Id;
}
}
}
}
}
Notice that the UpdateProductCommand class implements IRequest<int> same as the CreateProductCommand.cs which was creating a new product on the database.
This class is self-explanatory and you can very well understand that it is updating a product. The product is updated inside the Handle() method, and this class returns the product id of the product which is updated.
Next, add Update() method of type Http Put to the ProductController class. The code is given below:
[HttpPut("{id}")]
public async Task<IActionResult> Update(int id, UpdateProductCommand command)
{
command.Id = id;
return Ok(await mediator.Send(command));
}
Let us now make a call to this method with Postman and update our product.
So, in Postman select PUT for the http verb and URL as https://localhost:44378/api/Product/1. Note that 1 at the end of the url is passing the id of the product.
Select “Body”, “raw” and “JSON” for the options just like when we created a product. For the text boy enter the new values for the product in json. I am changing the name of the product to “Men’s Shirt” and price to 89 by adding the below json to the text box.
{
"name": "Men's Shirt",
"price": 89
}
Finally click Send button and your product will get updated. See below image:
You can now confirm the product is updated by making GET request with Postman to the url – https://localhost:44378/api/Product. See the below screenshot.
The final CRUD operation is to delete a product with CQRS command. So, create a new class called DeleteProductByIdCommand.cs inside the CQRS ➤ Commands folder. Add the following code to this class:
using CQRSMediator.Models;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CQRSMediator.CQRS.Commands
{
public class DeleteProductByIdCommand : IRequest<int>
{
public int Id { get; set; }
public class DeleteProductByIdCommandHandler : IRequestHandler<DeleteProductByIdCommand, int>
{
private ProductContext context;
public DeleteProductByIdCommandHandler(ProductContext context)
{
this.context = context;
}
public async Task<int> Handle(DeleteProductByIdCommand command, CancellationToken cancellationToken)
{
var product = await context.Product.Where(a => a.Id == command.Id).FirstOrDefaultAsync();
context.Product.Remove(product);
await context.SaveChangesAsync();
return product.Id;
}
}
}
}
The class implements IRequest<int>, int as the type signifies that it will return an int value. Confirm this thing as Handle method is returning the product id at the very end.
return product.Id;
The handle method does the product deletion whose id the controller sends it to. The deletion of the product is done by Entity Framework Core.
Next, add the delete method to the ProductController.cs.
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
return Ok(await mediator.Send(new DeleteProductByIdCommand { Id = id }));
}
So, it’s time to test the delete functionality with Postman. So in Postman make Delete type of request to the URL – https://localhost:44378/api/Product/1. This will delete the product with id 1 from the database. The screenshot of Postman is shown below:
Now as usual make GET request to confirm the product is deleted. This time you will get empty json which tells the product is indeed deleted.
Congratulations, we successfully created CRUD operations with CQRS and Mediatory patterns in ASP.NET Core using Entity Framework Core.
So now we have 3 classes inside the Commands folder and 2 classes inside the Queries folder. Check the below image:
Let us now see other important features of MediatR library.
Till now we have seen a single request being handled by a single hander. Example – Delete request of a Product is handled by the Handle method of DeleteProductByIdCommandHandler.cs class.
Now we will see how a single request will be handled not by one but by multiple handlers. This is done by MediatR Notifications.
For example – whenever a product is deleted, we will do 2 things:
Let’s us create MediatR Notifications for it.
First create a new folder called Notifications on the root of the app. Then inside it, create a new class called DeleteProductNotification.cs with the code as given below:
using MediatR;
namespace CQRSMediator.Notifications
{
public class DeleteProductNotification : INotification
{
public int ProductId { get; set; }
}
public class EmailHandler : INotificationHandler<DeleteProductNotification>
{
public Task Handle(DeleteProductNotification notification, CancellationToken cancellationToken)
{
int id = notification.ProductId;
// send email to customers
return Task.CompletedTask;
}
}
public class SMSHandler : INotificationHandler<DeleteProductNotification>
{
public Task Handle(DeleteProductNotification notification, CancellationToken cancellationToken)
{
int id = notification.ProductId;
//send sms to sales team
return Task.CompletedTask;
}
}
}
This is the location of the class on the app directory.
Let’s understand what we are doing in this class:
int id = notification.ProductId;
Let’s trigger the notification which is done through the Publish method. So go to the ProductController.cs and add the Publish method to the Delete method as shown in highlighted code below:
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
await mediator.Publish(new Notifications.DeleteProductNotification { ProductId = id });
return Ok(await mediator.Send(new DeleteProductByIdCommand { Id = id }));
}
So now when you delete a product the MediatR Notification will be published and 2 handler classes will be executed. The handler classes are loosely coupled with the publish method. This is a great thing as we can extend more handler classes without any need to modify the publish method on the Delete method of the Product Controller class.
MediatR Behaviors are very similar to middlewares in ASP.NET Core. They accept a request, perform some action, then (optionally) pass along the request. The good thing about Behaviors is that we can put a logic in them instead of repeating it again and again in our app.
Let us create a MediatR behavior which will do logging for us. So create a new folder called Behaviors on the root of the app and add a new class called LoggingBehavior.cs to it.
The full code of this class is given below:
using MediatR;
namespace CQRSMediator.Behaviors
{
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly ILogger<LoggingBehavior<TRequest, TResponse>> logger;
public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
{
this.logger = logger;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
logger.LogInformation($"Befor {typeof(TRequest).Name}");
var response = await next();
logger.LogInformation($"After {typeof(TResponse).Name}");
return response;
}
}
}
Important things:
Go to the Program.cs and register this Behavior as shown below:
builder.Services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
To test the Behavior make a read product request with Postman. Like before make an HTTP GET request to the url – https://localhost:44378/api/Product. The port will be different in your case.
Next on Visual Studio open Output window whose path is View menu then Output. You will see 2 logs:
Information: Befor GetAllProductQuery
Information: After IEnumerable`1
I have shown these logs in the below given image:
Great! This is the logging output before and after our GetAllProductQuery query handler was invoked. Because of the loosely coupled architecture of CQRS we hooked the behaviour without touching any other feature of the app.
In this tutorial we first understood how CQRS and MediatR works and later created a full CRUD operation in ASP.NET Core app. We also created MediatR Notifications and Behaviors. This must be a good starting point for you. I hope you like this tutorial so kindly share it on reddit, facebook, twitter. The share links are given below.
SHARE THIS ARTICLE