| name | stack-dotnet |
| description | ASP.NET Core + C# backend — minimal APIs, dependency injection, EF Core, nullable reference types |
| license | Apache-2.0 |
| metadata | {"author":"manuel-retamozo-garcia","version":"1.0"} |
| capabilities | ["dotnet"] |
Critical Rules
- Any executor sub-agent using this skill must first inspect the workspace structure to auto-detect the project's flavor.
- If MVC controllers are present (e.g.,
Controllers/ directory or controller classes), it must use controllers and traditional WebAPI routing.
- If routes are mapped inline in
Program.cs without controllers, it must follow the Minimal APIs pattern.
- If multiple projects/directories exist representing Clean Architecture/DDD (e.g. Domain, Application, Infrastructure layers), it must respect the layered separation and domain patterns.
- Always enable and enforce nullable reference types (
<Nullable>enable</Nullable>) to prevent null reference exceptions.
Core Principles
1. Prefer Immutability
Use records and init-only properties for data models. Mutability should be an explicit, justified choice.
public sealed record Money(decimal Amount, string Currency);
public sealed class CreateOrderRequest
{
public required string CustomerId { get; init; }
public required IReadOnlyList<OrderItem> Items { get; init; }
}
public class Order
{
public string CustomerId { get; set; }
public List<OrderItem> Items { get; set; }
}
2. Explicit Over Implicit
Be clear about nullability, access modifiers, and intent.
public sealed class UserService
{
private readonly IUserRepository _repository;
private readonly ILogger<UserService> _logger;
public UserService(IUserRepository repository, ILogger<UserService> logger)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<User?> FindByIdAsync(Guid id, CancellationToken cancellationToken)
{
return await _repository.FindByIdAsync(id, cancellationToken);
}
}
3. Depend on Abstractions
Use interfaces for service boundaries. Register via DI container.
public interface IOrderRepository
{
Task<Order?> FindByIdAsync(Guid id, CancellationToken cancellationToken);
Task<IReadOnlyList<Order>> FindByCustomerAsync(string customerId, CancellationToken cancellationToken);
Task AddAsync(Order order, CancellationToken cancellationToken);
}
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
Async/Await Patterns
Proper Async Usage
public async Task<OrderSummary> GetOrderSummaryAsync(
Guid orderId,
CancellationToken cancellationToken)
{
var order = await _repository.FindByIdAsync(orderId, cancellationToken)
?? throw new NotFoundException($"Order {orderId} not found");
var customer = await _customerService.GetAsync(order.CustomerId, cancellationToken);
return new OrderSummary(order, customer);
}