Vertical slices (feature folders: endpoint + handler + models per feature) for API-shaped services; full Clean Architecture layering only when the domain genuinely warrants it — record the choice as an ADR either way.
Minimal APIs for small/medium surfaces; MVC controllers when filters/model-binding complexity earns it.
DI lifetimes (the classic traps)
Scoped services must NEVER be captured by singletons (captive dependency — resolve via IServiceScopeFactory inside the singleton instead).
HttpClient via IHttpClientFactory, never new HttpClient() per call (socket exhaustion).
Register by interface at the boundary you intend to fake in tests; concrete elsewhere is fine.
Async all the way
No .Result/.Wait()/GetAwaiter().GetResult() on async paths — deadlock and threadpool starvation fuel.
CancellationToken flows from the endpoint through every service and EF call: await db.Orders.ToListAsync(ct). Endpoints accept it; libraries propagate it.
ValueTask only where profiling justifies it; default is Task.
EF Core discipline
Reads: AsNoTracking() by default; project to DTOs with Select — never return entities from queries that don't update.
No lazy-loading proxies: load explicitly (Include) so every query's shape is visible — N+1 becomes impossible to miss.
Migrations: one migration per change, reviewed like code; destructive column changes follow expand-contract (see db-optimization skill).
Transactions explicit where multi-write consistency matters (ExecutionStrategy + TransactionScope-free patterns with BeginTransactionAsync).
Config — options pattern
IOptions<T> with ValidateDataAnnotations().ValidateOnStart() — invalid config fails the boot, not the first request.
Secrets from user-secrets/KeyVault/env — never appsettings committed values.
Error contract
ProblemDetails (RFC 9457) via AddProblemDetails(); exceptions mapped centrally (exception handler middleware), typed domain exceptions → status codes. No try/catch per controller.
Nullable reference types ON project-wide; warnings as errors for new code.
Testing shape
xUnit; unit tests fake at the interface boundary.
WebApplicationFactory<Program> for integration tests over the real pipeline (routing, filters, serialization) with a test database (Testcontainers where Docker exists).
Snapshot the API surface where the contract matters (Verify/ApiApprovals) so drift is a failing test.
1---2name: dotnet-patterns3description: C#/.NET Backend Patterns4---56# C#/.NET Backend Patterns78## Layout910- Vertical slices (feature folders: endpoint + handler + models per feature) for API-shaped services; full Clean Architecture layering only when the domain genuinely warrants it — record the choice as an ADR either way.11- Minimal APIs for small/medium surfaces; MVC controllers when filters/model-binding complexity earns it.1213## DI lifetimes (the classic traps)1415- Scoped services must NEVER be captured by singletons (captive dependency — resolve via `IServiceScopeFactory` inside the singleton instead).16- `HttpClient` via `IHttpClientFactory`, never `new HttpClient()` per call (socket exhaustion).17- Register by interface at the boundary you intend to fake in tests; concrete elsewhere is fine.1819## Async all the way2021- No `.Result`/`.Wait()`/`GetAwaiter().GetResult()` on async paths — deadlock and threadpool starvation fuel.22- `CancellationToken` flows from the endpoint through every service and EF call: `await db.Orders.ToListAsync(ct)`. Endpoints accept it; libraries propagate it.23- `ValueTask` only where profiling justifies it; default is `Task`.2425## EF Core discipline2627- Reads: `AsNoTracking()` by default; project to DTOs with `Select` — never return entities from queries that don't update.28- No lazy-loading proxies: load explicitly (`Include`) so every query's shape is visible — N+1 becomes impossible to miss.29- Migrations: one migration per change, reviewed like code; destructive column changes follow expand-contract (see db-optimization skill).30- Transactions explicit where multi-write consistency matters (`ExecutionStrategy` + `TransactionScope`-free patterns with `BeginTransactionAsync`).3132## Config — options pattern3334- `IOptions<T>` with `ValidateDataAnnotations().ValidateOnStart()` — invalid config fails the boot, not the first request.35- Secrets from user-secrets/KeyVault/env — never appsettings committed values.3637## Error contract3839- `ProblemDetails` (RFC 9457) via `AddProblemDetails()`; exceptions mapped centrally (exception handler middleware), typed domain exceptions → status codes. No try/catch per controller.40- Nullable reference types ON project-wide; warnings as errors for new code.4142## Testing shape4344- xUnit; unit tests fake at the interface boundary.45- `WebApplicationFactory<Program>` for integration tests over the real pipeline (routing, filters, serialization) with a test database (Testcontainers where Docker exists).46- Snapshot the API surface where the contract matters (Verify/ApiApprovals) so drift is a failing test.
Run npx skillmds@latest add jdanigo/dotnet-patterns in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
C#/.NET Backend Patterns It is listed under Coding & Dev Tools on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
jdanigo (@jdanigo) published this skill. Their other Agent Skills are listed on their SkillMD profile.