---
name: csharp-rules
description: "C#/.NET coding rules: style, patterns, security, testing. Triggers: .cs, .csproj, .sln, ASP.NET, ASP.NET Core, EF Core, LINQ, NUnit, xUnit, dotnet."
effort: medium
user-invocable: false
allowed-tools: Read
---

# C#/.NET Rules

These rules come from `app/rules/csharp/` in ai-toolkit. They cover
the project's standards for coding style, frameworks, patterns,
security, and testing in C#/.NET. Apply them when writing or
reviewing C#/.NET code.

# C# Coding Style

## Naming
- PascalCase: classes, structs, enums, interfaces, methods, properties, events.
- camelCase: local variables, parameters, private fields.
- Prefix interfaces with `I`: `IUserRepository`, `IDisposable`.
- Prefix private fields with `_`: `private readonly ILogger _logger;`.
- UPPER_SNAKE: not conventional in C#. Use PascalCase for constants.

## Nullable Reference Types
- Enable `<Nullable>enable</Nullable>` in all projects.
- Use `string?` only when null is semantically meaningful.
- Use `!` (null-forgiving) operator sparingly -- only when compiler cannot infer.
- Use `??` (null-coalescing) and `?.` (null-conditional) for safe navigation.
- Use `required` modifier (C# 11) on properties that must be set at initialization.

## Records and Types
- Use `record` for immutable value objects and DTOs.
- Use `record struct` for small, stack-allocated value types.
- Use `init` properties for immutable-after-construction objects.
- Use `with` expressions for non-destructive mutation of records.
- Use primary constructors (C# 12) for concise class definitions.

## Pattern Matching
- Use `is` pattern for type checks: `if (obj is string s)`.
- Use `switch` expressions for exhaustive matching over enums/types.
- Use property patterns: `user is { Age: > 18, Role: "admin" }`.
- Use relational patterns: `size is > 0 and < 100`.
- Use list patterns (C# 11): `numbers is [1, 2, .., var last]`.

## Async/Await
- Suffix async methods with `Async`: `GetUserAsync()`.
- Return `Task<T>` or `ValueTask<T>`, never `void` (except event handlers).
- Use `await` with `ConfigureAwait(false)` in library code.
- Use `CancellationToken` parameters in all async public APIs.
- Prefer `ValueTask<T>` when synchronous completion is common.

## File Organization
- One type per file. File name matches type name.
- Use file-scoped namespaces (C# 10): `namespace MyApp.Services;`.
- Order members: fields, constructors, properties, public methods, private methods.
- Use `global using` directives in a single `GlobalUsings.cs` file.

## Formatting
- Use `.editorconfig` with C# style rules committed to the repository.
- Use `dotnet format` for automated formatting.
- Use Roslyn analyzers for compile-time style enforcement.
- Max line length: 120 characters.

# C# Frameworks

## ASP.NET Core
- Use minimal APIs for simple endpoints. Use controllers for complex APIs.
- Use `[ApiController]` attribute for automatic model validation and error responses.
- Use `Results.Ok()`, `Results.NotFound()` for typed HTTP results.
- Use endpoint filters / middleware for cross-cutting concerns.
- Use `IHostedService` / `BackgroundService` for long-running background tasks.
- Map routes with `app.MapGet()`, `app.MapPost()` for minimal API style.

## Entity Framework Core
- Use code-first migrations: `dotnet ef migrations add`, `dotnet ef database update`.
- Use `DbContext` with scoped lifetime (one per request).
- Use `AsNoTracking()` for read-only queries. Use `AsTracking()` only for updates.
- Use `Include()` / `ThenInclude()` for eager loading related entities.
- Use shadow properties for audit fields (`CreatedAt`, `UpdatedAt`).
- Use `HasQueryFilter()` for soft-delete and multi-tenancy global filters.

## Blazor
- Use Blazor Server for internal tools. Use Blazor WASM for public-facing SPAs.
- Use `@inject` for dependency injection in components.
- Use `EventCallback<T>` for parent-child component communication.
- Use `CascadingValue` for deeply shared state (theme, auth).
- Use `StateContainer` pattern with events for cross-component state management.

## SignalR
- Use strongly-typed hubs: `Hub<IClientMethods>` for compile-time safety.
- Use `HubContext<T>` for sending messages from outside hubs.
- Use groups for targeted broadcasting: `Groups.AddToGroupAsync()`.
- Configure automatic reconnection on the client side.

## MassTransit / Messaging
- Use MassTransit for message bus abstraction over RabbitMQ/Azure Service Bus.
- Define messages as `record` types for immutability.
- Use consumers (`IConsumer<T>`) for message handling.
- Use sagas for long-running, multi-step workflows with state.
- Use retry and circuit breaker policies for transient failures.

## Logging
- Use `ILogger<T>` via DI. Never instantiate loggers manually.
- Use structured logging: `_logger.LogInformation("User {UserId} logged in", userId)`.
- Use Serilog with sinks for structured, centralized logging.
- Use log scopes for request correlation: `using (_logger.BeginScope(...))`.

## Configuration
- Use `appsettings.json` + environment-specific overrides + environment variables.
- Bind configuration sections to strongly-typed classes with `IOptions<T>`.
- Use `IOptionsMonitor<T>` for configuration that changes at runtime.
- Validate configuration at startup with `ValidateDataAnnotations()`.

## Health Checks
- Use `app.MapHealthChecks("/health")` for liveness probes.
- Register custom health checks for database, cache, and external service dependencies.
- Use `AspNetCore.HealthChecks.*` NuGet packages for common checks.

# C# Patterns

## Error Handling
- Use exceptions for truly exceptional conditions. Use `Result<T>` pattern for expected failures.
- Create domain exception hierarchies: `class DomainException : Exception`.
- Use `when` clause in catch: `catch (HttpRequestException e) when (e.StatusCode == 404)`.
- Use `ExceptionDispatchInfo.Capture(e).Throw()` to preserve original stack trace.
- Return `Result<T, Error>` types for operations with expected failure modes.

## Async Patterns
- Use `Task.WhenAll()` for concurrent independent operations.
- Use `SemaphoreSlim` for async-compatible resource limiting.
- Use `Channel<T>` for async producer-consumer patterns.
- Use `IAsyncEnumerable<T>` for streaming data from async sources.
- Use `Polly` for retry, circuit breaker, and timeout policies.
- Never use `.Result` or `.Wait()` on tasks (deadlock risk). Always `await`.

## Dependency Injection
- Use constructor injection exclusively. Avoid service locator pattern.
- Register services in `Program.cs` or `IServiceCollection` extension methods.
- Use `Scoped` for request-lifetime services, `Singleton` for stateless, `Transient` for lightweight.
- Use `IOptions<T>` pattern for configuration injection.
- Validate DI registrations at startup with `ValidateOnBuild = true`.

## LINQ
- Use method syntax for complex queries. Use query syntax for joins.
- Use `FirstOrDefault()` over `First()` for safe access.
- Use `AsNoTracking()` for read-only EF Core queries (performance).
- Avoid materializing large collections: use `IQueryable<T>` until final projection.
- Use `Select()` to project only needed columns from database queries.

## Disposable Pattern
- Implement `IAsyncDisposable` for async cleanup.
- Use `await using var resource = ...;` for deterministic async disposal.
- Use `IDisposable` with `using` declaration (C# 8) for scope-based cleanup.
- Register disposable services in DI container (auto-disposed at scope end).

## Mediator / CQRS
- Use MediatR for command/query separation and pipeline behaviors.
- Commands: `IRequest<Result>` for mutations. Queries: `IRequest<T>` for reads.
- Use pipeline behaviors for cross-cutting: validation, logging, transactions.
- Keep handlers thin: delegate to domain services for business logic.

## Value Objects
- Use `record` types for value objects with structural equality.
- Use factory methods with validation: `public static Result<Email> Create(string value)`.
- Override `ToString()` for logging-friendly representations.
- Use implicit/explicit operators sparingly for primitive wrapper conversions.

## Anti-Patterns
- Service locator: inject dependencies, do not resolve from container.
- `async void`: use only for event handlers. Everything else returns `Task`.
- Nested `try-catch`: flatten with early returns or guard clauses.
- Anemic domain model: put behavior in domain objects, not only services.
- Over-abstracting: do not create interfaces for classes with only one implementation.

# C# Security

## Input Validation
- Use data annotations (`[Required]`, `[StringLength]`, `[Range]`) on request models.
- Use `[ApiController]` for automatic 400 responses on validation failure.
- Use FluentValidation for complex, rule-based validation logic.
- Never trust client-provided IDs. Verify resource ownership server-side.
- Sanitize HTML input with a library like HtmlSanitizer. Never render raw user HTML.

## SQL Injection
- Use EF Core parameterized queries exclusively. Never concatenate SQL.
- Use `FromSqlInterpolated()` over `FromSqlRaw()` for raw SQL (auto-parameterized).
- Use stored procedures via `context.Database.ExecuteSqlInterpolatedAsync()`.
- Audit all `FromSqlRaw()` calls for parameter interpolation risks.
- Use Dapper with parameterized queries: `@param` syntax in SQL strings.

## Authentication
- Use ASP.NET Core Identity for user management and password hashing.
- Use `AddAuthentication().AddJwtBearer()` for JWT-based API auth.
- Use short-lived access tokens (15 min) with refresh token rotation.
- Use `[Authorize]` attribute globally. Use `[AllowAnonymous]` selectively.
- Use HTTPS redirection: `app.UseHttpsRedirection()`.

## Authorization
- Use policy-based authorization: `[Authorize(Policy = "AdminOnly")]`.
- Use `IAuthorizationHandler` for custom authorization logic.
- Use resource-based authorization for object-level access control.
- Default deny: apply `[Authorize]` at controller/app level, opt out per endpoint.
- Check ownership in service layer, not just role membership.

## CSRF and XSS
- Use anti-forgery tokens for form-based submissions.
- Razor/Blazor auto-encodes output. Never use `@Html.Raw()` with user data.
- Set `Content-Security-Policy` headers to restrict script sources.
- Use `SameSite=Strict` on cookies for CSRF mitigation.
- Enable CORS only for specific origins. Never use `AllowAnyOrigin()` with credentials.

## Data Protection
- Use `IDataProtectionProvider` for symmetric encryption of sensitive data.
- Use `SecureString` or `ProtectedData` for in-memory sensitive data (limited use).
- Use ASP.NET Core Data Protection API for token and cookie encryption.
- Hash passwords with `PasswordHasher<T>` (PBKDF2 with salt).

## Secrets Management
- Use `dotnet user-secrets` for local development. Use Azure Key Vault for production.
- Use `IConfiguration` with environment variable providers. Never hardcode secrets.
- Use `[SensitiveData]` attributes to exclude fields from logging and serialization.
- Never log request headers containing Authorization or cookie values.

## Dependency Security
- Run `dotnet list package --vulnerable` to check for known CVEs.
- Use Dependabot or NuGetAudit for automated vulnerability scanning.
- Pin package versions explicitly. Avoid floating version ranges.
- Update `Microsoft.AspNetCore.*` packages promptly for security patches.

# C# Testing

## Framework
- Use xUnit as the primary test framework (modern, extensible).
- Use NSubstitute for mocking (clean syntax, no setup boilerplate).
- Use FluentAssertions for readable, expressive assertions.
- Use Testcontainers for integration tests with databases and services.

## File Naming
- Test files: `FooTests.cs` in a separate `*.Tests` project.
- Mirror source project namespace structure in test project.
- Integration tests: separate `*.IntegrationTests` project.
- Use `[Collection("Database")]` for shared fixtures across test classes.

## Structure
- Use `[Fact]` for single test cases. Use `[Theory]` for parameterized tests.
- Use `[InlineData]` or `[MemberData]` for test data in theories.
- Use constructor injection for per-test setup. Use `IClassFixture<T>` for shared setup.
- Name tests: `MethodName_Scenario_ExpectedResult`.

## Assertions (FluentAssertions)
- Use `result.Should().Be(expected)` for value assertions.
- Use `action.Should().Throw<InvalidOperationException>()` for exception testing.
- Use `collection.Should().ContainSingle(x => x.Id == 1)` for collection assertions.
- Use `result.Should().BeEquivalentTo(expected)` for deep object comparison.
- Use `execution.Should().CompleteWithinAsync(5.Seconds())` for timeout assertions.

## Mocking (NSubstitute)
- Create mocks: `var repo = Substitute.For<IUserRepository>()`.
- Stub returns: `repo.GetAsync(1).Returns(user)`.
- Verify calls: `repo.Received(1).SaveAsync(Arg.Any<User>())`.
- Use `Arg.Is<T>(predicate)` for argument matching.
- Use `ReturnsForAnyArgs()` for lenient stubs in arrangement-focused tests.

## Integration Testing
- Use `WebApplicationFactory<Program>` for ASP.NET Core integration tests.
- Override services with `WithWebHostBuilder(b => b.ConfigureServices(...))`.
- Use `HttpClient` from factory for endpoint testing.
- Use `Respawn` for database cleanup between tests.
- Use `[Collection]` attribute to prevent parallel execution of shared-resource tests.

## Test Data
- Use Builder pattern for complex test data: `new UserBuilder().WithName("Ada").Build()`.
- Use `AutoFixture` for auto-generated test data.
- Use `Bogus` library for realistic fake data generation.
- Keep test data creation close to the test, not in distant shared files.

## Best Practices
- Test behavior, not implementation. Avoid testing private methods.
- Keep tests independent. No shared mutable state between tests.
- Use `CancellationToken.None` explicitly in async test calls.
- Run tests in CI with `dotnet test --blame-hang-timeout 60s`.
