6 January 2019
In a previous post, we explored the CQRS (Command Query Responsibility Segregation) pattern, which is a powerful way to separate the handling of command and query logic in your application. While CQRS helps structure your application in a more maintainable way, it can become complex, especially as your application grows. To help manage this complexity, we can use a library called MediatR.
In this post, we will introduce MediatR, demonstrate how it can be used to implement CQRS in .NET Core, and show you how to create commands, queries, and handlers with it. By the end of this post, you will be able to leverage MediatR to streamline your CQRS architecture and improve the maintainability of your application.
What is MediatR?
MediatR is a simple and powerful library that helps to implement mediator patterns in .NET applications. It helps to decouple different components in an application by promoting communication through a central mediator rather than directly between components. MediatR is particularly useful for implementing CQRS because it allows us to clearly separate the command and query logic and handle them independently.
MediatR provides:
- Request objects (Commands and Queries)
- Handler classes that handle these requests
- A Mediator class to dispatch requests to their handlers.
With MediatR, you no longer have to manually wire up your command and query logic, as it handles that for you in a clean and efficient way.
Setting Up MediatR in Your .NET Core Project
First, you’ll need to install MediatR via NuGet:
dotnet add package MediatR
Then, add the MediatR dependency to your Startup.cs by registering it in the ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
services.AddMediatR(Assembly.GetExecutingAssembly());
}
Creating a Command and Query with MediatR
In CQRS applications, the Command is used for writing data (create, update, delete), while the Query is used for reading data. With MediatR, these are represented as request classes.
Creating a Command
Let’s start by creating a simple CreateProductCommand to handle the creation of a product.
public class CreateProductCommand : IRequest<Product>
{
public string Name { get; set; }
public decimal Price { get; set; }
}
In this case, the CreateProductCommand
implements the IRequest
interface, which tells MediatR this is a request that the mediator should handle. We’re also specifying that the return type of this request is a Product.
Creating a Query
Next, let’s create a GetProductQuery to retrieve a product by its ID.
public class GetProductQuery : IRequest<Product>
{
public int ProductId { get; set; }
}
Just like the command, the GetProductQuery
implements IRequest
, and the handler will return a Product.
Creating Handlers for Commands and Queries
Now that we have our command and query classes, let’s create handlers to process them.
Command Handler
For the CreateProductCommand, we need a handler that will create the product and save it to the database.
public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, Product>
{
private readonly ApplicationDbContext _context;
public CreateProductCommandHandler(ApplicationDbContext context)
{
_context = context;
}
public async Task<Product> Handle(CreateProductCommand request, CancellationToken cancellationToken)
{
var product = new Product
{
Name = request.Name,
Price = request.Price
};
_context.Products.Add(product);
await _context.SaveChangesAsync();
return product;
}
}
Here, we define the Handle
method, which is called when the CreateProductCommand
is dispatched. It creates a new product and saves it to the database.
Query Handler
Similarly, we create a handler for the GetProductQuery:
public class GetProductQueryHandler : IRequestHandler<GetProductQuery, Product>
{
private readonly ApplicationDbContext _context;
public GetProductQueryHandler(ApplicationDbContext context)
{
_context = context;
}
public async Task<Product> Handle(GetProductQuery request, CancellationToken cancellationToken)
{
var product = await _context.Products
.FirstOrDefaultAsync(p => p.ProductId == request.ProductId, cancellationToken);
return product;
}
}
The Handle
method for the query retrieves a product by its ID from the database.
Using MediatR in Controllers
Once the handlers are set up, we can use MediatR to dispatch commands and queries from our controllers.
Product Command Controller
[ApiController]
[Route("api/products")]
public class ProductCommandController : ControllerBase
{
private readonly IMediator _mediator;
public ProductCommandController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateProductCommand command)
{
var product = await _mediator.Send(command);
return Ok(product);
}
}
Product Query Controller
[ApiController]
[Route("api/products")]
public class ProductQueryController : ControllerBase
{
private readonly IMediator _mediator;
public ProductQueryController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
var product = await _mediator.Send(new GetProductQuery { ProductId = id });
if (product == null)
return NotFound();
return Ok(product);
}
}
In the controllers, we use MediatR‘s Send
method to dispatch commands and queries. The CreateProductCommand
is sent to the CreateProductCommandHandler
, and the GetProductQuery
is sent to the GetProductQueryHandler
.
Conclusion
In this post, we introduced MediatR as a library for implementing CQRS in .NET Core. By using MediatR, we can simplify the handling of commands and queries and cleanly separate the responsibilities of reading and writing data. We also saw how to set up command and query models and their respective handlers, as well as how to integrate them with .NET Core controllers.
MediatR streamlines the development process by reducing boilerplate code and making your CQRS architecture more maintainable. In future posts, we’ll dive deeper into additional CQRS patterns and explore more advanced uses of MediatR.