As .NET applications grow in size and complexity, organizing your codebase becomes critical. Clean Architecture has become one of the most popular and effective ways to structure enterprise applications in .NET.
In this article, we’ll break down Clean Architecture in simple terms, explore its building blocks, and provide practical examples using .NET 8, EF Core, and Dependency Injection.
What Is Clean Architecture?
Clean Architecture is a software design approach proposed by Robert C. Martin (“Uncle Bob”). The main idea is simple:
Your business logic should not depend on infrastructure. Infrastructure should depend on your business logic.
This leads to:
Core Principles
1. Separation of Concerns
Each layer has one responsibility.
2. Dependency Rule
The inner layers should not depend on external layers.
3. Independent Business Logic
Your core domain should work even without:
Database
UI
Third-party services
Frameworks
Typical Clean Architecture Layers
Presentation Layer (API, MVC, Blazor)
↓
Application Layer (Use Cases)
↓
Domain Layer (Entities, Rules)
↓
Infrastructure Layer (EF Core, Files, External APIs, Services)
✔ Domain Layer — The Heart of the System
The Domain is the most important layer. It contains:
Entities
Value Objects
Interfaces
Domain Events
Example: Product Entity
public class Product
{
public int Id { get; private set; }
public string Name { get; private set; }
public decimal Price { get; private set; }
public Product(string name, decimal price)
{
Name = name;
Price = price;
}
public void UpdatePrice(decimal newPrice)
{
Price = newPrice;
}
}
Notice there is no reference to EF Core.
✔ Application Layer — Use Cases
Contains:
Example: IProductRepository
public interface IProductRepository
{
Task<Product> GetByIdAsync(int id);
Task AddAsync(Product product);
Task SaveChangesAsync();
}
Example: CreateProductCommand
public class CreateProductCommand
{
public string Name { get; set; }
public decimal Price { get; set; }
}
Command Handler
public class CreateProductHandler
{
private readonly IProductRepository _repo;
public CreateProductHandler(IProductRepository repo)
{
_repo = repo;
}
public async Task<int> HandleAsync(CreateProductCommand command)
{
var product = new Product(command.Name, command.Price);
await _repo.AddAsync(product);
await _repo.SaveChangesAsync();
return product.Id;
}
}
Notice: No EF Core here.
The application layer depends only on the Domain.
✔ Infrastructure Layer — Implementation Details
This is where you add:
Example: EF Core Repository
public class ProductRepository : IProductRepository
{
private readonly AppDbContext _db;
public ProductRepository(AppDbContext db)
{
_db = db;
}
public async Task<Product> GetByIdAsync(int id)
{
return await _db.Products.FindAsync(id);
}
public async Task AddAsync(Product product)
{
await _db.Products.AddAsync(product);
}
public async Task SaveChangesAsync()
{
await _db.SaveChangesAsync();
}
}
✔ Presentation Layer — API Controllers
The thin layer that exposes endpoints.
Example: ASP.NET Core Controller
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
private readonly CreateProductHandler _handler;
public ProductsController(CreateProductHandler handler)
{
_handler = handler;
}
[HttpPost]
public async Task<IActionResult> Create(CreateProductCommand command)
{
var id = await _handler.HandleAsync(command);
return Ok(new { ProductId = id });
}
}
Controller → Application Layer → Domain → Infrastructure
Exactly how clean architecture flows.
Dependency Injection Setup
In .NET 8, register the dependencies like this:
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddScoped<CreateProductHandler>();
Why Clean Architecture Works So Well
✔ Testability
You can test domain and application logic without database or API calls.
✔ Maintainability
Each responsibility lives in its correct layer.
✔ Flexibility
Change EF Core → Dapper → MongoDB without touching business logic.
✔ Perfect for Microservices
Each service becomes clean, maintainable, and scalable.
Common Mistakes to Avoid
❌ Putting EF Core code in Domain layer
❌ Putting business logic in Controllers
❌ Letting Infrastructure leak into Application layer
❌ Making handlers too big (violate SRP)
❌ Not separating Commands & Queries
Conclusion
Clean Architecture continues to be one of the best ways to structure enterprise-level .NET applications in 2025. By separating domain logic, application logic, and infrastructure details, you create a robust system that is easier to test, maintain, and scale.