Stack conventions
.NET (C# / ASP.NET Core)
C# and .NET 8+ services, APIs and libraries (ASP.NET Core, EF Core), built with nullable reference types and analyzers on.
C# and .NET 8+ services, APIs and libraries (ASP.NET Core, EF Core), built with nullable reference types and analyzers on.
Used for: dotnet.
Principles
- Keep nullable reference types enabled and honour the annotations: no
!(null-forgiving) to silence warnings without a proven reason. - Be async all the way: return
Task/ValueTask, never block with.Result,.Wait()orGetAwaiter().GetResult(), and never writeasync voidexcept event handlers. - Accept and pass a
CancellationTokenthrough every I/O-bound call chain (request aborted, host shutdown). - In reusable libraries use
ConfigureAwait(false); in ASP.NET Core application code it is unnecessary (there is no synchronization context) — follow the repo's existing choice. - Use the built-in dependency injection container with constructor injection and correct lifetimes; never resolve scoped services from singletons.
- Bind configuration to typed options (
IOptions<T>/IOptionsMonitor<T>) with validation at start-up (ValidateDataAnnotations().ValidateOnStart()). - Log with
ILogger<T>and message templates (named placeholders), never string interpolation; use[LoggerMessage]source generation on hot paths. - With EF Core, read with
AsNoTracking()or projections, load related data deliberately (projection/Include) to avoid N+1 queries, and keepDbContextscoped to one unit of work. - Follow the API style the repo uses (minimal APIs or controllers); return typed results (
TypedResults,ActionResult<T>) and RFC 7807 problem details for errors. - Prefer immutable data:
recordtypes for DTOs,init/requiredmembers, andreadonlyfields.
Conventions
Accessibility
Concurrency
- DbContext is not thread-safe: do not run parallel queries on one instance.
- Protect shared state with immutable data,
Concurrent*collections orSemaphoreSlim(async); neverlockaroundawait. - Honour cancellation tokens and avoid fire-and-forget tasks; use a hosted service or queue.
Dependencies
- Prefer framework packages (
Microsoft.Extensions.*) before third-party libraries; keep package versions consistent across projects. - Keep NuGet audit warnings visible; update vulnerable packages rather than suppressing warnings.
Documentation
- XML doc comments (
///) on public APIs of libraries, including exceptions thrown; keep OpenAPI metadata (WithSummary,Produces) accurate for endpoints. - Comment why, not what; keep the README's run, configuration and migration steps current when they change.
Errors
- Throw specific exceptions (
ArgumentNullException.ThrowIfNull,ArgumentOutOfRangeException.ThrowIfNegative); never throwExceptionor catch it without rethrowing or logging. - Do not use exceptions for control flow on expected outcomes; return results or
nullwith nullable annotations. - Rethrow with
throw;, notthrow ex;, to keep the stack trace.
Logging
- Use structured message templates (
logger.LogInformation("Order {OrderId} shipped", id)) with stable property names. - Use log scopes for correlation; never log secrets, tokens or personal data.
- Pick levels deliberately: Information for business events, Warning for recoverable issues, Error with the exception object.
Naming
- PascalCase for types, methods, properties, constants and public fields; camelCase for locals and parameters;
_camelCasefor private fields; interfaces start withI. - Async methods end with
Async; boolean members read as predicates (IsActive,HasItems). - Namespaces follow the folder structure and use file-scoped declarations.
Performance
- Project to DTOs with
Selectrather than loading full entity graphs; page large results. - Avoid synchronous I/O and
Task.Runin request paths; useIAsyncEnumerable<T>for streaming. - Use
HybridCache/IMemoryCache/output caching deliberately with explicit expiry, where the repo has a caching approach.
Security
- Protect endpoints with authorisation policies (
RequireAuthorization,[Authorize(Policy=...)]) and default to deny for new endpoints. - Never concatenate input into SQL: use LINQ, parameters, or
FromSql/ExecuteSqlwith interpolation (parameterised), neverFromSqlRawwith concatenated input. - Keep secrets out of
appsettings*.json; use user secrets locally and a secret store (e.g. Key Vault) in deployed environments. - Validate request models at the edge and bind only the fields you expect (no over-posting of entities).
Structure
- One public type per file named after the type; keep
Program.csto composition (service registration, middleware, endpoint mapping). - Separate API, application/domain and infrastructure concerns along the lines the solution already uses; do not introduce a new layering scheme in one change.
- Use
Directory.Build.propsand central package management (Directory.Packages.props) when the repo has them.
Testing
- Use the repo's framework (xUnit, NUnit or MSTest); name tests
Method_Scenario_ExpectedResultor the repo's convention. - Integration-test HTTP APIs with
WebApplicationFactory<Program>; use Testcontainers or a real provider for relational behaviour instead of the EF Core in-memory provider. - Do not mock types you do not own (DbContext, HttpClient); fake the boundary (
HttpMessageHandler, repository interface). - Unit-test domain logic and handlers without the host; integration tests use
WebApplicationFactoryand a real database (Testcontainers) for anything crossing a boundary. - Done means tested: new or changed behaviour ships with tests in the same change, and a fixed bug gets a regression test that fails without the fix.
Review checklist
- Are nullable warnings addressed without unjustified
!operators? - Is the code async end to end with no
.Result,.Wait()orasync void(except event handlers)? - Are CancellationTokens accepted and passed to I/O calls?
- Are DI lifetimes correct (no scoped service captured by a singleton)?
- Is configuration bound through validated options rather than read ad hoc?
- Do log calls use message templates and avoid secrets/personal data?
- Do EF Core reads avoid tracking where not needed and avoid N+1 queries?
- Is raw SQL parameterised?
- Do new endpoints require authorisation and validate input?
- Are errors returned as problem details with correct status codes?
- Are DTOs used at the API boundary instead of entities?
- Are HTTP clients created through IHttpClientFactory?
Quality checks (run when your repository has the tool configured)
| Check | Kind | Tool | Blocks the pull request |
|---|---|---|---|
dotnet-format |
format | dotnet format | no |
dotnet-build-warnaserror |
static | dotnet build (analyzers) | no |
nuget-vulnerable |
security | dotnet list package | no |
gitleaks |
security | gitleaks | no |