---
name: dotnet-10-csharp-14
description: Use when building .NET 10 or C# 14 applications; when using minimal APIs, modular monolith patterns, or feature folders; when implementing HTTP resilience, Options pattern, Channels, or validation; when seeing outdated patterns like old extension method syntax
---
# .NET 10 & C# 14 Best Practices
.NET 10 (LTS, Nov 2025) with C# 14. Covers minimal APIs, not MVC.
**Official docs:** [.NET 10](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-10/overview) | [C# 14](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14) | [ASP.NET Core 10](https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-10.0)
## Detail Files
| File | Topics |
|------|--------|
| [csharp-14.md](csharp-14.md) | Extension blocks, `field` keyword, null-conditional assignment |
| [minimal-apis.md](minimal-apis.md) | Validation, TypedResults, filters, modular monolith, vertical slices |
| [security.md](security.md) | JWT auth, CORS, rate limiting, OpenAPI security, middleware order |
| [infrastructure.md](infrastructure.md) | Options, resilience, channels, health checks, caching, Serilog, EF Core, keyed services |
| [testing.md](testing.md) | WebApplicationFactory, integration tests, auth testing |
| [anti-patterns.md](anti-patterns.md) | HttpClient, DI captive, blocking async, N+1 queries |
| [libraries.md](libraries.md) | MediatR, FluentValidation, Mapster, ErrorOr, Polly, Aspire |
---
## Quick Start
```xml
net10.0
14
enable
```
```csharp
var builder = WebApplication.CreateBuilder(args);
// Core services
builder.Services.AddValidation();
builder.Services.AddProblemDetails();
builder.Services.AddOpenApi();
// Security
builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization();
builder.Services.AddRateLimiter(opts => { /* see security.md */ });
// Infrastructure
builder.Services.AddHealthChecks();
builder.Services.AddOutputCache();
// Modules
builder.Services.AddUsersModule();
var app = builder.Build();
// Middleware (ORDER MATTERS - see security.md)
app.UseExceptionHandler();
app.UseHttpsRedirection();
app.UseCors();
app.UseRateLimiter();
app.UseAuthentication();
app.UseAuthorization();
app.UseOutputCache();
app.MapOpenApi();
app.MapHealthChecks("/health");
app.MapUsersEndpoints();
app.Run();
```
---
## Decision Flowcharts
### Result vs Exception
```dot
digraph {
"Error type?" [shape=diamond];
"Expected?" [shape=diamond];
"Result/ErrorOr" [shape=box];
"Exception" [shape=box];
"Error type?" -> "Expected?" [label="domain"];
"Error type?" -> "Exception" [label="infrastructure"];
"Expected?" -> "Result/ErrorOr" [label="yes"];
"Expected?" -> "Exception" [label="no"];
}
```
### IOptions Selection
```dot
digraph {
"Runtime changes?" [shape=diamond];
"Per-request?" [shape=diamond];
"IOptions" [shape=box];
"IOptionsSnapshot" [shape=box];
"IOptionsMonitor" [shape=box];
"Runtime changes?" -> "IOptions" [label="no"];
"Runtime changes?" -> "Per-request?" [label="yes"];
"Per-request?" -> "IOptionsSnapshot" [label="yes"];
"Per-request?" -> "IOptionsMonitor" [label="no"];
}
```
### Channel Type
```dot
digraph {
"Trust producer?" [shape=diamond];
"Can drop?" [shape=diamond];
"Bounded+Wait" [shape=box,style=filled,fillcolor=lightgreen];
"Bounded+Drop" [shape=box];
"Unbounded" [shape=box];
"Trust producer?" -> "Unbounded" [label="yes"];
"Trust producer?" -> "Can drop?" [label="no"];
"Can drop?" -> "Bounded+Drop" [label="yes"];
"Can drop?" -> "Bounded+Wait" [label="no"];
}
```
---
## Key Patterns Summary
### C# 14 Extension Blocks
```csharp
extension(IEnumerable source)
{
public bool IsEmpty => !source.Any();
}
```
### .NET 10 Built-in Validation
```csharp
builder.Services.AddValidation();
app.MapPost("/users", (UserDto dto) => TypedResults.Ok(dto));
```
### TypedResults (Always Use)
```csharp
app.MapGet("/users/{id}", async (int id, IUserService svc) =>
await svc.GetAsync(id) is { } user
? TypedResults.Ok(user)
: TypedResults.NotFound());
```
### Module Pattern
```csharp
public static class UsersModule
{
public static IServiceCollection AddUsersModule(this IServiceCollection s) => s
.AddScoped();
public static IEndpointRouteBuilder MapUsersEndpoints(this IEndpointRouteBuilder app)
{
var g = app.MapGroup("/api/users").WithTags("Users");
g.MapGet("/{id}", GetUser.Handle);
return app;
}
}
```
### HTTP Resilience
```csharp
builder.Services.AddHttpClient()
.AddStandardResilienceHandler();
```
### Error Handling (RFC 9457)
```csharp
builder.Services.AddProblemDetails();
app.UseExceptionHandler();
app.UseStatusCodePages();
```
---
## MANDATORY Patterns (Always Use These)
| Task | ✅ ALWAYS Use | ❌ NEVER Use |
|------|--------------|--------------|
| Extension members | C# 14 `extension()` blocks | Traditional `this` extension methods |
| Property validation | C# 14 `field` keyword | Manual backing fields |
| Null assignment | `obj?.Prop = value` | `if (obj != null) obj.Prop = value` |
| API returns | `TypedResults.Ok()` | `Results.Ok()` |
| Options validation | `.ValidateOnStart()` | Missing validation |
| HTTP resilience | `AddStandardResilienceHandler()` | Manual Polly configuration |
| Timestamps | `DateTime.UtcNow` | `DateTime.Now` |
---
## Quick Reference Card
```
┌─────────────────────────────────────────────────────────────────┐
│ .NET 10 / C# 14 PATTERNS │
├─────────────────────────────────────────────────────────────────┤
│ EXTENSION PROPERTY: extension(IEnumerable s) { │
│ public bool IsEmpty => !s.Any(); │
│ } │
├─────────────────────────────────────────────────────────────────┤
│ FIELD KEYWORD: public string Name { │
│ get => field; │
│ set => field = value?.Trim(); │
│ } │
├─────────────────────────────────────────────────────────────────┤
│ OPTIONS VALIDATION: .BindConfiguration(Section) │
│ .ValidateDataAnnotations() │
│ .ValidateOnStart(); // CRITICAL! │
├─────────────────────────────────────────────────────────────────┤
│ HTTP RESILIENCE: .AddStandardResilienceHandler(); │
├─────────────────────────────────────────────────────────────────┤
│ TYPED RESULTS: TypedResults.Ok(data) │
│ TypedResults.NotFound() │
│ TypedResults.Created(uri, data) │
├─────────────────────────────────────────────────────────────────┤
│ ERROR PATTERN: ErrorOr or user?.Match(...) │
├─────────────────────────────────────────────────────────────────┤
│ IOPTIONS: IOptions → startup, no reload │
│ IOptionsSnapshot → per-request reload │
│ IOptionsMonitor → live + OnChange() │
└─────────────────────────────────────────────────────────────────┘
```
---
## Anti-Patterns Quick Reference
| Anti-Pattern | Fix |
|--------------|-----|
| `new HttpClient()` | Inject `HttpClient` or `IHttpClientFactory` |
| `Results.Ok()` | `TypedResults.Ok()` |
| Manual Polly config | `AddStandardResilienceHandler()` |
| Singleton → Scoped | Use `IServiceScopeFactory` |
| `GetAsync().Result` | `await GetAsync()` |
| Exceptions for flow | Use `ErrorOr` Result pattern |
| `DateTime.Now` | `DateTime.UtcNow` |
| Missing `.ValidateOnStart()` | Always add to Options registration |
See [anti-patterns.md](anti-patterns.md) for complete list.
---
## Libraries Quick Reference
| Library | Package | Purpose |
|---------|---------|---------|
| MediatR | `MediatR` | CQRS |
| FluentValidation | `FluentValidation.DependencyInjectionExtensions` | Validation |
| Mapster | `Mapster.DependencyInjection` | Mapping |
| ErrorOr | `ErrorOr` | Result pattern |
| Polly | `Microsoft.Extensions.Http.Resilience` | Resilience |
| Serilog | `Serilog.AspNetCore` | Logging |
See [libraries.md](libraries.md) for usage examples.