Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/mghabin/dotnet-engineering-guide/badge)](https://securityscorecards.dev/viewer/?uri=github.com/mghabin/dotnet-engineering-guide)

An opinionated **.NET 10 / C# 13 / Aspire 9.x** doctrine for senior backend engineers who own long-lived services in production, compiled from primary sources (Microsoft Learn, the .NET / ASP.NET / EF Core team blogs, `dotnet/runtime` docs, IETF RFCs, the OpenTelemetry spec) and a small set of named industry voices, with every chapter ending in a Sources block so you can follow the citations.
An opinionated **.NET 10 / C# 14 / Aspire** doctrine for senior backend engineers who own long-lived services in production, compiled from primary sources (Microsoft Learn, the .NET / ASP.NET / EF Core team blogs, `dotnet/runtime` docs, IETF RFCs, the OpenTelemetry spec) and a small set of named industry voices, with every chapter ending in a Sources block so you can follow the citations.

Aspire baseline is split by target framework: **Aspire 9.x GA** for `net8.0` / `net9.0` services, **Aspire 13 GA** for `net10.0` services (the version line jumped 9.x → 13.0 to align with the .NET 10 wave and the rebrand to "Aspire" with docs at <https://aspire.dev>).

## Read this first

These five pages are the synthesis.
The numbered chapters are doctrine you reach for *when the decision tree points you there*.

- [`docs/decision-trees.md`](./docs/decision-trees.md) — one-screen decision trees for the questions .NET teams actually argue about (Minimal APIs vs Controllers, EF Core vs Dapper, render mode, NativeAOT, Aspire scope, Cosmos partition key, cache tier).
- [`docs/decision-trees.md`](./docs/decision-trees.md) — one-screen decision trees for the questions .NET teams actually argue about (Minimal APIs vs Controllers, EF Core vs Dapper / Cosmos vs SQL, EF migration deploy, Blazor render mode, client platform, test type, assertion library, NativeAOT, GC, `IHttpClientFactory`, auth policy shape, cache tier, K8s QoS, resilience, Aspire scope, Cosmos partition key).
- [`checklist.md`](./checklist.md) — one-page do/don't card for code review, every line a `must`-severity rule.
- [`SCOPE.md`](./SCOPE.md) — who this guide is for, who it isn't, and the technical and organisational envelope the defaults assume.
- [`glossary.md`](./glossary.md) — canonical, opinionated definitions for every term used normatively, with primary-source citations where the ecosystem disagrees.
Expand All @@ -19,15 +21,15 @@ The numbered chapters are doctrine you reach for *when the decision tree points
## Chapters (doctrine)

Read in depth when the decision tree or checklist points you here.
The numbered order also works as a linear read for engineers new to the .NET 10 / Aspire 9.x stack.
The numbered order also works as a linear read for engineers new to the .NET 10 / C# 14 / Aspire stack.

- [`docs/01-foundations.md`](./docs/01-foundations.md) — language baseline (C# 13, NRT, analyzers), the `async`/`await` rules (Cleary canon), DI lifetimes, and runtime configuration defaults for the Generic Host.
- [`docs/02-aspnetcore.md`](./docs/02-aspnetcore.md) — Minimal APIs as the default, ProblemDetails (RFC 9457) for errors, Microsoft Entra bearer auth with `scp` vs `roles` modelled correctly, and OpenTelemetry wired from day one.
- [`docs/01-foundations.md`](./docs/01-foundations.md) — language baseline (C# 14, NRT, analyzers), the `async`/`await` rules (Cleary canon), DI lifetimes, and runtime configuration defaults for the Generic Host.
- [`docs/02-aspnetcore.md`](./docs/02-aspnetcore.md) — Minimal APIs as the default, ProblemDetails (RFC 9457) for errors, Microsoft Entra bearer auth with `scp` vs `roles` modelled as separate delegated and app-only policies, and OpenTelemetry wired from day one.
- [`docs/03-data.md`](./docs/03-data.md) — EF Core 10 patterns, Cosmos DB modelling, zero-downtime migrations (expand-contract), and the cache decision matrix from in-memory through HybridCache to CDN.
- [`docs/04-testing.md`](./docs/04-testing.md) — xUnit v3 on Microsoft.Testing.Platform (MTP), `WebApplicationFactory` for in-process integration, Testcontainers for real dependencies, and Aspire `DistributedApplicationTestingBuilder` for full App Host coverage.
- [`docs/05-performance.md`](./docs/05-performance.md) — BenchmarkDotNet methodology, allocation analysis with pooling (`Span<T>`, `ArrayPool`, `RecyclableMemoryStream`), NativeAOT trade-offs, and server GC / DATAS tuning.
- [`docs/06-cloud-native.md`](./docs/06-cloud-native.md) — .NET Aspire 9.x as the local-orchestration and deployment-manifest authority, Kubernetes probes and QoS classes, service discovery, and Data Protection keyring management in cloud.
- [`docs/07-client.md`](./docs/07-client.md) — Blazor render-mode decision tables (Static SSR, Server, WASM, Auto), MAUI Window lifecycle, and MVVM with the CommunityToolkit source generators.
- [`docs/06-cloud-native.md`](./docs/06-cloud-native.md) — .NET Aspire (9.x for net8/9, 13 for net10) as the local-orchestration and deployment-manifest authority, Kubernetes probes and QoS classes, service discovery, and Data Protection keyring management in cloud.
- [`docs/07-client.md`](./docs/07-client.md) — Blazor render-mode decision tables (Static SSR, Server, WASM, Auto) on .NET 10 / C# 14, MAUI Window lifecycle, and MVVM with the CommunityToolkit source generators.

## Patterns

Expand Down
18 changes: 11 additions & 7 deletions SCOPE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ If your situation falls outside this envelope, individual chapters may still be
Concrete reader profiles.
If two or more apply, you are the target reader.

- A senior or staff backend .NET engineer owning one or more long-lived services on .NET 10 / C# 13.
- A senior or staff backend .NET engineer owning one or more long-lived services on .NET 10 / C# 14.
- A solutions or application architect choosing the default project, hosting, and data shape for a new bounded context.
- A tech lead inheriting a .NET estate and asked to "make it boring" before scaling the team.
- A platform engineer standardising `Directory.Build.props`, Central Package Management, analyzers, and CI templates across many .NET repos ([learn.microsoft.com/nuget/consume-packages/central-package-management](https://learn.microsoft.com/nuget/consume-packages/central-package-management)).
- An engineer wiring a new ASP.NET Core service to AKS, App Service, or Azure Container Apps and picking between Minimal APIs and Controllers ([learn.microsoft.com/aspnet/core/fundamentals/minimal-apis/overview](https://learn.microsoft.com/aspnet/core/fundamentals/minimal-apis/overview)).
- An engineer adopting .NET Aspire 9.x for local orchestration, service discovery, and OTel defaults ([learn.microsoft.com/dotnet/aspire/whats-new/dotnet-aspire-9](https://learn.microsoft.com/dotnet/aspire/whats-new/dotnet-aspire-9)).
- An engineer adopting .NET Aspire for local orchestration, service discovery, and OTel defaults — Aspire 9.x for `net8.0` / `net9.0` services and Aspire 13 for `net10.0` services ([learn.microsoft.com/dotnet/aspire/whats-new/dotnet-aspire-9](https://learn.microsoft.com/dotnet/aspire/whats-new/dotnet-aspire-9), [aspire.dev](https://aspire.dev)).
- An SRE or perf-focused engineer using BenchmarkDotNet, dotnet-counters, and PerfView to investigate allocations, GC, or NativeAOT trade-offs ([devblogs.microsoft.com/dotnet/performance-improvements-in-net-9](https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-9)).

---
Expand All @@ -30,7 +30,7 @@ Specifically:

- **.NET 10 GA on the current LTS / STS cadence** ([learn.microsoft.com/dotnet/core/releases-and-support](https://learn.microsoft.com/dotnet/core/releases-and-support)).
Older TFMs are mentioned only when guidance differs.
- **C# 13 language defaults**, NRT enabled, `TreatWarningsAsErrors=true`, analyzers on by default ([learn.microsoft.com/dotnet/csharp/whats-new/csharp-13](https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-13)).
- **C# 14 language defaults** (the compiler default tied to `net10.0`), NRT enabled, `TreatWarningsAsErrors=true`, analyzers on by default ([learn.microsoft.com/dotnet/csharp/whats-new/csharp-14](https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-14), [learn.microsoft.com/dotnet/csharp/language-reference/configure-language-version](https://learn.microsoft.com/dotnet/csharp/language-reference/configure-language-version)).
- **Cloud as the deployment target.** AKS, Azure App Service, Azure Container Apps, and Azure Functions on the isolated worker model ([learn.microsoft.com/azure/azure-functions/dotnet-isolated-process-guide](https://learn.microsoft.com/azure/azure-functions/dotnet-isolated-process-guide)).
On-prem IIS is not the default.
- **Multi-team monorepos or bounded-context services**, not single-solution single-team codebases.
Expand Down Expand Up @@ -107,19 +107,22 @@ Each path is ordered; do not skip steps.

### 6.1 First 90 days on a new .NET 10 service

- Start at [`patterns/anti-patterns.md`](./patterns/anti-patterns.md) for the decision trees implied by the rejected patterns.
- Start at [`docs/decision-trees.md`](./docs/decision-trees.md) the synthesis entrypoint that routes you to the chapter that owns each decision.
- Then [`checklist.md`](./checklist.md) as the one-page review card.
- Then [`patterns/anti-patterns.md`](./patterns/anti-patterns.md) for the rejected shapes you will see most often.
- Then [`docs/01-foundations.md`](./docs/01-foundations.md) for solution layout, CPM, analyzers, async, and DI.

### 6.2 Architect choosing the shape of a new bounded context

- Start at [`patterns/anti-patterns.md`](./patterns/anti-patterns.md) and [`patterns/patterns.md`](./patterns/patterns.md) for the rejected and preferred shapes.
- Start at [`docs/decision-trees.md`](./docs/decision-trees.md) for Minimal vs Controllers, EF vs Dapper vs Cosmos, render mode, Aspire scope, Cosmos partition key.
- Then [`patterns/anti-patterns.md`](./patterns/anti-patterns.md) and [`patterns/patterns.md`](./patterns/patterns.md) for the rejected and preferred shapes.
- Then [`docs/02-aspnetcore.md`](./docs/02-aspnetcore.md) for Minimal vs Controllers, ProblemDetails, versioning, OpenAPI, resilience.
- Then [`docs/06-cloud-native.md`](./docs/06-cloud-native.md) for Aspire, AKS probes, container hygiene.

### 6.3 SRE / performance engineer chasing latency or cost

- Start at [`docs/05-performance.md`](./docs/05-performance.md) for BenchmarkDotNet methodology, allocation analysis, `Span<T>`, pooling, source generators, NativeAOT.
- Start at [`docs/decision-trees.md`](./docs/decision-trees.md) for the NativeAOT, GC, resilience, QoS, and cache trees.
- Then [`docs/05-performance.md`](./docs/05-performance.md) for BenchmarkDotNet methodology, allocation analysis, `Span<T>`, pooling, source generators, NativeAOT.
- Then [`docs/06-cloud-native.md`](./docs/06-cloud-native.md) for OTel defaults, probes, limits, graceful shutdown.
- Then [`checklist.md`](./checklist.md) for the perf-relevant review items.

Expand All @@ -130,7 +133,8 @@ Each path is ordered; do not skip steps.
- **Targets the current GA stable .NET.**
At time of writing, .NET 10 (STS / LTS per [learn.microsoft.com/dotnet/core/releases-and-support](https://learn.microsoft.com/dotnet/core/releases-and-support)).
When .NET 11 ships, guidance moves with it; the previous LTS is called out inline only where defaults differ.
- **Targets the current Aspire GA, currently 9.x** ([learn.microsoft.com/dotnet/aspire/whats-new/dotnet-aspire-9](https://learn.microsoft.com/dotnet/aspire/whats-new/dotnet-aspire-9)).
- **Targets the current Aspire GA, split by TFM**: Aspire 9.x for `net8.0` / `net9.0`, Aspire 13 for `net10.0` ([learn.microsoft.com/dotnet/aspire/whats-new/dotnet-aspire-9](https://learn.microsoft.com/dotnet/aspire/whats-new/dotnet-aspire-9), [aspire.dev](https://aspire.dev)).
The version line jumped 9.x → 13.0 to align with the .NET 10 wave, alongside the rebrand from "**.NET Aspire**" to "**Aspire**".
- **Pre-GA features are labeled.**
Anything behind a preview feature flag, an `[Experimental]` attribute, or a `RequiresPreviewFeatures` gate is marked `preview` inline and is not a default.
- **EF Core, ASP.NET Core, and runtime release notes are the source of truth** for breaking changes between minor versions ([learn.microsoft.com/ef/core/what-is-new](https://learn.microsoft.com/ef/core/what-is-new), [learn.microsoft.com/aspnet/core/release-notes](https://learn.microsoft.com/aspnet/core/release-notes)).
Expand Down
37 changes: 27 additions & 10 deletions coverage-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,18 @@ Source: [`docs/02-aspnetcore.md`](./docs/02-aspnetcore.md).
- `ProblemDetails` per RFC 9457 as the single error contract (`AddProblemDetails`, `IProblemDetailsService`, exception handler middleware).
- API versioning policy (`Asp.Versioning.Http`, URL/segment vs header) and deprecation headers.
- OpenAPI generation via `Microsoft.AspNetCore.OpenApi` (transformer pipeline, schema customization, replacing Swashbuckle).
- AuthN/AuthZ on the request pipeline: JWT bearer with Microsoft Entra, validation of `iss`/`aud`/`scp`/`roles`/`azp`, policy-based authorization.
- Antiforgery / CSRF handling for cookie-auth surfaces and Blazor server interactions on the API side.
- OpenTelemetry HTTP wiring on the request pipeline (`AddAspNetCoreInstrumentation`, `AddHttpClientInstrumentation`, propagation).
- Rate limiting middleware, request size limits, and Kestrel surface defaults relevant to APIs.
- AuthN/AuthZ on the request pipeline (§10): JWT bearer with Microsoft Entra, validation of `iss`/`aud`/`scp`/`roles`/`azp`, **separate delegated (`scp`) and app-only (`roles` + `azp` allow-list) policies** (no single OR-claims policy), CAE / claims-challenge wiring.
- Antiforgery / CSRF handling for cookie-auth surfaces and Blazor server interactions on the API side (§14 — Security).
- OpenTelemetry HTTP wiring on the request pipeline (§6 — `AddAspNetCoreInstrumentation`, `AddHttpClientInstrumentation`, propagation).
- **HTTP resilience for outbound calls** (§7): `IHttpClientFactory` + `AddStandardResilienceHandler` as the single owner of retry / timeout / circuit-breaker / hedging defaults. Chapter 06 references this section, does not re-decide.
- **`HttpClient` factory and typed clients** (§11): `AddHttpClient<T>`, named vs typed, `SocketsHttpHandler` defaults, `PooledConnectionLifetime`.
- **OutputCache** (§9): HTTP-response caching middleware (`AddOutputCache`, policies, tag invalidation). Chapter 03's caching matrix links here for the OutputCache row.
- **Background work in the request host** (§12): `IHostedService` / `BackgroundService` patterns, `Channel<T>` pipelines, graceful drain of in-flight work — composition lifetime owned here; the cluster-side drain contract is owned by [06 §11](#chapter-06--cloud-native).
- **Security middleware** (§14): HTTPS redirection, HSTS, security headers, antiforgery, request-size limits, forwarded headers (paired with §15).
- **gRPC surface** (§16): `MapGrpcService`, gRPC-Web, code-first vs proto-first, interceptors, deadlines.
- **SignalR surface** (§17): hub authorization, backplane choice, scaling rules, sticky sessions.
- **Health-check endpoint mapping** (§18): the in-process `MapHealthChecks` helpers and how the request host exposes them — but the **probe contract** (endpoint names, semantics, what a check may do) is owned by [06 §10](#chapter-06--cloud-native).
- Rate limiting middleware (§8), request size limits, and Kestrel surface defaults relevant to APIs.

### References (does not re-decide)

Expand Down Expand Up @@ -188,13 +196,22 @@ Source: [`docs/06-cloud-native.md`](./docs/06-cloud-native.md).

### Owns

- .NET Aspire 9.x AppHost composition and client integrations (`Aspire.Hosting.*`, `Aspire.*`).
- `ServiceDefaults` shared project: OTel wiring, health checks, resilience handlers, service discovery defaults.
- .NET Aspire AppHost composition and client integrations — **Aspire 9.x** for `net8.0` / `net9.0` services, **Aspire 13** for `net10.0` services (`Aspire.Hosting.*`, `Aspire.*`).
- `ServiceDefaults` shared project (§1): OTel wiring, default health checks, resilience handlers, service discovery defaults.
- **Configuration in cluster** (§4): ConfigMap + CSI Key Vault driver as the configuration substrate; `appsettings.Production.json` rejected. Foundations §6 owns the in-process options pattern; this chapter owns where the bytes come from in the cluster.
- **Resilience pipeline defaults at the platform layer** (§6): Polly v8 standard-pipeline composition for non-HTTP resources (queues, blobs, repositories) and the rationale for keeping HTTP resilience in [02 §7](#chapter-02--aspnetcore). HTTP retry verb policy is owned by ch02; this chapter does not re-decide it.
- **Health-check probe contract** (§10): `/health/live`, `/health/ready`, `/health/startup` endpoint names, semantics, what each probe may do, and the K8s probe wiring — matches the Aspire `ServiceDefaults` mapping. Chapter 02 §18 only owns the in-process `MapHealthChecks` plumbing and links here for the contract.
- Kubernetes runtime contract: liveness / readiness / startup probes, QoS class, the CPU-limits-considered-harmful stance, requests sizing.
- Service discovery via `Microsoft.Extensions.ServiceDiscovery` and `HttpClient` integration.
- OTLP exporters (traces, metrics, logs) and collector topology assumptions.
- ASP.NET Core Data Protection key-ring storage (Azure Blob + Key Vault, or equivalent) for multi-replica deployments.
- Service discovery via `Microsoft.Extensions.ServiceDiscovery` and `HttpClient` integration (§7).
- **Networking** (§13): HTTP/2 + HTTP/3 enablement, forwarded headers in cluster, mTLS via service mesh, TLS termination boundary.
- OTLP exporters (traces, metrics, logs) and collector topology assumptions (§5).
- **Secrets & identity** (§8): Workload Identity + Federated Identity Credentials only — no client secrets, no pod-identity v1, no service-principal passwords on disk.
- ASP.NET Core Data Protection key-ring storage (Azure Blob + Key Vault, or equivalent) for multi-replica deployments (§9).
- **Graceful shutdown** (§11): `IHostApplicationLifetime`, `preStop` hook, drain order, K8s `terminationGracePeriodSeconds`. Chapter 02's background-work section composes against this contract.
- Outbox **dispatcher** topology (hosted service / sidecar / worker pool) — the pattern itself is owned by [03](#chapter-03--data).
- **CI/CD** (§12): reproducible image build, Sigstore/cosign signing, SBOM, scanning gate. Source-build hygiene is owned by [01 §11](#chapter-01--foundations); this chapter owns the cluster-deployment pipeline.
- **Multi-tenancy at runtime** (§14): tenant-scoped DI, per-tenant configuration / connection-string resolution, isolation expectations at the cluster boundary.
- **Cost & efficiency** (§15): right-sizing requests, scale-to-zero suitability, image size baseline, the cluster-level levers a service team owns.
- Dockerfile baseline for .NET 10 images, including the runtime env-vars selected in [05](#chapter-05--performance).

### References (does not re-decide)
Expand Down Expand Up @@ -276,7 +293,7 @@ Source: [`checklist.md`](./checklist.md).

### References (does not re-decide)

- Every checklist line links to the owning chapter for rationale; the rule itself lives in the chapter.
- The card itself does not encode rationale; each line restates a rule whose canonical wording lives in the owning chapter listed in the checklist's footer chapter index.
- `should` / `prefer` / `avoid` nuance lives in chapters, not on the card.

### Cross-links
Expand Down
Loading
Loading