Roadmap › .NET Project Architectures & Folder Structures — Layered, Clean, Onion, Vertical Slice › Module: Architecture Styles
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?
| Concern | Place |
|---|---|
| Exception handling | Api: UseExceptionHandler + IExceptionHandler |
| Validation | Application: FluentValidation + pipeline behaviour; Domain: invariants in constructors/methods |
| Logging | Everywhere via ILogger<T>; request logging middleware in Api |
| Authentication/authorization | Api (middleware, policies); resource checks in Application |
| Transactions | Application (unit of work around a command) |
| Caching | Application (decorator/behaviour) or Infrastructure |
| Configuration | Api/Infrastructure (Options pattern), injected as interfaces/options |
| Mapping | Application (manual mapping, Mapster, or AutoMapper) |