Implementing CQRS with MediatR – Part 1

This article explains the CQRS and Mediator patterns, compares CQRS to CRUD, discusses trade‑offs, and provides a step‑by‑step guide to configuring MediatR in an ASP.NET Core Web API, creating commands, queries, handlers, and testing the setup with Postman.

AI Skills Research
AI Skills Research
AI Skills Research
Implementing CQRS with MediatR – Part 1

CQRS and Mediator Pattern

MediatR helps developers quickly implement two architectural patterns: CQRS and Mediator. Although they appear similar, they have distinct characteristics.

CQRS

CQRS (Command Query Responsibility Segregation) separates write (command) and read (query) responsibilities into different models, unlike the traditional CRUD approach where a single data store handles all four operations.

The diagram shows the application splitting query and command models. CQRS does not prescribe a specific way to separate them; the separation can be logical within a single application or physical across different servers, depending on the scenario.

The design of CQRS resembles database read‑write splitting.

While attractive, CQRS introduces trade‑offs such as managing separate systems, potential data staleness, and added complexity, which can lead to over‑design if not needed.

Mediator Pattern

The Mediator pattern defines an object that encapsulates how other objects interact, removing direct dependencies between them. It enables loose coupling, similar to Inversion of Control, making code simpler and easier to test.

In the illustration, SomeService sends a message to the Mediator, which then invokes multiple handlers without the services directly depending on each other. This is similar to a publish/subscribe broker.

Using MediatR

MediatR acts as an in‑process mediator, allowing all communication between the UI and data store to go through it. It is limited to in‑process messaging; for cross‑process scenarios, a message broker such as Kafka, RabbitMQ, or Azure Service Bus is recommended.

Configuring MediatR in an ASP.NET Core API Project

Project Setup

Create a new ASP.NET Core Web API project named CqrsMediatrExample.

Install Packages

PM> install-package MediatR

If using a version prior to 12, also install:

MediatR.Extensions.Microsoft.DependencyInjection

Register Services

In Program.cs add:

builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));

Now MediatR is ready for injection.

Data Store

Define a simple Product class and a fake in‑memory data store:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class FakeDataStore
{
    private static List<Product> _products;
    public FakeDataStore()
    {
        _products = new List<Product>
        {
            new Product { Id = 1, Name = "Test Product 1" },
            new Product { Id = 2, Name = "Test Product 2" },
            new Product { Id = 3, Name = "Test Product 3" }
        };
    }
    public async Task AddProduct(Product product)
    {
        _products.Add(product);
        await Task.CompletedTask;
    }
    public async Task<IEnumerable<Product>> GetAllProducts() => await Task.FromResult(_products);
}

Register the store as a singleton:

builder.Services.AddSingleton<FakeDataStore>();

Separate Commands, Queries, Handlers

Create three folders: Commands, Queries, and Handlers to physically separate the models.

Query Example

Define a query record:

public record GetProductsQuery() : IRequest<IEnumerable<Product>>;

Implement its handler:

public class GetProductsHandler : IRequestHandler<GetProductsQuery, IEnumerable<Product>>
{
    private readonly FakeDataStore _fakeDataStore;
    public GetProductsHandler(FakeDataStore fakeDataStore) => _fakeDataStore = fakeDataStore;
    public async Task<IEnumerable<Product>> Handle(GetProductsQuery request, CancellationToken cancellationToken) =>
        await _fakeDataStore.GetAllProducts();
}

Add an action in ProductsController to invoke the query:

[HttpGet]
public async Task<ActionResult> GetProducts()
{
    var products = await _mediator.Send(new GetProductsQuery());
    return Ok(products);
}

Command Example

Define a command record: public record AddProductCommand(Product Product) : IRequest; Implement its handler:

public class AddProductHandler : IRequestHandler<AddProductCommand>
{
    private readonly FakeDataStore _fakeDataStore;
    public AddProductHandler(FakeDataStore fakeDataStore) => _fakeDataStore = fakeDataStore;
    public async Task Handle(AddProductCommand request, CancellationToken cancellationToken)
    {
        await _fakeDataStore.AddProduct(request.Product);
        return;
    }
}

Add a POST action in the controller:

[HttpPost]
public async Task<ActionResult> AddProduct([FromBody] Product product)
{
    await _mediator.Send(new AddProductCommand(product));
    return StatusCode(201);
}

Testing with Postman

Run the project, then use Postman to send a GET request to retrieve the product list, a POST request to add a new product, and finally another GET to verify the new product appears.

Conclusion

The article demonstrated how to set up MediatR for CQRS in ASP.NET Core, covering both query and command flows, and highlighted the trade‑offs of CQRS. The next part will explore MediatR notifications.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Dependency InjectionCQRSMediator PatternASP.NET CoreQueryCommandMediatR
AI Skills Research
Written by

AI Skills Research

Fearlessly forging ahead, constantly growing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.