.NET ASP.NET Core Agent Rules
Project Context
You are building .NET 8 ASP.NET Core applications with C# 12. Use minimal APIs for lean endpoints, EF Core for data access, and the built-in DI container. Follow Clean Architecture layering: API, Application, Domain, and Infrastructure projects.
Code Style
- Use primary constructors throughout: `class OrdersController(IOrderService service, ILogger<OrdersController> logger)`.
- Use records for DTOs: `record CreateOrderRequest([Required] string ProductId, [Range(1, 999)] int Quantity)`.
- Use file-scoped namespaces; one type per file.
- Enable nullable reference types and treat warnings as errors: `<Nullable>enable</Nullable>` and `<TreatWarningsAsErrors>true</TreatWarningsAsErrors>`.
- Use `sealed` on classes not designed for inheritance; use `init`-only properties on immutable types.
Minimal APIs
- Group related endpoints with `MapGroup`: `var orders = app.MapGroup("/api/v1/orders").RequireAuthorization()`.
- Use `TypedResults` for compile-time return type safety: `Results<Ok<OrderResponse>, NotFound, BadRequest<ProblemDetails>>`.
- Apply filters with `AddEndpointFilter<ValidationFilter<TRequest>>()` for shared validation logic across groups.
- Use `IEndpointRouteBuilder` extension methods to organize endpoint registrations by feature.
- Apply `WithName`, `WithSummary`, and `WithOpenApi` for Scalar/Swagger documentation without controller attributes.
Entity Framework Core
- Use code-first migrations named descriptively: `AddOrderStatusColumn`, not `Migration20241201`.
- Configure entity mappings in `IEntityTypeConfiguration<T>` classes: `class OrderConfiguration : IEntityTypeConfiguration<Order>`.
- Use `AsNoTracking()` on all read-only queries; EF tracks changes unnecessarily for projection-only reads.
- Use compiled queries via `EF.CompileAsyncQuery` for frequently executed queries with fixed structure.
- Never expose `IQueryable` outside the data layer — return `IReadOnlyList<T>` or projections.
- Add `DbContext` migrations to CI: `dotnet ef migrations has-pending-model-changes` fails the build on model/migration drift.
Dependency Injection
- Register services in static extension methods: `services.AddOrderingServices()` in `OrderingServiceExtensions.cs`.
- Use `IOptions<T>` for read-once configuration, `IOptionsSnapshot<T>` for per-request, `IOptionsMonitor<T>` for live-reloading config.
- Validate options at startup: `services.AddOptions<PaymentOptions>().BindConfiguration("Payment").ValidateDataAnnotations().ValidateOnStart()`.
- Use keyed services (`services.AddKeyedSingleton<IPaymentGateway, StripeGateway>("stripe")`) to disambiguate multiple implementations.
Middleware & Filters
- Use `app.UseExceptionHandler("/error")` in production to produce `ProblemDetails` for all unhandled exceptions.
- Configure rate limiting with `AddRateLimiter` and apply per route group: `app.MapGroup("/api").RequireRateLimiting("fixed")`.
- Use `OutputCache` (ASP.NET Core 7+) for GET endpoints that return stable data: `.CacheOutput(p => p.Expire(TimeSpan.FromMinutes(5)))`.
- Add `CorrelationId` middleware early in the pipeline to attach a `X-Correlation-ID` header to all requests and log responses.
Authentication & Authorization
- Use `AddAuthentication().AddJwtBearer(...)` for API authentication; configure issuer, audience, and signing key from `IOptions`.
- Define authorization policies: `services.AddAuthorization(opt => opt.AddPolicy("CanManageOrders", p => p.RequireRole("Admin", "Manager")))`.
- Apply policies with `RequireAuthorization("CanManageOrders")` on route groups or individual endpoints.
- Use `ICurrentUserService` (a custom interface) to decouple business logic from `HttpContext.User` claims.
Error Handling
- Install `app.UseExceptionHandler` configured with a `IProblemDetailsService` handler to produce RFC 9457 `ProblemDetails` responses.
- Map domain exceptions to HTTP status codes in one `ExceptionToProblemDetailsHandler` rather than in each endpoint.
- Use `Result<T>` or `OneOf<TSuccess, TFailure>` in the Application layer to express expected failures without exceptions.
- Log with structured Serilog sinks: `Log.ForContext<OrderService>().Error(ex, "Failed to create order for {CustomerId}", customerId)`.
Configuration
- Store environment-specific settings in `appsettings.{Environment}.json`; never commit connection strings or API keys.
- Use `dotnet user-secrets` in development; use Azure Key Vault, AWS Secrets Manager, or environment variables in production.
- Validate all required configuration at startup — fail fast with a descriptive message rather than a null ref at runtime.
Testing
- Use `WebApplicationFactory<Program>` with `CustomWebApplicationFactory` to replace infrastructure dependencies with fakes.
- Use Testcontainers (`Testcontainers.PostgreSql`) for database integration tests.
- Use `FluentAssertions` for readable assertions; use `AutoFixture` for generating test data.
- Use `NSubstitute` for mocking: `var service = Substitute.For<IOrderService>()`.
- Write tests named with the `MethodName_Scenario_ExpectedBehavior` convention for discoverability.