dotnet-best-practices
3
总安装量
3
周安装量
#61574
全站排名
安装命令
npx skills add https://github.com/kaiboo404/agent-skills-with-project-template --skill dotnet-best-practices
Agent 安装分布
gemini-cli
3
github-copilot
3
codex
3
kimi-cli
3
opencode
3
amp
3
Skill 文档
.NET Best Practices
Production-grade .NET development guidance from top thought leaders
When to Apply
Reference these guidelines when:
- Architecting new .NET projects (choosing between Simple/Clean/Modular patterns)
- Writing or reviewing C# code for performance
- Implementing CQRS or mediator patterns
- Optimizing Entity Framework Core queries
- Designing RESTful APIs
- Debugging production performance issues
- Upgrading to .NET 8/9 features
Architecture Decision Matrix
Choose architecture based on project complexity and team size:
| Project Type | Team Size | Recommended Architecture | Reference |
|---|---|---|---|
| Simple API/MVP | 1-2 devs | Simple Layered | arch-simple.md |
| Rapid development | 1-4 devs | Vertical Slice | arch-vertical-slice.md |
| Medium complexity | 2-5 devs | Clean Architecture | arch-clean.md |
| Large/Enterprise | 5+ devs | Modular Monolith | arch-modular-monolith.md |
| Scaling required | 10+ devs | Microservices (migrate later) | Start with Modular Monolith |
[!IMPORTANT] Start simple, evolve when pain points emerge. Over-engineering kills velocity. A well-structured simple API can handle most business needs.
Rule Categories by Priority
| Priority | Category | Impact | Prefix | Reference |
|---|---|---|---|---|
| 1 | Architecture Selection | CRITICAL | arch- |
Project Sizing |
| 2 | Performance & Memory | CRITICAL | perf- |
Memory, Async |
| 3 | Distributed Systems | CRITICAL | dist- |
Distributed Systems |
| 4 | Database & EF Core | HIGH | ef- |
EF Core |
| 5 | CQRS & Mediator | HIGH | cqrs- |
CQRS Patterns |
| 6 | API Design | MEDIUM-HIGH | api- |
API Design |
| 7 | Error & Logging | MEDIUM | error- |
Pitfalls |
| 8 | Testing | MEDIUM | test- |
Testing |
| 9 | Modern .NET | MEDIUM | dotnet- |
.NET 8/9 |
Quick Reference
1. Architecture Selection (CRITICAL)
Simple API Pattern â For MVPs, internal tools, small teams:
// â
Simple: Controllers â Services â DbContext
public class ProductsController : ControllerBase
{
private readonly ProductService _service;
public ProductsController(ProductService service) => _service = service;
[HttpGet("{id}")]
public async Task<ActionResult<ProductDto>> Get(int id)
=> Ok(await _service.GetByIdAsync(id));
}
Clean Architecture â For complex domain logic, medium teams:
Domain â Application â Infrastructure â Presentation
(no dependencies on outer layers)
Modular Monolith â For enterprise, large teams preparing for microservices:
Each module = independent bounded context with own:
- Domain layer
- Application layer
- Infrastructure (can share DB initially)
- Public API (contracts for inter-module communication)
2. Performance & Memory (CRITICAL)
| Rule | Impact | Quick Fix |
|---|---|---|
perf-span |
CRITICAL | Use Span<T> for slicing without allocation |
perf-arraypool |
HIGH | Use ArrayPool<T>.Shared for temp buffers |
perf-valuetask |
HIGH | Use ValueTask in hot paths when result often cached |
perf-struct |
MEDIUM | Use readonly struct for small, short-lived data |
perf-stringbuilder |
MEDIUM | Use StringBuilder for 4+ concatenations |
// â BAD: Allocates new array for each slice
var subset = array.Skip(10).Take(20).ToArray();
// â
GOOD: Zero-allocation slice
ReadOnlySpan<int> subset = array.AsSpan(10, 20);
// â BAD: New array allocation each call
public byte[] GetBuffer() => new byte[4096];
// â
GOOD: Rent from pool, return when done
public byte[] GetBuffer() => ArrayPool<byte>.Shared.Rent(4096);
// Remember: ArrayPool<byte>.Shared.Return(buffer);
3. Entity Framework Core (HIGH)
| Rule | Impact | Quick Fix |
|---|---|---|
ef-notracking |
CRITICAL | Use .AsNoTracking() for read-only queries |
ef-projection |
CRITICAL | Use .Select() to fetch only needed columns |
ef-n+1 |
CRITICAL | Use .Include() or projection to prevent N+1 |
ef-batch |
HIGH | Use AddRange(), call SaveChanges() once |
ef-compiled |
MEDIUM | Use compiled queries for hot paths |
// â BAD: Tracks entities + fetches all columns + N+1
var orders = await _context.Orders.ToListAsync();
foreach (var order in orders)
Console.WriteLine(order.Customer.Name); // N+1!
// â
GOOD: No tracking, projection, eager load
var orders = await _context.Orders
.AsNoTracking()
.Include(o => o.Customer)
.Select(o => new OrderDto(o.Id, o.Customer.Name, o.Total))
.ToListAsync();
4. CQRS & Mediator (HIGH)
| Rule | Impact | Description |
|---|---|---|
cqrs-separate |
HIGH | Commands mutate state, Queries read data |
cqrs-dapper-query |
HIGH | Use Dapper for complex read queries |
cqrs-ef-command |
MEDIUM | Use EF Core for commands (change tracking) |
cqrs-no-mediatr |
LOW | Consider custom interfaces over MediatR for simplicity |
// Command: Uses EF Core for rich domain model
public class CreateOrderHandler : ICommandHandler<CreateOrderCommand, Guid>
{
private readonly AppDbContext _context;
public async Task<Guid> Handle(CreateOrderCommand cmd, CancellationToken ct)
{
var order = Order.Create(cmd.CustomerId, cmd.Items); // Domain logic
_context.Orders.Add(order);
await _context.SaveChangesAsync(ct);
return order.Id;
}
}
// Query: Uses Dapper for performance
public class GetOrdersHandler : IQueryHandler<GetOrdersQuery, IEnumerable<OrderDto>>
{
private readonly ISqlConnectionFactory _sql;
public async Task<IEnumerable<OrderDto>> Handle(GetOrdersQuery query, CancellationToken ct)
{
using var conn = _sql.Create();
return await conn.QueryAsync<OrderDto>(
"SELECT Id, CustomerName, Total FROM Orders WHERE CustomerId = @Id",
new { query.CustomerId });
}
}
5. API Design (MEDIUM-HIGH)
| Rule | Impact | Quick Fix |
|---|---|---|
api-versioning |
HIGH | Use URL versioning: /api/v1/products |
api-pagination |
HIGH | Always paginate list endpoints |
api-problem-details |
MEDIUM | Return RFC 7807 ProblemDetails for errors |
api-async |
MEDIUM | All I/O endpoints must be async |
// â
GOOD: Versioned, paginated, proper response types
[ApiController]
[Route("api/v1/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet]
[ProducesResponseType<PagedResult<ProductDto>>(200)]
public async Task<ActionResult<PagedResult<ProductDto>>> GetAll(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20)
{
var result = await _service.GetPagedAsync(page, pageSize);
return Ok(result);
}
[HttpGet("{id:guid}")]
[ProducesResponseType<ProductDto>(200)]
[ProducesResponseType<ProblemDetails>(404)]
public async Task<ActionResult<ProductDto>> GetById(Guid id)
{
var product = await _service.GetByIdAsync(id);
return product is null ? NotFound() : Ok(product);
}
}
6. Async/Await Patterns (MEDIUM)
| Rule | Impact | Quick Fix |
|---|---|---|
async-all-way |
CRITICAL | Never mix sync and async (causes deadlocks) |
async-no-result |
CRITICAL | Never use .Result or .Wait() |
async-configureawait |
MEDIUM | Use ConfigureAwait(false) in libraries |
async-cancellation |
MEDIUM | Always accept and use CancellationToken |
// â BAD: Deadlock risk in ASP.NET
public string GetData()
{
return GetDataAsync().Result; // DEADLOCK!
}
// â
GOOD: Async all the way
public async Task<string> GetDataAsync(CancellationToken ct = default)
{
var data = await _httpClient.GetStringAsync(url, ct);
return data;
}
7. Common Production Pitfalls
| Pitfall | Impact | Solution |
|---|---|---|
| HttpClient per-request | CRITICAL | Use IHttpClientFactory |
Catching Exception |
HIGH | Catch specific exceptions |
| Logging sensitive data | HIGH | Use structured logging, sanitize |
| DateTime.Now | MEDIUM | Use DateTime.UtcNow or TimeProvider |
| String concatenation in loops | MEDIUM | Use StringBuilder |
// â BAD: Port exhaustion
public class MyService
{
public async Task CallApi()
{
using var client = new HttpClient(); // DON'T!
await client.GetAsync("...");
}
}
// â
GOOD: Factory-managed lifecycle
public class MyService
{
private readonly IHttpClientFactory _factory;
public async Task CallApi()
{
var client = _factory.CreateClient();
await client.GetAsync("...");
}
}
Searching References
# Find patterns by keyword
grep -l "ef core" references/
grep -l "async" references/
grep -l "memory" references/
grep -l "cqrs" references/
Problem â Reference Mapping
| Problem | Start With |
|---|---|
| Choosing architecture | arch-project-sizing.md |
| Simple API needed | arch-simple.md |
| Rapid dev / feature-focused | arch-vertical-slice.md |
| Complex domain logic | arch-clean.md |
| Large team, scaling | arch-modular-monolith.md |
| Slow queries | ef-core-performance.md |
| Memory issues | perf-memory.md |
| Deadlocks/async bugs | perf-async.md |
| API design questions | api-design.md |
| Implementing CQRS | cqrs-patterns.md |
| Implementing CQRS | cqrs-patterns.md |
| Event Sourcing | event-sourcing-wolverine.md |
| Distributed failures | distributed-systems.md |
| .NET 8/9 migration | dotnet-8-9-features.md |
| Test strategy | testing-strategies.md |
| Production bugs | production-pitfalls.md |
| Security issues | production-pitfalls-security.md |
| Middleware/DI issues | aspnet-core-internals.md |
Attribution
Based on teachings from Original Thinkers:
- David Fowler – Async/Await, Concurrency, ASP.NET Core (Microsoft)
- Jeremy D. Miller – Wolverine, Marten, Vertical Slice, Event Sourcing
- Julie Lerman – Entity Framework Core, DDD
- Nick Chapsas – C# Performance, Span, Benchmarking
- Andrew Lock – ASP.NET Core Internals, Middleware, DI
- Milan Jovanovic – Clean Architecture, CQRS, Functional Core
- Julio Casal – Modular Monoliths, Microservices Transition
- Gui Ferreira – Memory Management, Struct Layout
- Mukesh Murugan – API Design, Idempotency, Global Errors
- Dr. Milan Milanovic – Distributed Systems, Resilience Patterns