Lessons in this course (8)

6. CQRS, MediatR and cross-cutting concerns placement

Advanced★ Must knowLesson 6 of 8

CQRS separates commands (change state, return little) from queries (return data, no side effects). In a single app it usually means separate handler classes and models; reads can use Dapper/views while writes use EF Core and rich domain models.

csharp
public sealed record GetOrdersQuery(int CustomerId, int Page) : IRequest<PagedList<OrderDto>>;
public sealed class GetOrdersHandler(IDbConnectionFactory db) : IRequestHandler<GetOrdersQuery, PagedList<OrderDto>>
{
    public async Task<PagedList<OrderDto>> Handle(GetOrdersQuery q, CancellationToken ct) { /* Dapper query on a view */ return default!; }
}
// Controller: return Ok(await mediator.Send(new GetOrdersQuery(id, page)));

MediatR (commercial licence from v13, 2025) or similar mediators add pipeline behaviours for validation, logging, transactions. You can also call handlers directly via DI — the pattern doesn't require a library.

Where do cross-cutting concerns go?

ConcernPlace
Exception handlingApi: UseExceptionHandler + IExceptionHandler
ValidationApplication: FluentValidation + pipeline behaviour; Domain: invariants in constructors/methods
LoggingEverywhere via ILogger<T>; request logging middleware in Api
Authentication/authorizationApi (middleware, policies); resource checks in Application
TransactionsApplication (unit of work around a command)
CachingApplication (decorator/behaviour) or Infrastructure
ConfigurationApi/Infrastructure (Options pattern), injected as interfaces/options
MappingApplication (manual mapping, Mapster, or AutoMapper)