Implementing MediatR for CQRS and Command-Query Separation


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.


Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *