Pular para o conteúdo
.NETRequests e Commands com MediatR
Módulo 07Tópicos Avançados (Bônus)

Requests e Commands com MediatR

avancado 40 min de leitura·Atualizado · .NET 9
Resumo

Implementamos CQRS com MediatR: uma Query para leitura e um Command para escrita, cada um com seu handler. O controller apenas envia a mensagem via ISender.

Nota

a partir da v13.0.0 (2 de julho de 2025, Lucky Penny Software), o MediatR adota modelo comercial para uso empresarial. A v12.x (última livre, MIT) permanece gratuita. Avalie a licença antes de adotar a v13+ em produção; alternativas open-source (ex.: Wolverine, ou um mediator próprio) existem. Os conceitos de CQRS/Mediator desta parte valem para qualquer implementação.

1. Objetivos de Aprendizagem#

  • Instalar e registrar o MediatR.
  • Criar queries e commands com seus handlers.
  • Fazer o controller enviar mensagens em vez de chamar serviços.

2. Pré-requisitos#

  • Conceito de CQRS/Mediator (tópico 44.2).

3. Conceito#

No MediatR, cada caso de uso é uma mensagem (IRequest<TResponse>) tratada por um IRequestHandler. O controller injeta ISender e faz Send(mensagem). O porquê: o controller não conhece a implementação — só a intenção. Isso mantém controllers finos e a lógica em handlers testáveis.

4. Mão na Massa#

4.1. Setup#

Shell
dotnet new classlib -n Catalog.Application -f net9.0
dotnet add Catalog.Application package MediatR
dotnet add Catalog.Api reference Catalog.Application

Registro:

C#
builder.Services.AddMediatR(cfg =>
    cfg.RegisterServicesFromAssembly(typeof(Catalog.Application.AssemblyMarker).Assembly));

4.2. Implementação Passo a Passo#

  1. Query + handler (leitura):
C#
using MediatR;

public record GetProdutosQuery(Guid CategoriaId, bool TrackChanges)
    : IRequest<IEnumerable<ProdutoDto>>;

internal sealed class GetProdutosHandler
    : IRequestHandler<GetProdutosQuery, IEnumerable<ProdutoDto>>
{
    private readonly IRepositoryManager _repo;
    private readonly IMapper _mapper;
    public GetProdutosHandler(IRepositoryManager repo, IMapper mapper)
        => (_repo, _mapper) = (repo, mapper);

    public async Task<IEnumerable<ProdutoDto>> Handle(
        GetProdutosQuery request, CancellationToken ct)
    {
        var produtos = await _repo.Produto.GetByCategoriaAsync(
            request.CategoriaId, request.TrackChanges, ct);
        return _mapper.Map<IEnumerable<ProdutoDto>>(produtos);
    }
}
  1. Command + handler (escrita):
C#
public record CreateProdutoCommand(Guid CategoriaId, ProdutoForCreationDto Produto)
    : IRequest<ProdutoDto>;

internal sealed class CreateProdutoHandler
    : IRequestHandler<CreateProdutoCommand, ProdutoDto>
{
    private readonly IRepositoryManager _repo;
    private readonly IMapper _mapper;
    public CreateProdutoHandler(IRepositoryManager repo, IMapper mapper)
        => (_repo, _mapper) = (repo, mapper);

    public async Task<ProdutoDto> Handle(CreateProdutoCommand request, CancellationToken ct)
    {
        var entity = _mapper.Map<Produto>(request.Produto);
        entity.CategoriaId = request.CategoriaId;
        _repo.Produto.Create(entity);
        await _repo.SaveAsync();
        return _mapper.Map<ProdutoDto>(entity);
    }
}
  1. Controller com ISender:
C#
private readonly ISender _sender;
public ProdutosController(ISender sender) => _sender = sender;

[HttpGet]
public async Task<IActionResult> Get(Guid categoriaId, CancellationToken ct) =>
    Ok(await _sender.Send(new GetProdutosQuery(categoriaId, false), ct));

[HttpPost]
public async Task<IActionResult> Post(Guid categoriaId, ProdutoForCreationDto dto, CancellationToken ct)
{
    var criado = await _sender.Send(new CreateProdutoCommand(categoriaId, dto), ct);
    return CreatedAtRoute("ProdutoById", new { categoriaId, id = criado.Id }, criado);
}

4.3. Executando#

Shell
dotnet run
curl http://localhost:5000/api/categorias/{catId}/produtos

5. Exemplo Completo#

Cada novo caso de uso vira um par mensagem+handler, sem inflar controllers nem serviços genéricos.

6. Boas Práticas e Armadilhas#

FaçaEvite
Um handler por caso de usoHandlers gigantes com múltiplas responsabilidades
Propagar CancellationToken ao SendIgnorar cancelamento
Manter commands/queries imutáveis (record)Mensagens mutáveis compartilhadas
Atenção

MediatR resolve handlers via DI. Se o assembly não for registrado corretamente (RegisterServicesFromAssembly), o Send lança "handler não encontrado" em runtime.

7. Segurança e Produção#

  • A validação e a autorização por caso de uso podem ser centralizadas em pipeline behaviors (próximo tópico), evitando repetição.

8. Exercícios#

  • Fácil: crie uma GetProdutoByIdQuery.
  • Médio: implemente UpdateProdutoCommand e DeleteProdutoCommand.
  • Desafio: separe leitura e escrita em pastas/camadas distintas seguindo CQRS.

9. Resumo#

Com MediatR, cada caso de uso é uma query/command com handler dedicado; o controller só envia mensagens via ISender. Controllers ficam finos e a lógica, testável.

10. Próximos Passos#

A seguir, notifications e pipeline behaviors (validação transversal).

11. Referências#

44-03 — Requests e Commands com MediatR | Curso ASP.NET Core