From coding-skills
Use when designing or reviewing .NET Core project architecture, layer responsibilities, dependency direction, type boundaries, error handling strategy, or placement of cross-cutting concerns such as middleware and action filters.
How this skill is triggered — by the user, by Claude, or both
Slash command
/coding-skills:dotnet-architectureThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
```
Controller
↓ depends on
Application Service
↓ depends on
Domain Service ←→ Repository / Proxy
Dependencies flow inward only. Inner layers must not reference outer layers. Interfaces are defined in inner layers, implemented in outer layers (Infrastructure).
Two valid dependency patterns:
| Scenario | Allowed dependencies |
|---|---|
| Simple CRUD / query | Application Service → Repository or Proxy directly |
| Domain event handling | Application Service → Domain Service(s) only |
| Layer boundary | Input type | Output type |
|---|---|---|
| HTTP → Controller | HTTP request | — |
| Controller → Application Service | DTO | — |
| Application Service → caller (Controller) | — | Domain model (domain event) or DTO (query/simple op) |
| Controller → HTTP response | — | ViewModel (converted from domain model) |
| Domain Service → Application Service | Domain model | Domain model |
| Repository → any caller | — | Aggregate |
| Proxy → any caller | — | DTO |
Controller → IApplicationService
ApplicationService → IRepository | IProxy (simple CRUD/query)
ApplicationService → IDomainService (domain event)
DomainService → IRepository | IProxy
Use Action Filters for concerns that apply before a controller action executes and are specific to HTTP/API context:
public class RequireFeatureFlagFilter : IActionFilter {
public void OnActionExecuting(ActionExecutingContext context) {
if (!_featureFlags.IsEnabled("NewCheckout"))
context.Result = new StatusCodeResult(503);
}
public void OnActionExecuted(ActionExecutedContext context) { }
}
Use Middleware for concerns that span the entire request pipeline:
Each layer is responsible only for throwing the appropriate exception. Catching and converting to HTTP responses is handled by a single global exception-handling middleware.
| Exception | Thrown by | HTTP status |
|---|---|---|
NotFoundException | Repository, Domain Service | 404 |
ValidationException | Domain Service, Application Service | 422 |
UnauthorizedException | Domain Service | 403 |
Unhandled Exception | anywhere | 500 |
public class ExceptionHandlingMiddleware {
public async Task InvokeAsync(HttpContext context, RequestDelegate next) {
try {
await next(context);
}
catch (NotFoundException ex) {
context.Response.StatusCode = 404;
await context.Response.WriteAsJsonAsync(new { error = ex.Message });
}
catch (ValidationException ex) {
context.Response.StatusCode = 422;
await context.Response.WriteAsJsonAsync(new { error = ex.Message });
}
catch (Exception ex) {
context.Response.StatusCode = 500;
await context.Response.WriteAsJsonAsync(new { error = "Unexpected error." });
}
}
}
Layers throw — middleware catches. No try/catch blocks inside business logic.
// Controller
[HttpGet("{id}")]
public async Task<OrderResponse> GetOrder(Guid id) {
var dto = await _orderAppService.GetOrder(id);
return OrderResponse.From(dto); // ViewModel converted from DTO
}
// Application Service (simple query — depends on Repository directly)
public async Task<OrderDto> GetOrder(Guid id) {
var aggregate = await _orderRepo.Get(id); // Repository returns Aggregate
return aggregate.ToDto();
}
// Controller
[HttpPost]
public async Task<OrderResponse> PlaceOrder(PlaceOrderRequest request) {
var dto = request.ToDto();
var order = await _orderAppService.PlaceOrder(dto); // returns domain model
return OrderResponse.From(order); // ViewModel from domain model
}
// Application Service (domain event — depends on Domain Service only)
public async Task<Order> PlaceOrder(PlaceOrderDto dto) {
return await _orderDomainService.PlaceOrder(dto);
}
// Domain Service (business rules, depends on Repository interface)
public async Task<Order> PlaceOrder(PlaceOrderDto dto) {
var user = await _userRepo.Get(dto.UserId);
var products = await _productRepo.GetMany(dto.ProductIds);
var order = Order.For(user).WithProducts(products).Confirm();
await _orderRepo.Save(order);
return order;
}
| Symptom | Fix |
|---|---|
| Controller calls Repository directly | Route through Application Service |
Application Service contains if on domain state | Move logic to Domain Service |
| Application Service for domain event calls Repository directly | Route through Domain Service |
| Entity returned from Repository | Wrap in Aggregate |
| Entity exposed at Controller boundary | Convert to ViewModel first |
| ViewModel constructed from raw fields in Service | Move conversion to domain model To...() |
| Business logic in Controller | Move to Domain Service |
| Try/catch in a Service or Domain class | Remove — let middleware handle it |
| API pre-condition logic inside Controller action | Move to Action Filter |
| Infrastructure concern (logging, CORS) in Controller | Move to Middleware |
| Inner layer imports outer layer namespace | Invert dependency — define interface in inner layer |
npx claudepluginhub codemachine0121/dotnet-developing-skills --plugin coding-skillsScaffolds a complete .NET Clean Architecture solution with domain, application, infrastructure, and API layers. Creates project structure, dependency injection, and cross-cutting concerns.
Guides .NET Clean Architecture implementation: 4-project layout (Domain, Application, Infrastructure, Api), dependency inversion, use case handlers, domain entities with behavior, infrastructure as plugin.
Applies .NET 8 architecture recipes for backend services: clean architecture layering, CQRS with MediatR, minimal APIs, Entity Framework Core, JWT authentication, AOT compilation, cloud-native patterns.