BackendAdvanced
20 min readNov 24, 2025

Clean Architecture in .NET 8 with Real Examples

Implement clean architecture patterns in .NET 8 with entities, use cases, and dependency injection.

R

Rithy Tep

Author

Clean Architecture in .NET 8 with Real Examples

Project Structure

src/
├── Domain/              # Entities, Value Objects
├── Application/         # Use Cases, Interfaces
├── Infrastructure/      # Data Access, External Services
└── API/                 # Controllers, Middleware

Domain Layer - Entities

// Domain/Entities/Order.cs public class Order : BaseEntity { public string OrderNumber { get; private set; } public decimal TotalAmount { get; private set; } public OrderStatus Status { get; private set; } public List<OrderItem> Items { get; private set; } = new(); public static Order Create(string customerId, List<OrderItem> items) { var order = new Order { OrderNumber = GenerateOrderNumber(), Status = OrderStatus.Pending, Items = items }; order.CalculateTotal(); return order; } public void ConfirmOrder() { if (Status != OrderStatus.Pending) throw new InvalidOperationException("Order already processed"); Status = OrderStatus.Confirmed; } private void CalculateTotal() { TotalAmount = Items.Sum(item => item.Price * item.Quantity); } }

Application Layer - Use Cases

// Application/Orders/Commands/CreateOrderCommand.cs public record CreateOrderCommand( string CustomerId, List<OrderItemDto> Items ) : IRequest<OrderDto>; public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, OrderDto> { private readonly IOrderRepository _orderRepository; private readonly IUnitOfWork _unitOfWork; public async Task<OrderDto> Handle( CreateOrderCommand request, CancellationToken cancellationToken) { var items = request.Items.Select(dto => new OrderItem(dto.ProductId, dto.Quantity, dto.Price) ).ToList(); var order = Order.Create(request.CustomerId, items); await _orderRepository.AddAsync(order); await _unitOfWork.SaveChangesAsync(cancellationToken); return order.ToDto(); } }

Infrastructure Layer - Repository

// Infrastructure/Repositories/OrderRepository.cs public class OrderRepository : IOrderRepository { private readonly AppDbContext _context; public async Task<Order?> GetByIdAsync(Guid id) { return await _context.Orders .Include(o => o.Items) .FirstOrDefaultAsync(o => o.Id == id); } public async Task AddAsync(Order order) { await _context.Orders.AddAsync(order); } }

API Layer - Controller

[ApiController] [Route("api/[controller]")] public class OrdersController : ControllerBase { private readonly IMediator _mediator; [HttpPost] public async Task<ActionResult<OrderDto>> CreateOrder( CreateOrderCommand command) { var result = await _mediator.Send(command); return CreatedAtAction(nameof(GetOrder), new { id = result.Id }, result); } [HttpGet("{id}")] public async Task<ActionResult<OrderDto>> GetOrder(Guid id) { var query = new GetOrderQuery(id); var result = await _mediator.Send(query); return result ?? NotFound(); } }

Dependency Injection Setup

// Program.cs builder.Services.AddScoped<IOrderRepository, OrderRepository>(); builder.Services.AddScoped<IUnitOfWork, UnitOfWork>(); builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(CreateOrderCommand).Assembly) );

Benefits

  • Testability: Mock interfaces easily
  • Maintainability: Clear separation of concerns
  • Flexibility: Swap implementations without changing business logic
#.NET#C##Clean Architecture#CQRS#DDD
Clean Architecture in .NET 8 with Real Examples | Rithy Tep | Mercy Dev