$ npx rulesync-cli pull✓ Wrote CLAUDE.md (2 rulesets)# Coding Standards- Always use async/await- Prefer named exports
Rule Writing

CLAUDE.md for .NET and ASP.NET

AI generates .NET Framework patterns in .NET 8 projects. Rules for minimal APIs, EF Core, dependency injection, middleware, and modern C# in ASP.NET.

8 min read·September 24, 2025

AI generates .NET Framework 4.x patterns in .NET 8 projects

Minimal APIs, EF Core, built-in DI, middleware pipeline, and ProblemDetails

Why .NET and ASP.NET Need Version-Specific Rules

.NET has undergone a complete reinvention — from .NET Framework (Windows-only, IIS, System.Web) to .NET 8 (cross-platform, Kestrel, minimal APIs). AI assistants conflate these eras constantly: generating Startup.cs when .NET 6+ uses Program.cs with top-level statements, using System.Web when ASP.NET Core uses Microsoft.AspNetCore, and creating full MVC controllers when minimal APIs would suffice.

The version gap is enormous. .NET Framework 4.x code looks nothing like .NET 8 code — different namespaces, different project structure, different hosting model, different DI container. AI trained on Stack Overflow answers from 2015-2020 generates patterns that don't compile on .NET 8.

These rules target .NET 8 with ASP.NET Core. Specify your .NET version prominently — it's the single most important rule for preventing era-mismatch code generation.

Rule 1: Minimal APIs vs Controllers

The rule: 'Use minimal APIs for simple endpoints: app.MapGet("/users/{id}", async (int id, IUserService service) => await service.GetById(id)). Use controllers for endpoints that need: model binding with [FromBody]/[FromQuery] complexity, action filters, or extensive documentation attributes. Default to minimal APIs — they're simpler, faster to write, and sufficient for most endpoints.'

For organizing minimal APIs: 'Group endpoints with MapGroup: var users = app.MapGroup("/users"); users.MapGet("/", GetAll); users.MapPost("/", Create). Use extension methods to register route groups: app.MapUserEndpoints(). This keeps Program.cs clean while maintaining the minimal API pattern.'

For top-level statements: '.NET 8 uses Program.cs with top-level statements — no Startup.cs, no Main method, no class wrapper. AI generates the old Startup.cs + Program.cs two-file pattern from .NET 5 training data. One file (Program.cs) configures services and the middleware pipeline.'

  • Minimal APIs for simple CRUD — controllers for complex binding/filters
  • MapGroup for organized routes — extension methods for registration
  • Top-level Program.cs — no Startup.cs, no Main method, no class wrapper
  • builder.Services for DI registration — app.Use* for middleware pipeline
  • WebApplication.CreateBuilder(args) — single entry point pattern
⚠️ No Startup.cs

.NET 6+ uses top-level Program.cs — no Startup.cs, no Main method. AI generates the two-file Startup pattern from .NET 5 training data. One file does everything: services, middleware, endpoints.

Rule 2: Entity Framework Core Patterns

The rule: 'Use EF Core for all database operations. Configure relationships in OnModelCreating with Fluent API — not data annotations for anything beyond simple attributes. Use migrations: dotnet ef migrations add, dotnet ef database update. Use AsNoTracking() for read-only queries. Use ExecuteUpdateAsync/ExecuteDeleteAsync for bulk operations (EF Core 7+).'

For DbContext: 'Register DbContext with AddDbContext<AppDbContext>(options => options.UseNpgsql(connectionString)). Use one DbContext per bounded context — not one god-context for the entire app. Inject via constructor: public UsersController(AppDbContext db). Use IDbContextFactory for background services that outlive a single request.'

For performance: 'Use IQueryable for building queries — materialize with ToListAsync() only when needed. Use Split Queries for complex includes: .AsSplitQuery(). Use compiled queries for hot paths: EF.CompileAsyncQuery. Profile with database logging: optionsBuilder.LogTo(Console.WriteLine). Never use lazy loading in web applications — it causes N+1.'

Rule 3: Built-In Dependency Injection

The rule: 'Use .NET's built-in DI container — never install a third-party container (Autofac, Ninject) unless you have specific requirements it can't meet. Register services in Program.cs: builder.Services.AddScoped<IUserService, UserService>(). Use AddScoped for request-scoped, AddTransient for stateless, AddSingleton for app-wide. Inject via constructor — never use ServiceLocator pattern.'

