--- 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 `enable` 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` or `ValueTask`, never `void` (except event handlers). - Use `await` with `ConfigureAwait(false)` in library code. - Use `CancellationToken` parameters in all async public APIs. - Prefer `ValueTask` 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` 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` for compile-time safety. - Use `HubContext` 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`) 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` 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`. - Use `IOptionsMonitor` 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` 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` 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` for async producer-consumer patterns. - Use `IAsyncEnumerable` 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` 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` 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` for mutations. Queries: `IRequest` 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 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` (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` for shared setup. - Name tests: `MethodName_Scenario_ExpectedResult`. ## Assertions (FluentAssertions) - Use `result.Should().Be(expected)` for value assertions. - Use `action.Should().Throw()` 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()`. - Stub returns: `repo.GetAsync(1).Returns(user)`. - Verify calls: `repo.Received(1).SaveAsync(Arg.Any())`. - Use `Arg.Is(predicate)` for argument matching. - Use `ReturnsForAnyArgs()` for lenient stubs in arrangement-focused tests. ## Integration Testing - Use `WebApplicationFactory` 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`.