About

sprout · Apr 21, 2026 · 2 min read

MediatR


MediatR is a popular open-source .NET library by Jimmy Bogard that provides an in-process implementation of the Mediator design pattern. In plain terms, it acts as a middleman — instead of one object talking directly to another, both talk to MediatR, and MediatR routes the message to whoever should handle it.

It is the most common way to implement CQRS in a .NET application.


How MediatR connects to CQRS

CQRS defines the concept — separate your reads from your writes. MediatR provides the mechanism to do it cleanly in .NET:

CQRS concept MediatR equivalent
Command IRequest (returns Unit or a result)
Query IRequest<TResponse>
Command Handler IRequestHandler<TCommand, TResponse>
Query Handler IRequestHandler<TQuery, TResponse>
Command/Query Bus IMediator.Send()
Notification (event) INotification + INotificationHandler

Core flow

Controller
    │
    └──► mediator.Send(new GetUserQuery(id))
                │
                └──► MediatR resolves ──► GetUserQueryHandler.Handle()
                                                │
                                                └──► returns UserDto

The controller never knows GetUserQueryHandler exists. MediatR resolves the correct handler from the DI container at runtime.


Setting it up

Install via NuGet:

dotnet add package MediatR

Register in Program.cs:

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

A minimal CQRS example

Query + Handler:

// Query (the request)
public record GetProductQuery(int Id) : IRequest<ProductDto>;

// Handler (the responder)
public class GetProductHandler : IRequestHandler<GetProductQuery, ProductDto>
{
    public async Task<ProductDto> Handle(GetProductQuery request, CancellationToken ct)
    {
        // fetch from DB and return DTO
    }
}

Command + Handler:

public record CreateProductCommand(string Name, decimal Price) : IRequest<int>;

public class CreateProductHandler : IRequestHandler<CreateProductCommand, int>
{
    public async Task<int> Handle(CreateProductCommand request, CancellationToken ct)
    {
        // persist to DB, return new ID
    }
}

Dispatching from a controller:

[ApiController]
public class ProductsController : ControllerBase
{
    private readonly IMediator _mediator;

    public ProductsController(IMediator mediator) => _mediator = mediator;

    [HttpGet("{id}")]
    public Task<ProductDto> Get(int id) => _mediator.Send(new GetProductQuery(id));

    [HttpPost]
    public Task<int> Create(CreateProductCommand cmd) => _mediator.Send(cmd);
}

Pipeline Behaviours (middleware for handlers)

One of MediatR's most powerful features is IPipelineBehavior<TRequest, TResponse> — it lets you wrap every handler with cross-cutting logic without touching the handler itself.

Common uses:

Behaviour What it does
Validation Run FluentValidation before the handler executes
Logging Log request/response automatically
Caching Return a cached result for queries
Transaction Wrap commands in a DB transaction
public class LoggingBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
    {
        Console.WriteLine($"Handling {typeof(TRequest).Name}");
        var response = await next();
        Console.WriteLine($"Handled {typeof(TResponse).Name}");
        return response;
    }
}

Register it:

builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehaviour<,>));

When to use MediatR

MediatR shines when:

Avoid it for simple CRUD apps where the extra layer adds noise without benefit.



Sources

  1. CQRS Pattern with MediatR – Milan Jovanović
  2. CQRS and MediatR in ASP.NET Core – Code Maze
  3. Implementing the microservice application layer using Web API – Microsoft Learn