PluginBench
Skill
Review
Audit score 70

dotnet-10-csharp-14

mhagrelius/dotfiles

Build .NET 10 & C# 14 apps with minimal APIs, modular patterns, and modern best practices.

What is dotnet-10-csharp-14?

Guidance for .NET 10 (LTS) and C# 14 development, covering minimal APIs, modular monolith patterns, security, resilience, and modern language features. Use when building new ASP.NET Core applications, refactoring to feature folders, or adopting C# 14 syntax like extension blocks and the field keyword.

  • C# 14 extension blocks, field keyword, and null-conditional assignment patterns
  • Minimal APIs with validation, TypedResults, filters, and modular monolith architecture
  • Security: JWT auth, CORS, rate limiting, middleware ordering, OpenAPI security
  • Infrastructure: Options pattern selection, HTTP resilience, Channels, health checks, caching, Serilog, EF Core
  • Decision flowcharts for result vs exception handling, IOptions selection, and Channel types
  • Anti-pattern detection: HttpClient instantiation, blocking async, captive dependencies, N+1 queries

How to install dotnet-10-csharp-14

npx skills add https://github.com/mhagrelius/dotfiles --skill dotnet-10-csharp-14
Claude Code
Cursor
Windsurf
Cline

How to use dotnet-10-csharp-14

  1. 1.Review the Quick Start section to scaffold a new .NET 10 project with LangVersion 14 and Nullable enabled
  2. 2.Choose your architectural pattern: modular monolith, vertical slices, or feature folders using the Module Pattern example
  3. 3.Use the decision flowcharts to determine error handling strategy (Result vs Exception), Options type, and Channel configuration
  4. 4.Apply MANDATORY patterns from the reference table: extension blocks, TypedResults, field keyword, Options validation
  5. 5.Implement security middleware in the correct order: ExceptionHandler → HTTPS → CORS → RateLimiter → Authentication → Authorization
  6. 6.Configure HTTP resilience with AddStandardResilienceHandler() and add health checks, caching, and problem details
  7. 7.Reference the detail files (csharp-14.md, minimal-apis.md, security.md, infrastructure.md) for deep dives on specific topics

Use cases

Good for
  • Building a new ASP.NET Core 10 minimal API with modular feature folders and vertical slices
  • Refactoring legacy code to use C# 14 extension blocks and the field keyword for cleaner properties
  • Implementing JWT authentication with rate limiting and proper middleware ordering in a secure API
  • Setting up HTTP resilience with Polly and configuring Options validation at startup
  • Writing integration tests with WebApplicationFactory and testing authenticated endpoints
Who it's for
  • Backend developers building .NET 10 applications
  • Teams adopting C# 14 language features and modern patterns
  • Developers implementing minimal APIs instead of traditional MVC
  • Architects designing modular monoliths with feature-based organization
  • DevOps/infrastructure engineers configuring resilience, caching, and health checks

dotnet-10-csharp-14 FAQ

When should I use IOptions vs IOptionsSnapshot vs IOptionsMonitor?

Use IOptions<T> for startup-only configuration with no runtime changes. Use IOptionsSnapshot<T> when you need per-request reloading. Use IOptionsMonitor<T> for live configuration changes with OnChange() callbacks. See the IOptions Selection flowchart.

Should I throw exceptions or return ErrorOr/Result types?

Use ErrorOr<T> or Result<T> for expected domain errors. Throw exceptions only for unexpected infrastructure failures. The Result vs Exception flowchart guides this decision.

What's the difference between extension blocks and traditional extension methods?

C# 14 extension blocks (extension<T>(source) { }) are cleaner, more readable, and the mandatory pattern. Traditional this extension methods are outdated and should not be used.

Why is middleware order critical in ASP.NET Core?

Middleware executes in declaration order. Incorrect order breaks security: UseExceptionHandler must come first, then HTTPS, CORS, RateLimiter, Authentication, Authorization. See security.md for the full order.

Should I use Results.Ok() or TypedResults.Ok()?

Always use TypedResults.Ok(). It provides better type safety, OpenAPI documentation, and is the modern .NET 10 pattern. Results.Ok() is legacy.

Full instructions (SKILL.md)

Source of truth, from mhagrelius/dotfiles.


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 | C# 14 | ASP.NET Core 10

Detail Files

FileTopics
csharp-14.mdExtension blocks, field keyword, null-conditional assignment
minimal-apis.mdValidation, TypedResults, filters, modular monolith, vertical slices
security.mdJWT auth, CORS, rate limiting, OpenAPI security, middleware order
infrastructure.mdOptions, resilience, channels, health checks, caching, Serilog, EF Core, keyed services
testing.mdWebApplicationFactory, integration tests, auth testing
anti-patterns.mdHttpClient, DI captive, blocking async, N+1 queries
libraries.mdMediatR, FluentValidation, Mapster, ErrorOr, Polly, Aspire

