What it is
MediatR implements the mediator pattern for .NET, dispatching requests to handlers and decoupling callers from implementations, with a pipeline for cross-cutting behaviour.
Define a request and a handler. Controllers send the request rather than calling a service, and pipeline behaviours wrap every handler for logging, validation or transactions.
Installation
dotnet add package MediatRGetting started
The smallest useful thing you can do with it, and what each part means.
csharp
public record GetBookQuery(int Id) : IRequest<BookDto?>;
public class GetBookHandler : IRequestHandler<GetBookQuery, BookDto?>
{
private readonly AppDb _db;
public GetBookHandler(AppDb db) => _db = db;
public async Task<BookDto?> Handle(GetBookQuery request, CancellationToken ct) =>
await _db.Books
.Where(b => b.Id == request.Id)
.Select(b => new BookDto(b.Id, b.Title))
.FirstOrDefaultAsync(ct);
}
app.MapGet("/books/{id}", async (int id, ISender sender) =>
{
var book = await sender.Send(new GetBookQuery(id));
return book is null ? Results.NotFound() : Results.Ok(book);
});Advanced usage
Where the library earns its place over a simpler alternative.
csharp
public class ValidationBehaviour<TReq, TRes> : IPipelineBehavior<TReq, TRes>
where TReq : notnull
{
private readonly IEnumerable<IValidator<TReq>> _validators;
public async Task<TRes> Handle(TReq request,
RequestHandlerDelegate<TRes> next, CancellationToken ct)
{
var failures = (await Task.WhenAll(
_validators.Select(v => v.ValidateAsync(request, ct))))
.SelectMany(r => r.Errors)
.Where(f => f is not null)
.ToList();
if (failures.Count > 0) throw new ValidationException(failures);
return await next();
}
}
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- No handler was found for request of type
- The assembly containing handlers was not registered. Check AddMediatR points at the right assembly.
- Behaviours do not run
- Open generic registration is required: typeof(IPipelineBehavior<,>), not a closed type.
Best practices
- Use records for requests; they are immutable and give value equality for free.
- Put cross-cutting concerns in behaviours rather than repeating them in handlers.
- Keep handlers thin and focused on one use case.
- Consider whether the indirection is worth it — for a small CRUD API, calling a service directly is simpler.
Background
Why it exists, and what it was reacting to.
Created by Jimmy Bogard, MediatR became the backbone of CQRS-style .NET architectures, where each use case is a request type with its own handler.
