Comprehensive .NET development guidelines covering DDD, SOLID principles, ASP.NET Core REST APIs, and C# best practices. Use when working with: (1) C# code files (.cs), (2) .NET project files (.csproj, .sln), (3) ASP.NET Core applications, (4) Domain-Driven Design implementations, (5) REST API development, (6) Entity Framework Core, (7) Unit/integration testing in .NET. Specifically triggers for: classes inheriting AggregateRoot/Entity, services with IRepository dependencies, controllers inheriting ControllerBase, or files in Domain/Application/Infrastructure folders.
Limited to specific tools
Additional assets for this skill
This skill is limited to using the following tools:
Execute this process for any .NET implementation task:
Before writing code, perform these steps:
1.1. Identify domain concepts: List all aggregates, entities, and value objects involved in this change 1.2. Determine affected layer: Specify whether changes target Domain, Application, or Infrastructure 1.3. Map SOLID principles: Document which principles apply and how they guide the design 1.4. Assess security requirements: Identify authorization rules and data protection needs
Verify the approach against these criteria:
2.1. Check aggregate boundaries: Confirm they preserve transactional consistency 2.2. Apply Single Responsibility: Ensure each class has exactly one reason to change 2.3. Enforce Dependency Inversion: Verify dependencies point inward (Infrastructure → Application → Domain) 2.4. Validate domain encapsulation: Confirm business logic resides in domain objects, not services
Execute with these standards:
3.1. Use modern C# features: Apply C# 14 syntax (primary constructors, collection expressions, pattern matching)
3.2. Implement async correctly: Use async/await for all I/O operations, propagate CancellationToken
3.3. Apply constructor injection: Inject all dependencies via primary constructors
3.4. Validate at boundaries: Check inputs at application layer entry points, trust internal calls
3.5. Encapsulate business rules: Place all domain logic in aggregate methods, not services
Write tests following these guidelines:
4.1. Apply naming convention: Use MethodName_Condition_ExpectedResult pattern
4.2. Structure with AAA: Organize tests into Arrange, Act, Assert sections
4.3. Test domain invariants: Cover all business rules with unit tests
4.4. Verify events: Assert that correct domain events are raised
[Fact]
public void CalculateTotal_WithDiscount_ReturnsReducedAmount()
{
// Arrange
var order = new Order();
order.ApplyDiscount(0.1m);
// Act
var total = order.CalculateTotal();
// Assert
Assert.Equal(90m, total);
}
</workflow>
| Concept | Purpose |
|---|---|
| Ubiquitous Language | Consistent business terminology across code |
| Bounded Contexts | Clear service boundaries |
| Aggregates | Transactional consistency boundaries |
| Domain Events | Capture business-significant occurrences |
| Rich Domain Models | Business logic in domain, not services |
Naming:
I (e.g., IUserService)Formatting:
nameof over string literalsNullability:
is null / is not null (not == null)Examples ordered by complexity (Easy → Medium → Hard):
<example name="domain-layer" complexity="easy"> ### Domain Layer// Aggregate root with encapsulated business logic
public class Order : AggregateRoot
{
private readonly List<OrderLine> _lines = [];
public IReadOnlyCollection<OrderLine> Lines => _lines.AsReadOnly();
public void AddLine(Product product, int quantity)
{
if (quantity <= 0)
throw new DomainException("Quantity must be positive");
_lines.Add(new OrderLine(product, quantity));
AddDomainEvent(new OrderLineAddedEvent(Id, product.Id, quantity));
}
}
</example>
<example name="infrastructure-layer" complexity="medium">
### Infrastructure Layer
// Repository implementation with EF Core
public class OrderRepository(AppDbContext db) : IOrderRepository
{
public async Task<Order?> GetByIdAsync(Guid id, CancellationToken ct) =>
await db.Orders
.Include(o => o.Lines)
.FirstOrDefaultAsync(o => o.Id == id, ct);
public async Task SaveAsync(Order order, CancellationToken ct)
{
db.Orders.Update(order);
await db.SaveChangesAsync(ct);
}
}
</example>
<example name="application-layer" complexity="hard">
### Application Layer
// Application service orchestrates domain operations
public class OrderService(
IOrderRepository orders,
IProductRepository products,
IEventPublisher events)
{
public async Task<OrderDto> AddLineAsync(
Guid orderId,
AddLineCommand command,
CancellationToken ct = default)
{
// Validate input at boundary
ArgumentNullException.ThrowIfNull(command);
var order = await orders.GetByIdAsync(orderId, ct)
?? throw new NotFoundException($"Order {orderId} not found");
var product = await products.GetByIdAsync(command.ProductId, ct)
?? throw new NotFoundException($"Product {command.ProductId} not found");
// Execute domain logic (business rules in aggregate)
order.AddLine(product, command.Quantity);
// Persist and publish events
await orders.SaveAsync(order, ct);
await events.PublishAsync(order.DomainEvents, ct);
return order.ToDto();
}
}
</example>
var orders = app.MapGroup("/api/orders")
.WithTags("Orders")
.RequireAuthorization();
orders.MapPost("/{orderId:guid}/lines", async (
Guid orderId,
AddLineCommand command,
OrderService service,
CancellationToken ct) =>
{
var result = await service.AddLineAsync(orderId, command, ct);
return Results.Ok(result);
})
.WithName("AddOrderLine")
.Produces<OrderDto>()
.ProducesProblem(StatusCodes.Status404NotFound);
</example>
<example name="controller-api" complexity="hard">
### Controller-Based API
[ApiController]
[Route("api/[controller]")]
public class OrdersController(OrderService orderService) : ControllerBase
{
/// <summary>
/// Adds a line item to an existing order.
/// </summary>
[HttpPost("{orderId:guid}/lines")]
[ProducesResponseType<OrderDto>(StatusCodes.Status200OK)]
[ProducesResponseType<ProblemDetails>(StatusCodes.Status404NotFound)]
public async Task<IActionResult> AddLine(
Guid orderId,
AddLineCommand command,
CancellationToken ct)
{
var result = await orderService.AddLineAsync(orderId, command, ct);
return Ok(result);
}
}
</example>
<constraints>
## Performance Constraints
- Domain operations: <100ms execution time
- Repository calls: <500ms including database round-trip
- API response time: <200ms for simple queries, <1000ms for complex aggregations
<security_constraints>
<success_criteria>
For detailed patterns and checklists, see:
decimal for all financial calculations// Global exception handler middleware
app.UseExceptionHandler(error => error.Run(async context =>
{
var exception = context.Features.Get<IExceptionHandlerFeature>()?.Error;
var problem = exception switch
{
NotFoundException e => new ProblemDetails
{
Status = 404,
Title = "Not Found",
Detail = e.Message
},
DomainException e => new ProblemDetails
{
Status = 400,
Title = "Business Rule Violation",
Detail = e.Message
},
_ => new ProblemDetails
{
Status = 500,
Title = "Internal Server Error"
}
};
context.Response.StatusCode = problem.Status ?? 500;
await context.Response.WriteAsJsonAsync(problem);
}));
// Program.cs
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<OrderService>();
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));