$npx -y skills add softspark/ai-toolkit --skill csharp-rulesC#/.NET coding rules: style, patterns, security, testing. Triggers: .cs, .csproj, .sln, ASP.NET, ASP.NET Core, EF Core, LINQ, NUnit, xUnit, dotnet.
| 1 | # C#/.NET Rules |
| 2 | |
| 3 | These rules come from `app/rules/csharp/` in ai-toolkit. They cover |
| 4 | the project's standards for coding style, frameworks, patterns, |
| 5 | security, and testing in C#/.NET. Apply them when writing or |
| 6 | reviewing C#/.NET code. |
| 7 | |
| 8 | # C# Coding Style |
| 9 | |
| 10 | ## Naming |
| 11 | - PascalCase: classes, structs, enums, interfaces, methods, properties, events. |
| 12 | - camelCase: local variables, parameters, private fields. |
| 13 | - Prefix interfaces with `I`: `IUserRepository`, `IDisposable`. |
| 14 | - Prefix private fields with `_`: `private readonly ILogger _logger;`. |
| 15 | - UPPER_SNAKE: not conventional in C#. Use PascalCase for constants. |
| 16 | |
| 17 | ## Nullable Reference Types |
| 18 | - Enable `<Nullable>enable</Nullable>` in all projects. |
| 19 | - Use `string?` only when null is semantically meaningful. |
| 20 | - Use `!` (null-forgiving) operator sparingly -- only when compiler cannot infer. |
| 21 | - Use `??` (null-coalescing) and `?.` (null-conditional) for safe navigation. |
| 22 | - Use `required` modifier (C# 11) on properties that must be set at initialization. |
| 23 | |
| 24 | ## Records and Types |
| 25 | - Use `record` for immutable value objects and DTOs. |
| 26 | - Use `record struct` for small, stack-allocated value types. |
| 27 | - Use `init` properties for immutable-after-construction objects. |
| 28 | - Use `with` expressions for non-destructive mutation of records. |
| 29 | - Use primary constructors (C# 12) for concise class definitions. |
| 30 | |
| 31 | ## Pattern Matching |
| 32 | - Use `is` pattern for type checks: `if (obj is string s)`. |
| 33 | - Use `switch` expressions for exhaustive matching over enums/types. |
| 34 | - Use property patterns: `user is { Age: > 18, Role: "admin" }`. |
| 35 | - Use relational patterns: `size is > 0 and < 100`. |
| 36 | - Use list patterns (C# 11): `numbers is [1, 2, .., var last]`. |
| 37 | |
| 38 | ## Async/Await |
| 39 | - Suffix async methods with `Async`: `GetUserAsync()`. |
| 40 | - Return `Task<T>` or `ValueTask<T>`, never `void` (except event handlers). |
| 41 | - Use `await` with `ConfigureAwait(false)` in library code. |
| 42 | - Use `CancellationToken` parameters in all async public APIs. |
| 43 | - Prefer `ValueTask<T>` when synchronous completion is common. |
| 44 | |
| 45 | ## File Organization |
| 46 | - One type per file. File name matches type name. |
| 47 | - Use file-scoped namespaces (C# 10): `namespace MyApp.Services;`. |
| 48 | - Order members: fields, constructors, properties, public methods, private methods. |
| 49 | - Use `global using` directives in a single `GlobalUsings.cs` file. |
| 50 | |
| 51 | ## Formatting |
| 52 | - Use `.editorconfig` with C# style rules committed to the repository. |
| 53 | - Use `dotnet format` for automated formatting. |
| 54 | - Use Roslyn analyzers for compile-time style enforcement. |
| 55 | - Max line length: 120 characters. |
| 56 | |
| 57 | # C# Frameworks |
| 58 | |
| 59 | ## ASP.NET Core |
| 60 | - Use minimal APIs for simple endpoints. Use controllers for complex APIs. |
| 61 | - Use `[ApiController]` attribute for automatic model validation and error responses. |
| 62 | - Use `Results.Ok()`, `Results.NotFound()` for typed HTTP results. |
| 63 | - Use endpoint filters / middleware for cross-cutting concerns. |
| 64 | - Use `IHostedService` / `BackgroundService` for long-running background tasks. |
| 65 | - Map routes with `app.MapGet()`, `app.MapPost()` for minimal API style. |
| 66 | |
| 67 | ## Entity Framework Core |
| 68 | - Use code-first migrations: `dotnet ef migrations add`, `dotnet ef database update`. |
| 69 | - Use `DbContext` with scoped lifetime (one per request). |
| 70 | - Use `AsNoTracking()` for read-only queries. Use `AsTracking()` only for updates. |
| 71 | - Use `Include()` / `ThenInclude()` for eager loading related entities. |
| 72 | - Use shadow properties for audit fields (`CreatedAt`, `UpdatedAt`). |
| 73 | - Use `HasQueryFilter()` for soft-delete and multi-tenancy global filters. |
| 74 | |
| 75 | ## Blazor |
| 76 | - Use Blazor Server for internal tools. Use Blazor WASM for public-facing SPAs. |
| 77 | - Use `@inject` for dependency injection in components. |
| 78 | - Use `EventCallback<T>` for parent-child component communication. |
| 79 | - Use `CascadingValue` for deeply shared state (theme, auth). |
| 80 | - Use `StateContainer` pattern with events for cross-component state management. |
| 81 | |
| 82 | ## SignalR |
| 83 | - Use strongly-typed hubs: `Hub<IClientMethods>` for compile-time safety. |
| 84 | - Use `HubContext<T>` for sending messages from outside hubs. |
| 85 | - Use groups for targeted broadcasting: `Groups.AddToGroupAsync()`. |
| 86 | - Configure automatic reconnection on the client side. |
| 87 | |
| 88 | ## MassTransit / Messaging |
| 89 | - Use MassTransit for message bus abstraction over RabbitMQ/Azure Service Bus. |
| 90 | - Define messages as `record` types for immutability. |
| 91 | - Use consumers (`IConsumer<T>`) for message handling. |
| 92 | - Use sagas for long-running, multi-step workflows with state. |
| 93 | - Use retry and circuit breaker policies for transient failures. |
| 94 | |
| 95 | ## Logging |
| 96 | - Use `ILogger<T>` via DI. Never instantiate loggers manually. |
| 97 | - Use structured logging: `_logger.LogInformation("User {UserId} logged in", userId)`. |
| 98 | - Use Serilog with sinks for structured, centralized logging. |
| 99 | - Use log scopes for request correlation: `using (_logger.BeginScope(...))`. |
| 100 | |
| 101 | ## Configuration |
| 102 | - |