| name | dotnet-clean-architecture |
| description | Implements Clean Architecture in .NET / C# projects following SOLID principles, CQRS with MediatR, repository pattern, dependency injection, and layered solution structure. Use when creating new .NET or ASP.NET Core projects, adding features, refactoring existing solutions, designing domain models, writing Commands, Queries, Handlers, or asked about project structure, layers, DI, or architecture in C#.
|
| license | MIT |
| metadata | {"author":"iamBrzDev","version":"1.0"} |
When to activate
- Scaffolding a new .NET / ASP.NET Core project
- "How do I structure this feature?" in a C# context
- Writing a Command, Query, Handler, or Domain Event
- Designing entities, aggregates, or value objects
- Configuring dependency injection or DbContext
- Refactoring a "fat controller" or a service with too many responsibilities
- Any mention of MediatR, FluentValidation, EF Core architecture
Solution Structure
Always enforce this 4-layer structure. Dependencies flow inward only.
src/
├── YourApp.Domain/ # Zero external dependencies
│ ├── Entities/
│ ├── ValueObjects/
│ ├── Events/
│ └── Interfaces/
│
├── YourApp.Application/ # Depends only on Domain
│ ├── Features/
│ │ └── Orders/
│ │ ├── Commands/CreateOrder/
│ │ │ ├── CreateOrderCommand.cs
│ │ │ ├── CreateOrderCommandHandler.cs
│ │ │ ├── CreateOrderCommandValidator.cs
│ │ │ └── CreateOrderResponse.cs
│ │ └── Queries/GetOrderById/
│ │ ├── GetOrderByIdQuery.cs
│ │ └── GetOrderByIdQueryHandler.cs
│ ├── Common/
│ └── DependencyInjection.cs
│
├── YourApp.Infrastructure/ # Depends on Application + Domain
│ ├── Persistence/
│ ├── Services/
│ └── DependencyInjection.cs
│
└── YourApp.API/ # Depends on Application + Infrastructure
├── Controllers/
├── Middleware/
└── Program.cs
Non-negotiable Rules
- Domain has zero external dependencies. No EF Core, no MediatR, no HTTP.
- Application depends only on Domain. Never reference Infrastructure from Application.
- One Handler per Command/Query. No shared handlers.
- Controllers are thin. Receive → mediator → return. Nothing else.
- IRepository in Domain. Implementation in Infrastructure.
- No business logic in Controllers or Infrastructure.
CQRS Pattern
public record CreateOrderCommand(int CustomerId, List<OrderItemDto> Items)
: IRequest<CreateOrderResponse>;
public class CreateOrderCommandHandler
: IRequestHandler<CreateOrderCommand, CreateOrderResponse>
{
private readonly IOrderRepository _repository;
private readonly IUnitOfWork _unitOfWork;
public CreateOrderCommandHandler(
IOrderRepository repository, IUnitOfWork unitOfWork)
{
_repository = repository;
_unitOfWork = unitOfWork;
}
public async Task<CreateOrderResponse> Handle(
CreateOrderCommand request, CancellationToken cancellationToken)
{
var order = Order.Create(request.CustomerId, request.Items);
await _repository.AddAsync(order, cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
return new CreateOrderResponse(order.Id);
}
}
public class CreateOrderCommandValidator : AbstractValidator<CreateOrderCommand>
{
public CreateOrderCommandValidator()
{
RuleFor(x => x.CustomerId).GreaterThan(0);
RuleFor(x => x.Items).NotEmpty();
}
}
Naming Conventions
| Type | Convention | Example |
|---|
| Commands | {Action}{Entity}Command | CreateOrderCommand |
| Queries | Get{Entity}{Qualifier}Query | GetOrderByIdQuery |
| Handlers | Command/Query name + Handler | CreateOrderCommandHandler |
| Repo interface | I{Entity}Repository | IOrderRepository |
| Domain Events | {Entity}{Action}DomainEvent | OrderCreatedDomainEvent |
Dependency Injection — Register by Layer
public static IServiceCollection AddApplication(this IServiceCollection services)
{
services.AddMediatR(cfg =>
cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
services.AddTransient(typeof(IPipelineBehavior<,>),
typeof(ValidationPipelineBehavior<,>));
return services;
}
public static IServiceCollection AddInfrastructure(
this IServiceCollection services, IConfiguration configuration)
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("Default")));
services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
return services;
}
Common mistakes to avoid
- ❌ Injecting
DbContext directly into Application handlers
- ❌ Business logic inside Controllers
- ❌ Returning Domain entities from API endpoints — always map to DTOs
- ❌
new keyword for dependencies inside handlers — always inject
Reference files
references/cqrs-patterns.md — Pipeline behaviors, Domain Events, Result pattern
references/project-structure.md — Full scaffold with all files and .csproj dependencies