Clean Architecture in .NET: A Practical Guide for Developers
Clean Architecture in .NET: A Practical Guide for Developers
Clean Architecture in .NET is a practical approach for organizing applications so that business logic remains independent from databases, frameworks, external services, and user-interface concerns. As a .NET application grows, placing controllers, business rules, database queries, validation, and third-party integrations in the same project can make the code difficult to test and maintain. Clean Architecture addresses this problem by separating responsibilities and controlling the direction of dependencies. The most important idea is simple: the core business logic should not depend on infrastructure details. Instead, infrastructure components should depend on abstractions defined by the application core. In this guide, we will explore Clean Architecture in .NET, its layers, project structure, Dependency Injection, practical C# examples, testing strategy, common mistakes, and production considerations for enterprise applications.
What Is Clean Architecture in .NET?
Clean Architecture is a software architecture style that separates an application into independent layers with clearly defined responsibilities. The architecture is designed to keep business rules at the center while moving technical implementation details such as databases, file systems, email providers, HTTP clients, and external APIs toward the outer layers.
The central principle is the Dependency Rule. Dependencies should point inward toward the core of the application. The Domain layer should not know whether the application uses SQL Server, PostgreSQL, Entity Framework Core, a REST API, or another persistence technology. This separation makes the application easier to test, change, and maintain.
Microsoft’s .NET architecture guidance also describes Clean Architecture as an approach where the application core contains business logic and abstractions, while infrastructure implementations depend on those abstractions. This approach is particularly useful for non-trivial ASP.NET Core applications.
For developers who want to strengthen their overall architecture knowledge, the Advanced .NET Development Guide provides a broader overview of enterprise development concepts such as SOLID principles, Dependency Injection, design patterns, performance, and scalability.
Why Use Clean Architecture in .NET?
A small application can often work well with a simple project structure. However, as features increase, technical dependencies can quickly become difficult to manage. A controller may directly call Entity Framework Core, contain business calculations, validate input, send emails, and communicate with external APIs. Such code may work initially, but changes become increasingly risky.
Clean Architecture separates these responsibilities so that each part of the system has a specific purpose. Business rules can evolve without requiring changes to database implementation details, while infrastructure technologies can be replaced without rewriting the entire application.
- Business logic becomes easier to understand.
- Unit testing becomes simpler and faster.
- Infrastructure technologies can be replaced more easily.
- Controllers remain small and focused.
- Dependencies become explicit and controlled.
- Large applications become easier for teams to maintain.
- Technical debt can be reduced through clear boundaries.
Layers of Clean Architecture in .NET
There are different ways to name and organize Clean Architecture layers. A common ASP.NET Core implementation uses Domain, Application, Infrastructure, and Presentation layers. Some projects combine Domain and Application into an Application Core, while others keep them as separate projects. The important point is not the exact folder names but the dependency direction and responsibility of each layer.
1. Domain Layer
The Domain layer is the innermost part of the application. It contains the core business concepts and rules. It should have minimal dependencies and should not depend on ASP.NET Core, Entity Framework Core, SQL Server, external APIs, or other infrastructure technologies.
Typical Domain components include entities, value objects, domain exceptions, business rules, aggregates, and domain services. For example, an e-commerce application might have an Order entity that contains rules related to order status, payment state, or cancellation.
public class Order
{
public int Id { get; private set; }
public decimal TotalAmount { get; private set; }
public bool IsCancelled { get; private set; }
public void Cancel()
{
if (IsCancelled)
throw new InvalidOperationException("Order is already cancelled.");
IsCancelled = true;
}
}
Notice that this entity does not know anything about databases or HTTP requests. That independence is one of the major strengths of Clean Architecture.
2. Application Layer
The Application layer contains application-specific business workflows and use cases. It coordinates operations performed by the Domain layer and communicates with external concerns through abstractions such as interfaces.
For example, an application may define an IOrderRepository interface inside the Application layer. The Application layer knows that it needs order data, but it does not need to know whether the data comes from Entity Framework Core, Dapper, a web service, or another storage mechanism.
public interface IOrderRepository
{
Task<Order?> GetByIdAsync(int id);
Task SaveAsync(Order order);
}
Application services or use-case handlers can then depend on this interface instead of depending directly on a database implementation.
3. Infrastructure Layer
The Infrastructure layer contains implementation details that interact with external technologies. This may include Entity Framework Core, SQL Server, file storage, email providers, third-party APIs, caching providers, message brokers, and logging integrations.
For example, the Infrastructure project can implement the IOrderRepository interface using Entity Framework Core.
public class OrderRepository : IOrderRepository
{
private readonly AppDbContext _context;
public OrderRepository(AppDbContext context)
{
_context = context;
}
public async Task<Order?> GetByIdAsync(int id)
{
return await _context.Orders.FindAsync(id);
}
public async Task SaveAsync(Order order)
{
_context.Orders.Update(order);
await _context.SaveChangesAsync();
}
}
The important point is that Infrastructure implements an abstraction rather than forcing the business layer to depend on Entity Framework Core.
4. Presentation Layer
The Presentation layer is responsible for interacting with users or external clients. In an ASP.NET Core application, this layer commonly contains Web API controllers, MVC controllers, Razor Pages, middleware, filters, request models, and response models.
A controller should ideally coordinate the request and delegate the actual application work to the Application layer. It should not contain complex business rules or direct database operations.
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
private readonly IOrderService _orderService;
public OrdersController(IOrderService orderService)
{
_orderService = orderService;
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
var order = await _orderService.GetAsync(id);
return order == null ? NotFound() : Ok(order);
}
}
Clean Architecture Project Structure
A practical ASP.NET Core solution can be divided into separate projects instead of putting every class into folders inside one project. Separating projects provides stronger boundaries because the compiler can help prevent unwanted dependencies.
MyApplication.sln
src/
MyApplication.Domain/
Entities/
ValueObjects/
Exceptions/
MyApplication.Application/
Interfaces/
Services/
DTOs/
Features/
MyApplication.Infrastructure/
Persistence/
Repositories/
ExternalServices/
MyApplication.API/
Controllers/
Middleware/
Extensions/
Program.cs
tests/
MyApplication.UnitTests/
MyApplication.IntegrationTests/
A typical dependency direction is API to Application, Infrastructure to Application, and Application to Domain. Domain should remain independent from the other projects. The exact project references may vary depending on the application’s requirements, but the core principle remains the same: infrastructure should not control the business core.
Clean Architecture and Dependency Injection
Dependency Injection is one of the most important techniques used with Clean Architecture in .NET. Instead of allowing a class to create its dependencies directly, the dependencies are supplied from outside. ASP.NET Core provides a built-in Dependency Injection container that supports constructor injection and different service lifetimes.
For example, the API project can register the Infrastructure implementation for an Application interface.
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IOrderService, OrderService>();
The Application service depends on IOrderRepository, while the Infrastructure project provides OrderRepository. This creates a flexible design where the application code does not need to know the concrete repository implementation.
If you want a deeper explanation of service registration, constructor injection, Singleton, Scoped, and Transient lifetimes, see the Dependency Injection in ASP.NET Core guide.
Clean Architecture and Unit Testing
One of the biggest practical benefits of Clean Architecture is improved testability. When business logic is independent of databases and external services, unit tests can execute without starting a real database or calling an external API.
For example, suppose an OrderService depends on IOrderRepository. A unit test can provide a fake or mock implementation of the interface. The test can then focus only on the business behavior.
public class OrderService
{
private readonly IOrderRepository _repository;
public OrderService(IOrderRepository repository)
{
_repository = repository;
}
public async Task<Order?> GetAsync(int id)
{
return await _repository.GetByIdAsync(id);
}
}
This separation also makes integration testing more focused. Infrastructure tests can verify database behavior separately, while Application tests can verify business rules without depending on infrastructure.
Clean Architecture and the Repository Pattern
The Repository Pattern is commonly used when implementing Clean Architecture, although it should not be added automatically to every project. A repository can provide an abstraction over persistence operations, allowing application code to work with a business-oriented contract instead of database-specific APIs.
The important consideration is to avoid creating repositories that simply duplicate every Entity Framework Core method without providing meaningful abstraction. Clean Architecture should reduce unnecessary coupling, not create additional layers only for the sake of having more projects or interfaces.
Clean Architecture with Background Processing
Clean Architecture can also be used with background processing. For example, a Background Service can trigger an application use case that processes pending orders, sends notifications, generates reports, or synchronizes external data. The worker should coordinate the operation rather than contain all business logic itself.
This approach keeps background processing concerns separate from business rules. For practical information about hosted workers, dependency injection scopes, cancellation tokens, and production practices, see the Background Services in .NET guide.
Managing Configuration in Clean Architecture
Configuration is another area where clear boundaries are important. Connection strings, API endpoints, feature flags, and environment-specific settings should not be hardcoded inside business classes. Configuration can be managed by the application startup and infrastructure registration while the core application receives only the values or abstractions it actually needs.
For ASP.NET Core configuration concepts such as appsettings.json, environment-specific configuration, and strongly typed settings, see the appsettings.json in ASP.NET Core guide.
Common Clean Architecture Mistakes
Clean Architecture can become unnecessarily complicated when developers follow diagrams mechanically instead of understanding the underlying principles. The goal is not to create the maximum number of projects or interfaces. The goal is to create useful boundaries around business logic and external dependencies.
- Do not put business rules directly inside controllers.
- Do not make the Domain layer depend on Entity Framework Core.
- Do not place SQL queries inside API controllers.
- Do not expose infrastructure implementation details throughout the application.
- Do not create interfaces for every class without a meaningful reason.
- Do not use a repository abstraction merely to wrap every database method.
- Do not allow configuration and external services to leak into the Domain layer.
- Do not create excessive layers that make simple features harder to implement.
Clean Architecture vs Traditional Layered Architecture
Traditional layered architecture commonly follows a top-to-bottom dependency model such as Presentation, Business Logic, and Data Access. This can work well for many applications, but the business layer may eventually become dependent on database implementation details.
Clean Architecture changes the dependency direction by placing abstractions closer to the business core. Infrastructure implements those abstractions. This makes the business logic less dependent on technology choices and generally easier to test.
| Traditional Layered Architecture | Clean Architecture |
|---|---|
| Dependencies usually move downward | Dependencies point toward the core |
| Business layer may depend on data access | Infrastructure depends on application abstractions |
| Testing may require infrastructure | Core logic can be tested independently |
| Technology can influence business code | Business rules remain more technology-independent |
When Should You Use Clean Architecture?
Clean Architecture is particularly useful for medium and large applications where business rules are expected to evolve over time. Enterprise APIs, financial systems, e-commerce platforms, workflow applications, SaaS products, and systems with multiple external integrations can benefit significantly from clear architectural boundaries.
For a small CRUD application with only a few screens and limited business logic, a full Clean Architecture implementation may introduce unnecessary complexity. In such cases, a simpler structure can be more productive. Architecture should match the complexity and expected lifetime of the software.
Best Practices for Clean Architecture in .NET
- Keep the Domain layer independent from infrastructure technologies.
- Use interfaces when they provide meaningful boundaries.
- Prefer constructor injection for required dependencies.
- Keep controllers thin and focused on HTTP concerns.
- Keep business rules in Domain or Application services rather than controllers.
- Use DTOs to control data crossing application boundaries.
- Keep database-specific implementation inside Infrastructure.
- Write unit tests around business rules and application use cases.
- Use integration tests for database and external-service behavior.
- Keep project dependencies intentional and easy to understand.
- Avoid unnecessary abstractions and excessive architectural complexity.
Official .NET Guidance
Microsoft provides detailed guidance on common web application architectures, dependency inversion, Application Core, Infrastructure, UI projects, and testing. Developers building enterprise ASP.NET Core applications can use this documentation as a useful reference when designing their own architecture.
Read Microsoft’s official Clean Architecture and web application architecture guidance.
Conclusion
Clean Architecture in .NET is primarily about controlling dependencies and protecting business logic from infrastructure details. By separating Domain, Application, Infrastructure, and Presentation responsibilities, developers can create applications that are easier to understand, test, maintain, and evolve. ASP.NET Core’s built-in Dependency Injection support makes this architectural style practical for modern .NET applications. However, Clean Architecture should not be treated as a fixed template that must be applied identically to every project. The best implementation is one that creates useful boundaries without introducing unnecessary complexity. For enterprise applications with significant business rules, multiple integrations, long development lifecycles, and several development teams, Clean Architecture can provide a strong foundation for sustainable software development.