This blog provides a comprehensive checklist for developers working on .NET 10 (C#) back‑end services. It collects authoritative guidelines from Microsoft’s documentation and industry best practices. The goal is to ensure consistent, secure and maintainable code across all projects
Architecture & Layering
Domain‑Driven Design (DDD) and Clean Architecture: Keep business logic in the domain layer and infrastructure concerns (databases, messaging, external services) in separate layers. Use interfaces in the domain layer and provide implementations in the infrastructure layer so that your domain model is decoupled from specific technology choices.
Repository Pattern: Each aggregate root should have a corresponding repository interface in the domain layer. Repositories encapsulate data access logic and are the sole channel for updating aggregates; this helps maintain transactional consistency (source). Implementations live in the infrastructure layer.
Separation of Commands and Queries: Use command services (or Domain services) for updates and separate query services for reads. This ensures that repositories are used primarily for writes and can help manage transactional boundaries.
Dependency Injection (DI)
Centralize service registration in
Program.cs: Register all dependencies (services, repositories, HTTP clients, etc.) at startup. Use constructor injection throughout the codebase instead of service locators or static accessors (source).Choose appropriate service lifetimes:
Inject options, not
IConfigurationdirectly: Use the options pattern to bind configuration sections to strongly‑typed classes and register them viaConfigure<TOptions>(source).Avoid injecting concrete classes: Depend on abstractions (interfaces) to decouple implementations and allow for easier testing.
Entity Framework Core & Data Access
Context lifetime: Register
DbContextasScoped. Use one context per request to manage unit of work.Tracking vs. No‑Tracking queries: Use tracking only when you need to update entities; for read‑only queries call
.AsNoTracking()to avoid overhead and improve performance(source). Consider global no‑tracking for queries when appropriate.Repository guidelines: Encapsulate queries and commands within repository methods; avoid leaking
IQueryableoutside the repository. Compose only the necessary data (projections) and avoid returning entire entities when only a subset of fields is required.Migrations: Use EF Core migrations to evolve the database schema; generate migrations for each change and keep them under source control. Test migrations against a copy of the production database to verify they apply cleanly.
Transaction handling: Use
IDbContextTransactionfor multi‑operation sequences; keep transactions short and explicit.Asynchronous database calls: Use
SaveChangesAsyncand async LINQ operations to improve scalability.
Configuration & Secrets Management
Do not store secrets in source code or configuration files: Microsoft guidance warns against storing passwords or sensitive data in code or config; secrets should not be committed to source control(source).
Use development secrets for local work: Use the User Secrets tool for local development; secrets are stored outside the project tree and are not encrypted, so never use them in production(source).
Use environment variables for containerised or deployed apps: Environment variables override configuration values and support hierarchical keys using double underscores (
__); however, they are plain text and may need additional security measures(source).Use Azure Key Vault (or another secret store) in production: Azure Key Vault secures configuration values and controls access through Managed Identities. It meets FIPS 140‑2 HSM requirements and should be used for production secrets(source). The Key Vault configuration provider loads secrets into the configuration system(source).
Options pattern: Bind configuration sections to strongly typed classes (e.g.
MyOptions) and register withservices.Configure<MyOptions>(Configuration.GetSection("MySection")). This encapsulates configuration, improves testability and allows validation(source).
HTTP Client Management
Use IHttpClientFactory: Do not instantiate
HttpClientmanually; register named or typed clients viaservices.AddHttpClientand inject them into services. The factory centralizes configuration (base addresses, headers) and managesHttpMessageHandlerlifetimes to avoid socket exhaustion(source).Resilience policies: Use Polly to add retry, circuit breaker and timeout policies. Add message handlers to typed clients to handle cross‑cutting concerns like logging or authentication.
Handler lifetime pooling: IHttpClientFactory pools
HttpMessageHandlerinstances and controls their lifetime to improve performance(source).Typed clients: Create classes that encapsulate HTTP operations; typed clients can specify policies, base addresses and default headers.
Asynchronous Programming
Prefer async all the way: Methods doing I/O or CPU‑bound operations should be asynchronous. Return
TaskorTask<T>rather thanvoid(except event handlers). Avoid blocking on tasks with.Resultor.Wait()—this can cause deadlocks in ASP.NET Core(source).Avoid
async void: Useasync Taskfor asynchronous methods;async voidshould only be used for event handlers(source).Propagate CancellationToken: Accept
CancellationTokenparameters to allow cooperative cancellation and pass them down the call chain.
Validation & Model Binding
Use
[ApiController]: This attribute automatically enforces 400 responses for invalid model states and ensures clients receive consistent error responses(source).Validate input: Use built‑in data annotation attributes like
[Required],[StringLength],[Range],[RegularExpression]to enforce invariants(source). For complex validation, consider FluentValidation.Check
ModelState.IsValid: When not using[ApiController], explicitly verify the model state before processing requests and return BadRequest if invalid(source).Centralize validation: Use action filters or middleware to apply validation consistently.
Error Handling & Problem Details
Use middleware to handle errors globally: Add
UseExceptionHandlerto configure a global exception handler and return standardized error responses. UseUseStatusCodePagesfor non‑exception scenarios.Problem Details (RFC 7807): Use
ProblemDetailsto return machine‑readable error responses. RegisterIProblemDetailsServiceviaAddProblemDetails. The exception handler and status code pages middleware automatically return problem detail responses(source).Do not leak sensitive information: Avoid returning internal exception messages to clients. Log details internally and return generic error messages.
Model validation errors: ProblemDetails responses should include the validation failure details to help clients correct requests.
Logging & Observability
Structured logging: Use
ILogger<T>to log structured messages. Do not manually build strings; use named message templates. InjectILogger<T>via DI rather than creating it directly(source).Configure log providers: Configure log output to appropriate destinations (console, files, Application Insights, etc.). Use minimum log levels per provider.
Use compile‑time logging: .NET’s source generation logging features can improve performance when logging at high volume(source).
Include correlation IDs: Attach correlation identifiers to logs (e.g.
Activity.Current?.Id, HTTPTraceIdentifier) to trace requests across services.Health checks: Implement liveness and readiness endpoints using the built‑in health checks. Health checks allow orchestrators or load balancers to monitor service status and dependencies(source).
Security & Data Protection
Enforce HTTPS: Use
UseHttpsRedirectionandUseHstsmiddleware to ensure all requests are served over HTTPS and to communicate strict transport security to clients【596577191649470†L184-L191】. Do not rely on[RequireHttps]for API controllers; configure the server to listen only on HTTPS【596577191649470†L151-L158】.Data protection stack: Use ASP.NET Core’s data protection API to protect secrets (e.g. cookie data, tokens). It provides cryptographic APIs for key management, encryption and signing; keys are stored and managed securely(source).
Cross‑origin resource sharing (CORS): Configure CORS policies to restrict which origins can access your API.
Authentication & authorization: Use ASP.NET Core Identity or external providers to authenticate users; apply policy‑based authorization rather than inline role checks.
Input sanitization: Always validate and sanitize user input; avoid SQL injection by using parameterized queries and EF Core.
API Versioning & Contracts
Do not break existing clients: Treat API endpoints as contracts; once published, avoid breaking changes. When major changes are required, version your API and support old and new versions concurrently(source).
Versioning strategies: Embed version numbers in the URL path (e.g.
/api/v2/resource) or in HTTP headers. Use libraries likeMicrosoft.AspNetCore.Mvc.Versioningto manage versioning.Mediation pattern: Consider using the Mediator pattern to route requests to version‑specific handlers for cleaner separation(source).
Testing & Quality Gates
Unit testing: Write tests for services, repositories and critical logic. Use xUnit/NUnit and mocking frameworks (Moq, NSubstitute). Aim for high coverage but focus on meaningful tests.
Integration testing: Test API endpoints end‑to‑end using
WebApplicationFactory<T>and an in‑memory test database to verify behaviour.Performance testing: Use load testing tools to ensure the service scales under load. Test asynchronous concurrency scenarios.
Quality gates in CI: Enforce passing unit tests and static analysis before code merges. Use SonarQube or a similar tool to set coverage thresholds and check for code smells and duplications. Block merges when thresholds are not met.
Performance & Scalability
Avoid blocking calls: Do not call
.Resultor.Wait()on asynchronous methods; this can cause deadlocks and degrade performance(source).Make hot code paths asynchronous: Use
async/awaitto free up threads for other tasks(source).Use pagination and projections: Load only the necessary data from the database to reduce memory usage and improve query performance.
Cache results: Use in‑memory caching (e.g.
IMemoryCache) or distributed caches (e.g. Redis) to avoid repeated expensive computations.Compiled queries: For hot queries, use EF Core’s compiled queries to reduce overhead.
Connection pooling: For external resources (databases, HTTP endpoints), tune connection pools appropriately and reuse connections.
CI/CD & Branching
Git branching model: Maintain long‑lived
main(ormaster) anddevelopmentbranches. Create short‑livedfeaturebranches offdevelopmentfor each task; delete feature branches after merging.Pull Requests: All changes must be merged via Pull Requests. PRs trigger validation pipelines that run static code analysis, unit tests and build validation. Direct pushes to protected branches are disallowed.
CI pipeline: On each commit or PR, run automated builds, static analysis and unit/integration tests. Use
dotnet testanddotnet buildin your pipeline. Integrate SonarQube for quality gates.Release process: At the end of each sprint, create a
releasebranch fromdevelopment. Perform regression testing on this branch. Apply bug fixes directly to the release branch and merge back intodevelopmentto keep branches in sync. Only after testers and the Product Owner approve the release, promote it to production.Automated deployments: Use a deployment pipeline (e.g. Azure DevOps release pipeline) to push builds to staging and production environments. Implement manual approval steps for production releases; only designated approvers can promote to production.
Environment parity: Maintain parity between staging and production to avoid configuration drift.
Additional Practices
Documentation: Maintain up‑to‑date API documentation (OpenAPI/Swagger) and internal developer guides. Use XML comments and
SwaggerGento produce interactive API docs.Health, metrics & monitoring: Expose metrics via Prometheus exporters or Application Insights; monitor memory usage, CPU and request latencies.
Domain events & audit logging: Record domain events and audit logs for key actions (who did what and when). Use correlation IDs and include user identifiers where appropriate.
Secure coding: Follow OWASP Top Ten guidelines; validate input, escape output, and perform regular security reviews.
Localization: Design for localization by externalizing user‑facing strings. Use
IStringLocalizerto load resource strings.Accessibility: Ensure UI and API responses are accessible; follow WCAG guidelines for front‑end and provide comprehensive API error messages.
Sustainable maintenance: Regularly update NuGet packages and frameworks to stay current. Use automated dependency bots to raise PRs for updates. Evaluate and apply patches promptly, especially for security vulnerabilities.
Summary
This checklist gathers best practices from Microsoft’s official documentation and field experience.
Adhering to these guidelines will result in maintainable, secure and performant services. Use it as a living document – update it when new .NET versions introduce features or when the team agrees on changes.
At a minimum, always: use proper DI, secure configuration, manage HTTP clients with IHttpClientFactory, write asynchronous code, validate input, handle errors consistently with problem details, implement structured logging, enforce HTTPS, version your APIs, write tests and enforce quality gates. Following these practices will set your projects on a strong foundation for long‑term success.