For interfaces: 'Define interfaces for services that need to be mocked in tests: IUserService, IEmailService. Don't create interfaces for every class — only for services that have multiple implementations or need mocking. Repository pattern is optional over EF Core — DbContext is already a unit of work.'

For options pattern: 'Use IOptions<T> for configuration: builder.Services.Configure<SmtpSettings>(builder.Configuration.GetSection("Smtp")). Inject IOptions<SmtpSettings> in services. Use records for option types: record SmtpSettings(string Host, int Port, string Username). Validate options with ValidateDataAnnotations() or ValidateOnStart().'

  • Built-in DI: AddScoped, AddTransient, AddSingleton — no third-party containers
  • Constructor injection — never ServiceLocator or manual new
  • Interfaces for mockable services — not for every class
  • IOptions<T> for typed configuration — records for option types
  • ValidateOnStart() for startup-time config validation — fail fast
💡 Built-In DI Is Enough

.NET's built-in DI handles 99% of scenarios. AI installs Autofac or Ninject from training data. AddScoped/AddTransient/AddSingleton + constructor injection covers most apps without a third-party container.

Rule 4: Middleware Pipeline

The rule: 'Middleware order matters — configure in Program.cs in this sequence: app.UseExceptionHandler, app.UseHsts (prod), app.UseHttpsRedirection, app.UseStaticFiles, app.UseRouting, app.UseCors, app.UseAuthentication, app.UseAuthorization, app.MapControllers/MapEndpoints. Reordering auth before routing or CORS after endpoints causes silent security failures.'

For error handling: 'Use ProblemDetails (RFC 7807) for all error responses: builder.Services.AddProblemDetails(). Use app.UseExceptionHandler() for global error handling. Map exceptions to ProblemDetails: StatusCodes.Status404NotFound, Status400BadRequest, Status500InternalServerError. Never return raw exception messages to clients — log details server-side, return ProblemDetails to clients.'

For custom middleware: 'Implement IMiddleware or use the request delegate pattern: app.Use(async (context, next) => { ... await next(context); ... }). Middleware before next() handles the request, after next() handles the response. Use short-circuit middleware for early returns: if (unauthorized) { context.Response.StatusCode = 401; return; }.'

ℹ️ Middleware Order = Security

Authentication before Authorization, both after Routing. CORS before Auth. ExceptionHandler first. Reordering silently breaks security — the pipeline is ordered, not declarative.

Rule 5: Testing ASP.NET Applications

The rule: 'Use xUnit for testing (ASP.NET's default). Use WebApplicationFactory<Program> for integration tests: var client = factory.CreateClient(). Use Moq or NSubstitute for mocking. Use FluentAssertions for readable assertions: result.Should().BeOfType<OkObjectResult>().Which.Value.Should().BeEquivalentTo(expected). Use Testcontainers for database integration tests.'

For integration tests: 'WebApplicationFactory boots your entire app in-memory — test the full pipeline: middleware, DI, routing, controllers. Override services for testing: factory.WithWebHostBuilder(builder => builder.ConfigureServices(services => services.AddScoped<IEmailService>(_ => mockEmail))). Test endpoints with the HttpClient from factory.CreateClient().'

For unit tests: 'Test services independently by constructing with mocked dependencies. Test controllers independently by constructing with mocked services. Use Arrange-Act-Assert pattern. Use [Theory] with [InlineData] for parameterized tests. Use [Fact] for single-case tests.'

Complete .NET / ASP.NET Rules Template

Consolidated rules for .NET 8 + ASP.NET Core projects.

  • .NET 8 — top-level Program.cs — no Startup.cs, no Main — jakarta namespace awareness
  • Minimal APIs for simple CRUD — controllers for complex scenarios — MapGroup for organization
  • EF Core: Fluent API, migrations, AsNoTracking, ExecuteUpdateAsync — never lazy loading in web
  • Built-in DI: AddScoped/Transient/Singleton — IOptions<T> — ValidateOnStart()
  • Middleware order: ExceptionHandler → HTTPS → Static → Routing → CORS → Auth → Endpoints
  • ProblemDetails for all errors — never raw exception messages to clients
  • xUnit + WebApplicationFactory — FluentAssertions — Moq — Testcontainers
  • dotnet format for style — SonarAnalyzer for quality — minimal API analyzers in CI
CLAUDE.md for .NET and ASP.NET — RuleSync Blog