Skip to content

.NET Backend Standards

The rules live in committed config, not in this page. Humans and AI agents read the exact same enforced rules.

Who this applies to: new repos comply on day one. An existing repo complies at its next LTS bump — the gap is a ticket, not an exception.

File Pins Enforcement
global.json SDK version + rollForward Same compiler and analyzers for every developer and CI agent
Directory.Build.props TFM, Nullable, TreatWarningsAsErrors, AnalysisLevel, AnalysisMode Build breaks
Directory.Packages.props Every package version, once (ManagePackageVersionsCentrally) No Version= in any .csproj
.editorconfig Style, formatting, and per-rule analyzer severity dotnet format --verify-no-changes in the PR pipeline
  • LTS only. New services target the current LTS. We don’t ship STS releases to clients — the support window expires mid-contract.
  • Pin AnalysisLevel to the target framework, never latest. An SDK patch must not break an unrelated PR.
  • A repo-wide NoWarn needs Backend Lead sign-off. A per-file #pragma is the escape hatch.
flowchart LR
  Ep["Api — Endpoints/&lt;Feature&gt;/&lt;UseCase&gt;<br/>endpoint · request · validator · query|command"] --> Dom["Domain<br/>pure — no EF, no web framework"]
  Ep --> Inf["Infrastructure<br/>EF Core · integrations"]
  Inf --> Dom
  • FastEndpoints vertical slices. One folder per use case, holding one endpoint plus its request, response, validator, and its single query or command.
  • No MVC controllers, no MediatR, no anaemic pass-through service layer. The endpoint is the handler. Extract a service only when behaviour is genuinely shared or independently testable.
  • Litmus: if a file still makes sense with the HTTP framework removed, it does not belong under Endpoints/.
Concern Default
Errors One IExceptionHandler + AddProblemDetails(). Exception text goes to the log, never to the client — shape in API Design
Validation FluentValidation at the boundary, one validator file per slice. Domain invariants throw a typed exception. No Result<T> monad
Config AddOptions<T>().Bind(…).ValidateDataAnnotations().ValidateOnStart() — a bad config fails the boot instead of defaulting
Secrets user-secrets locally, Key Vault–backed variable group when deployed. Never in appsettings*.json
Auth Server-side, per endpoint, permission-based. A client-side guard is UX, never protection
Logging Serilog, with a correlation id enriched on every request
Telemetry OpenTelemetry traces + metrics over OTLP
Health Readiness and liveness endpoints in every environment — restricted to the platform (auth or internal port) outside Development
  • Never edit an applied migration — add a new one. Forward-only against a client database.
  • A migration PR is blocked until the branch has merged every migration already on the target branch. Enforced by a migration-ordering check in the PR pipeline.
  • Migrations are added through the repo’s committed script, so the --context and startup project can’t drift.
  • One writable DbContext per database. A context over data we don’t own is read-only in code, not by convention.
  • No ambient transaction registered in DI. Never ReadUncommitted. EnableRetryOnFailure() on every SQL Server context.
  • xUnit v3. Async tests pass TestContext.Current.CancellationToken — that is what makes the CancellationToken rule enforceable rather than aspirational.
  • WebApplicationFactory<Program> for the API surface. Container-dependent tests carry [Trait("Category", "RequiresDocker")] so the suite still runs green on a machine without Docker.
  • EF InMemory is for composition tests only. It is not a relational provider, so it cannot validate SQL translation, constraints, or concurrency.
  • One test builds the production service graph with ValidateOnBuild + ValidateScopes. Captive dependencies and unresolvable services fail in CI, not at 2am.

See Testing strategy for the coverage gate and the CI lanes.

  • Async all the way — no .Result, .Wait(), or async void. Pass CancellationToken through the call chain.
  • DI over statics — constructor injection only. No service locator.
  • Nullable on — model absence explicitly. No null-forgiving ! without a comment saying why.

See also: Examples · API Design · Branching & PRs · Pipelines & Environments.

👤 Owner: Firas Darwish🗓 Last reviewed: 2026-07-25

اقرأ هذه الصفحة بالعربية