dotnet-best-practices
SKILL.md
.NET Best Practices
Comprehensive development guide for modern .NET applications with C# 12+. Contains 100+ rules across 11 categories, prioritized by impact to guide code generation, refactoring, and reviews.
When to Apply
Reference these guidelines when:
- Writing new C# code or refactoring existing code
- Designing APIs, services, or domain models
- Implementing data access layers or async operations
- Reviewing code for performance, correctness, or maintainability
- Setting up dependency injection, configuration, or logging
- Writing tests (unit, integration, snapshot)
- Building distributed systems or microservices
Rule Categories by Priority
| Priority | Category | Impact | Prefix | Rules |
|---|---|---|---|---|
| 1 | Error Handling | CRITICAL | error- |
8 |
| 2 | Async Patterns | CRITICAL | async- |
9 |
| 3 | Type Design | HIGH | type- |
10 |
| 4 | Database Performance | HIGH | db- |
12 |
| 5 | API Design | MEDIUM-HIGH | api- |
8 |
| 6 | Dependency Injection | MEDIUM | di- |
7 |
| 7 | Architecture | MEDIUM | arch- |
8 |
| 8 | Serialization | MEDIUM | serial- |
6 |
| 9 | Performance | LOW-MEDIUM | perf- |
12 |
| 10 | Logging | LOW-MEDIUM | log- |
6 |
| 11 | Testing | LOW | test- |
8 |
Total: 94 Rules
Quick Reference
1. Error Handling (CRITICAL)
error-result-pattern- Return Result for expected errors, not exceptionserror-validation-boundaries- Validate at handler entry points, fail fasterror-guard-clauses- Use guard clauses for null/range checkserror-exception-filters- Usecatch whenfor selective exception handlingerror-global-handlers- Implement middleware for unhandled exceptionserror-never-catch-all- Avoid catching all exceptions without rethrowingerror-custom-exceptions- Use built-in exceptions, avoid custom oneserror-exception-properties- Add context to exceptions via Data property
2. Async Patterns (CRITICAL)
async-cancellation-token- Always accept CancellationToken in async methodsasync-all-the-way- Never block on async code with .Result or .Wait()async-valuetask- Use ValueTask for hot paths with synchronous completionsasync-iasyncenumerable- Use IAsyncEnumerable for streaming dataasync-parallel-foreach- Use Parallel.ForEachAsync for bounded concurrencyasync-channel- Use System.Threading.Channels for producer-consumerasync-semaphore- Use SemaphoreSlim for rate limitingasync-configureawait- Use ConfigureAwait(false) in library codeasync-avoid-async-void- Never use async void except for event handlers
3. Type Design (HIGH)
type-records-for-dtos- Use records for immutable DTOstype-readonly-record-struct- Use readonly record struct for value objectstype-seal-classes- Seal classes by default unless designed for inheritancetype-composition-over-inheritance- Prefer composition over inheritancetype-nullable-reference-types- Enable nullable reference types, handle nulls explicitlytype-pattern-matching- Use switch expressions and pattern matchingtype-primary-constructors- Use primary constructors for simple classestype-immutability-default- Design types to be immutable by defaulttype-explicit-conversions- Avoid implicit conversions for value objectstype-no-public-setters- Use init or constructor, not public setters
4. Database Performance (HIGH)
db-read-write-separation- Separate read models from write modelsdb-notracking-default- Configure NoTracking by default in EF Coredb-row-limits- Always apply row limits to queriesdb-n-plus-one- Avoid N+1 queries with Include or batch fetchingdb-cartesian-explosion- Use AsSplitQuery to prevent Cartesian productsdb-compiled-queries- Use EF.CompileAsyncQuery for hot pathsdb-connection-pooling- Use NpgsqlDataSource for connection poolingdb-optimistic-concurrency- Use RowVersion for concurrent updatesdb-projections-over-entities- Use Select projections, not full entitiesdb-no-application-joins- Do joins in SQL, never in application codedb-bulk-operations- Use ExecuteUpdate/Delete for bulk modificationsdb-no-generic-repositories- Use purpose-built stores, not generic repositories
5. API Design (MEDIUM-HIGH)
api-accept-abstractions- Accept IEnumerable/IReadOnlyCollection in parametersapi-return-specific-types- Return IReadOnlyList/IReadOnlyCollection from methodsapi-readonly-collections- Return immutable collections from public APIsapi-method-overloads- Use overloads for common scenarios, not optionalsapi-extension-methods- Use extension methods for fluent APIsapi-fluent-builders- Use builder pattern for complex object constructionapi-async-suffix- Suffix async methods with Asyncapi-avoid-out-params- Return tuples or records instead of out parameters
6. Dependency Injection (MEDIUM)
di-extension-methods- Group service registrations in extension methodsdi-lifetime-management- Understand Singleton, Scoped, Transient lifetimesdi-options-pattern- Use IOptions for configurationdi-validate-on-start- Call ValidateOnStart() for configuration validationdi-keyed-services- Use keyed services for multiple implementationsdi-avoid-service-locator- Never inject IServiceProvider as service locatordi-no-scoped-in-singleton- Never inject Scoped services into Singleton
7. Architecture (MEDIUM)
arch-vertical-slice- Organize by feature (vertical slices), not layersarch-feature-organization- One feature per file with all related codearch-static-handlers- Use static handler methods in featuresarch-module-boundaries- Use InternalsVisibleTo for module boundariesarch-feature-flags- Implement runtime feature togglingarch-background-services- Use IHostedService for background workarch-minimal-apis- Prefer minimal APIs over controller-based APIsarch-map-endpoints-per-feature- Each feature maps its own endpoints
8. Serialization (MEDIUM)
serial-system-text-json- Use System.Text.Json, not Newtonsoft.Jsonserial-source-generators- Use JsonSerializerContext source generatorsserial-protobuf- Use Protobuf for actor systems and event sourcingserial-messagepack- Use MessagePack for high-performance scenariosserial-wire-compatibility- Design for forward/backward compatibilityserial-no-type-names- Never embed type names in wire format
9. Performance (LOW-MEDIUM)
perf-span-and-memory- Use Span/Memory for buffer operationsperf-defer-enumeration- Don't materialize IEnumerable until necessaryperf-frozen-collections- Use FrozenDictionary/FrozenSet for static dataperf-string-interpolation- Use string interpolation, not concatenationperf-list-capacity- Initialize List with capacity when size is knownperf-dictionary-trygetvalue- Use TryGetValue instead of ContainsKey + indexerperf-array-pool- Use ArrayPool for temporary large buffersperf-object-pool- Use ObjectPool for expensive-to-create objectsperf-compiled-regex- Use GeneratedRegex source generatorperf-static-pure-functions- Prefer static methods for pure functionsperf-stackalloc- Use stackalloc for small temporary buffersperf-avoid-closure-allocations- Cache delegates to avoid closure allocations
10. Logging (LOW-MEDIUM)
log-structured-logging- Use structured logging with named parameterslog-logger-message-define- Use LoggerMessage.Define for high-performance logginglog-correlation-ids- Implement request correlation via Activity.Currentlog-activity-tracing- Use System.Diagnostics.Activity for distributed tracinglog-log-levels- Use appropriate log levels (Trace/Debug/Info/Warning/Error/Critical)log-avoid-string-interpolation- Pass message template, not interpolated strings
11. Testing (LOW)
test-testcontainers- Use TestContainers for integration tests, not mockstest-snapshot-testing- Use Verify for snapshot testing APIs and outputstest-arrange-act-assert- Follow AAA pattern for test structuretest-fakes-vs-mocks- Prefer hand-written fakes over mocking librariestest-data-builders- Use builder pattern for complex test datatest-integration-webappfactory- Use WebApplicationFactory for API integration teststest-one-assertion-per-test- Focus each test on a single assertiontest-descriptive-names- Use descriptive test names that explain the scenario
How to Use
Read individual rule files for detailed explanations and code examples:
rules/error-result-pattern.md
rules/async-cancellation-token.md
rules/db-notracking-default.md
Each rule file contains:
- Brief explanation of why it matters
- ā Incorrect code example with explanation
- ā Correct code example with explanation
- Additional context and references
Full Compiled Document
For the complete guide with all rules expanded inline: AGENTS.md
This document is optimized for LLM context loading and contains all rules with full code examples.
Anti-Patterns Summary
Common mistakes to avoid:
| Anti-Pattern | Rule |
|---|---|
| Throwing exceptions for business logic | error-result-pattern |
| Blocking on async code with .Result | async-all-the-way |
| Mutable DTOs with public setters | type-records-for-dtos |
| Queries without row limits | db-row-limits |
| N+1 query problems | db-n-plus-one |
| Returning List from APIs | api-readonly-collections |
| Massive Program.cs with 200+ DI registrations | di-extension-methods |
| Organizing by technical layers | arch-vertical-slice |
| Using Newtonsoft.Json | serial-system-text-json |
| String concatenation in loops | perf-string-interpolation |
| Mocking databases in tests | test-testcontainers |
Resources
- .NET Documentation: https://learn.microsoft.com/en-us/dotnet/
- C# Language Reference: https://learn.microsoft.com/en-us/dotnet/csharp/
- EF Core Performance: https://learn.microsoft.com/en-us/ef/core/performance/
- ASP.NET Core: https://learn.microsoft.com/en-us/aspnet/core/
- Performance Best Practices: https://learn.microsoft.com/en-us/dotnet/standard/performance/
Weekly Installs
1
Repository
akires47/agent-skillsFirst Seen
Jan 29, 2026
Installed on
opencode1
cursor1
claude-code1