Quick Start

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <LangVersion>14</LangVersion>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>
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

digraph {
    "Error type?" [shape=diamond];
    "Expected?" [shape=diamond];
    "Result<T>/ErrorOr" [shape=box];
    "Exception" [shape=box];
    "Error type?" -> "Expected?" [label="domain"];
    "Error type?" -> "Exception" [label="infrastructure"];
    "Expected?" -> "Result<T>/ErrorOr" [label="yes"];
    "Expected?" -> "Exception" [label="no"];
}

IOptions Selection

digraph {
    "Runtime changes?" [shape=diamond];
    "Per-request?" [shape=diamond];
    "IOptions<T>" [shape=box];
    "IOptionsSnapshot<T>" [shape=box];
    "IOptionsMonitor<T>" [shape=box];
    "Runtime changes?" -> "IOptions<T>" [label="no"];
    "Runtime changes?" -> "Per-request?" [label="yes"];
    "Per-request?" -> "IOptionsSnapshot<T>" [label="yes"];
    "Per-request?" -> "IOptionsMonitor<T>" [label="no"];
}

Channel Type

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

extension<T>(IEnumerable<T> source)
{
    public bool IsEmpty => !source.Any();
}

.NET 10 Built-in Validation

builder.Services.AddValidation();
app.MapPost("/users", (UserDto dto) => TypedResults.Ok(dto));

TypedResults (Always Use)

app.MapGet("/users/{id}", async (int id, IUserService svc) =>
    await svc.GetAsync(id) is { } user
        ? TypedResults.Ok(user)
        : TypedResults.NotFound());

Module Pattern

public static class UsersModule
{
    public static IServiceCollection AddUsersModule(this IServiceCollection s) => s
        .AddScoped<IUserService, UserService>();

    public static IEndpointRouteBuilder MapUsersEndpoints(this IEndpointRouteBuilder app)
    {
        var g = app.MapGroup("/api/users").WithTags("Users");
        g.MapGet("/{id}", GetUser.Handle);
        return app;
    }
}

HTTP Resilience

builder.Services.AddHttpClient<IApi, ApiClient>()
    .AddStandardResilienceHandler();

Error Handling (RFC 9457)

builder.Services.AddProblemDetails();
app.UseExceptionHandler();
app.UseStatusCodePages();

MANDATORY Patterns (Always Use These)

Task✅ ALWAYS Use❌ NEVER Use
Extension membersC# 14 extension<T>() blocksTraditional this extension methods
Property validationC# 14 field keywordManual backing fields
Null assignmentobj?.Prop = valueif (obj != null) obj.Prop = value
API returnsTypedResults.Ok()Results.Ok()
Options validation.ValidateOnStart()Missing validation
HTTP resilienceAddStandardResilienceHandler()Manual Polly configuration
TimestampsDateTime.UtcNowDateTime.Now

Quick Reference Card

┌─────────────────────────────────────────────────────────────────┐
│                    .NET 10 / C# 14 PATTERNS                      │
├─────────────────────────────────────────────────────────────────┤
│ EXTENSION PROPERTY:  extension<T>(IEnumerable<T> 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<User> or user?.Match(...)          │
├─────────────────────────────────────────────────────────────────┤
│ IOPTIONS:            IOptions<T>        → startup, no reload    │
│                      IOptionsSnapshot<T> → per-request reload   │
│                      IOptionsMonitor<T>  → live + OnChange()    │
└─────────────────────────────────────────────────────────────────┘

Anti-Patterns Quick Reference

Anti-PatternFix
new HttpClient()Inject HttpClient or IHttpClientFactory
Results.Ok()TypedResults.Ok()
Manual Polly configAddStandardResilienceHandler()
Singleton → ScopedUse IServiceScopeFactory
GetAsync().Resultawait GetAsync()
Exceptions for flowUse ErrorOr<T> Result pattern
DateTime.NowDateTime.UtcNow
Missing .ValidateOnStart()Always add to Options registration

See anti-patterns.md for complete list.


Libraries Quick Reference

LibraryPackagePurpose
MediatRMediatRCQRS
FluentValidationFluentValidation.DependencyInjectionExtensionsValidation
MapsterMapster.DependencyInjectionMapping
ErrorOrErrorOrResult pattern
PollyMicrosoft.Extensions.Http.ResilienceResilience
SerilogSerilog.AspNetCoreLogging

See libraries.md for usage examples.