← All topics
🧩 Design Patterns

Mediator Pattern

Decoupling senders from handlers via a central dispatcher — MediatR in practice.

Mediator removes direct dependencies between components by routing communication through a central object. In .NET, this almost always means MediatR: instead of a controller directly calling five different services, it sends one IRequest through IMediator.Send(), and a single matching IRequestHandler processes it — the controller doesn't know or care what handles it.

public record GetOrderQuery(Guid OrderId) : IRequest<OrderDto>;

public class GetOrderQueryHandler(IOrderRepository repo) : IRequestHandler<GetOrderQuery, OrderDto>
{
    public async Task<OrderDto> Handle(GetOrderQuery request, CancellationToken ct)
    {
        var order = await repo.GetByIdAsync(request.OrderId)
            ?? throw new NotFoundException(request.OrderId);
        return order.ToDto();
    }
}

// controller
[HttpGet("{id}")]
public async Task<OrderDto> Get(Guid id) => await mediator.Send(new GetOrderQuery(id));

This is also the natural on-ramp into CQRS (next topic): Mediator gives you a clean, consistent way to route both Commands and Queries through the same pipeline, and MediatR's pipeline behaviors (IPipelineBehavior) are where cross-cutting concerns — validation, logging, transactions — get bolted on without cluttering individual handlers.

Flashcards (3)

What problem does Mediator solve in an ASP.NET Core controller with many dependencies?
tap to reveal answer
It removes the controller's direct coupling to every service it needs — the controller sends one request object through IMediator, and a single matching handler processes it. The controller doesn't need to know which service(s) handle the logic.
In MediatR, what's the relationship between IRequest<TResponse> and IRequestHandler<TRequest, TResponse>?
tap to reveal answer
IRequest<TResponse> is the message/DTO being sent; IRequestHandler<TRequest, TResponse> is the single class that processes it and returns TResponse. MediatR resolves the matching handler at runtime via DI when you call mediator.Send().
What are MediatR pipeline behaviors used for?
tap to reveal answer
Cross-cutting concerns that should apply to many/all requests — validation, logging, transaction wrapping, caching — implemented once as an IPipelineBehavior<TRequest,TResponse> that wraps every handler call, instead of duplicating that logic inside each handler.