.NET Development Skill
Execute this process for any .NET implementation task:
- Analysis Phase (Required)
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
- Architecture Review (Required)
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
- Implementation
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
- Testing (Required)
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);
}
Core Principles
Domain-Driven Design
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
SOLID Principles
-
SRP: One reason to change per class
-
OCP: Open for extension, closed for modification
-
LSP: Subtypes substitutable for base types
-
ISP: No forced dependency on unused methods
-
DIP: Depend on abstractions
C# Conventions
Naming:
-
PascalCase: Types, methods, public members, properties
-
camelCase: Private fields, local variables
-
Prefix interfaces with I (e.g., IUserService )
Formatting:
-
File-scoped namespaces
-
Newline before opening braces
-
Pattern matching and switch expressions preferred
-
Use nameof over string literals
Nullability:
-
Enable nullable reference types
-
Use is null / is not null (not == null )
-
Validate at entry points, trust annotations internally
Layer Responsibilities
Examples ordered by complexity (Easy → Medium → Hard):
// 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));
}
}
// 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);
}
}
// 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();
}
}
REST API Patterns
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);
[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); } }
Complexity Limits
-
Maximum 50 lines per method (excluding blank lines and comments)
-
Cyclomatic complexity <10 per method
-
Maximum 7 dependencies per class (constructor parameters)
-
Aggregate size <1MB serialized
-
Collection properties limited to 1000 items maximum
Code Quality Standards
-
All public APIs must have XML documentation
-
No compiler warnings in production code
-
Nullable reference types enabled and enforced
-
No magic strings - use constants or nameof()
<security_constraints>
Input Validation
-
Validate all external input at application layer boundaries
-
Use FluentValidation or Data Annotations for request validation
-
Sanitize string inputs to prevent injection attacks
-
Validate GUIDs and IDs before database queries
Data Protection
-
Never log sensitive data (passwords, tokens, PII)
-
Use parameterized queries exclusively (EF Core handles this)
-
Implement proper authorization checks before data access
-
Hash passwords using BCrypt or Argon2, never store plaintext
API Security
-
Require authentication on all endpoints except explicitly public ones
-
Implement rate limiting on public endpoints
-
Validate JWT tokens with proper issuer and audience checks
-
Use HTTPS exclusively in production </security_constraints>
<success_criteria>
Code Quality Gates
-
All unit tests pass (0 failures)
-
Domain layer test coverage >= 90%
-
Application layer test coverage >= 85%
-
No SonarQube blocker or critical issues
-
No security vulnerabilities in dependency scan
Architectural Compliance
-
Domain layer has zero infrastructure dependencies
-
All aggregate modifications go through aggregate root methods
-
No business logic in controllers or infrastructure layer
-
All async operations use CancellationToken
Review Checklist
-
SOLID principles applied correctly
-
Aggregate boundaries maintain consistency
-
Domain events capture all significant state changes
-
Error handling follows ProblemDetails (RFC 7807)
-
Tests follow MethodName_Condition_ExpectedResult naming </success_criteria>
Reference Documentation
For detailed patterns and checklists, see:
-
DDD Patterns: Aggregate design, domain events, specifications
-
API Patterns: Validation, error handling, versioning, documentation
-
Testing Patterns: Test categories, mocking strategies, coverage requirements
Quick Reference
Monetary Values
-
Use decimal for all financial calculations
-
Implement currency-aware value objects
-
Handle rounding per financial standards
-
Maintain precision through calculation chains
Error Handling
// 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);
}));
Dependency Injection Setup
// Program.cs builder.Services.AddScoped<IOrderRepository, OrderRepository>(); builder.Services.AddScoped<OrderService>(); builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));