As modern enterprise applications scale in size and complexity, maintaining a clean, modular architecture becomes critical. Traditional layered designs often fall short when performance, scalability, and clear separation of concerns are required.

That’s where implementing CQRS and MediatR in ASP.NET Core can transform your software architecture—offering a highly testable, loosely coupled, and maintainable approach. 

In this blog, we’ll walk you through the process of applying the CQRS pattern with MediatR in an ASP.NET Core project, explore real-world design principles, and share practical tips tailored for teams building enterprise-grade applications. 

🔍 What is CQRS in ASP.NET Core? 

CQRS (Command Query Responsibility Segregation) is a design pattern that separates read operations from write operations. In large applications, this segregation can lead to improved performance, scalability, and maintainability. 

  • Command – Alters the state of the application (e.g., CreateOrder, UpdateCustomer). 

🔑 Benefits of CQRS in ASP.NET Core: 

  • Read and write operations scale independently. 
  • Business logic is easier to reason about. 
  • Read models can be optimized separately from write models. 

🧩 How MediatR Complements CQRS 

MediatR is a popular .NET library that implements the Mediator design pattern, helping you decouple the sender of a request from its handler. Instead of invoking services directly, you send commands and queries via IMediator. 

csharp 

CopyEdit 

// Traditional service call 
_orderService.CreateOrder(cmd); 
 
// With MediatR 
await _mediator.Send(cmd); 
 

Using MediatR in ASP.NET Core supports a clean separation of concerns, eliminates tight coupling, and promotes better testability—key advantages for enterprise software systems. 

📁 CQRS + MediatR Project Structure in ASP.NET Core 

Let’s say we are building an Order Management System. A modular folder structure may look like this: 

markdown 

CopyEdit 

/Features 
 /Orders 
   – CreateOrderCommand.cs 
   – CreateOrderHandler.cs 
   – GetOrderByIdQuery.cs 
   – GetOrderByIdHandler.cs 
 

🧰 Tech Stack: 

  • ASP.NET Core 8 
  • Entity Framework Core 
  • MediatR 
  • FluentValidation 

🚀 Setting Up ASP.NET Core with MediatR and FluentValidation 

First, install the required NuGet packages: 

bash 

CopyEdit 

dotnet add package MediatR.Extensions.Microsoft.DependencyInjection 
dotnet add package FluentValidation 
 

Then, configure Program.cs: 

csharp 

CopyEdit 

builder.Services.AddMediatR(cfg => 
   cfg.RegisterServicesFromAssembly(typeof(Program).Assembly)); 
builder.Services.AddValidatorsFromAssembly(typeof(Program).Assembly); 
 

This setup allows your commands and queries to be routed and validated without cluttering your controllers. 

🧾 Implementing a Command: CreateOrder 

CreateOrderCommand.cs 

csharp 

CopyEdit 

public record CreateOrderCommand(string CustomerName, List<string> Items) : IRequest<Guid>; 
 

CreateOrderHandler.cs 

csharp 

CopyEdit 

public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, Guid> 

   private readonly AppDbContext _db; 
 
   public CreateOrderHandler(AppDbContext db) 
   { 
       _db = db; 
   } 
 
   public async Task<Guid> Handle(CreateOrderCommand request, CancellationToken cancellationToken) 
   { 
       var order = new Order 
       { 
           Id = Guid.NewGuid(), 
           CustomerName = request.CustomerName, 
           Items = string.Join(“,”, request.Items), 
           CreatedAt = DateTime.UtcNow 
       }; 
 
       _db.Orders.Add(order); 
       await _db.SaveChangesAsync(cancellationToken); 
 
       return order.Id; 
   } 

 

Validation with FluentValidation 

csharp 

CopyEdit 

public class CreateOrderValidator : AbstractValidator<CreateOrderCommand> 

   public CreateOrderValidator() 
   { 
       RuleFor(x => x.CustomerName).NotEmpty(); 
       RuleFor(x => x.Items).NotEmpty().WithMessage(“Order must have at least one item.”); 
   } 

 

🔎 Implementing a Query: GetOrderById 

GetOrderByIdQuery.cs 

csharp 

CopyEdit 

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

GetOrderByIdHandler.cs 

csharp 

CopyEdit 

public class GetOrderByIdHandler : IRequestHandler<GetOrderByIdQuery, OrderDto> 

   private readonly AppDbContext _db; 
 
   public GetOrderByIdHandler(AppDbContext db) 
   { 
       _db = db; 
   } 
 
   public async Task<OrderDto> Handle(GetOrderByIdQuery request, CancellationToken cancellationToken) 
   { 
       var order = await _db.Orders.FindAsync(request.OrderId); 
       if (order == null) return null; 
 
       return new OrderDto 
       { 
           Id = order.Id, 
           CustomerName = order.CustomerName, 
           Items = order.Items.Split(‘,’).ToList(), 
           CreatedAt = order.CreatedAt 
       }; 
   } 

 

🌐 Using CQRS and MediatR in ASP.NET Core Controllers 

csharp 

CopyEdit 

[ApiController] 
[Route(“api/[controller]”)] 
public class OrdersController : ControllerBase 

   private readonly IMediator _mediator; 
 
   public OrdersController(IMediator mediator) 
   { 
       _mediator = mediator; 
   } 
 
   [HttpPost] 
   public async Task<IActionResult> Create(CreateOrderCommand cmd) 
   { 
       var id = await _mediator.Send(cmd); 
       return CreatedAtAction(nameof(Get), new { id }, null); 
   } 
 
   [HttpGet(“{id}”)] 
   public async Task<IActionResult> Get(Guid id) 
   { 
       var order = await _mediator.Send(new GetOrderByIdQuery(id)); 
       return order == null ? NotFound() : Ok(order); 
   } 

 

💡 Real-World Advantages of CQRS and MediatR in Enterprise Systems 

✅ Key Benefits: 

  • Separation of concerns – Each handler focuses on a single responsibility. 
  • Testability – Easier to write unit tests for individual handlers. 
  • Scalability – Read operations can scale independently from writes. 
  • Pipeline behaviors – Enables cross-cutting features like logging and transactions. 

⚠️ Common Pitfalls to Avoid: 

  • Overuse in simple apps – CQRS may be unnecessary for basic CRUD operations. 
  • Misaligned validation – Use centralized validators with FluentValidation. 
  • Inconsistent boundaries – Know when and where to separate commands and queries. 

📌 When Should You Use CQRS and MediatR in ASP.NET Core? 

Ideal use cases: 

  • Applications with complex business rules. 
  • Scenarios where reads and writes have very different performance needs. 
  • Teams seeking a decoupled architecture that supports scalability and DDD. 

Avoid it when: 

  • Your app is a simple internal tool or admin dashboard. 
  • Project timelines are tight and do not allow for architectural overhead. 

🏁 Conclusion: Implementing CQRS and MediatR in ASP.NET Core 

For enterprise-grade ASP.NET Core applications, implementing CQRS and MediatR brings structure, scalability, and testability to your software architecture. By clearly separating responsibilities, you empower your team to develop more robust, maintainable, and future-ready solutions. 

Whether you’re starting fresh or modernizing a legacy system, this pattern is worth considering for its ability to simplify complex logic and enhance long-term agility. 

Additional Resources: