ASP.NET Core is one of the fastest and most complete frameworks for building web APIs. But "it works on my machine" and "it is ready for production" are very different things. These are the ASP.NET Core Web API best practices we apply to every API we build for clients.
1. Structure the project around features
Organize code by feature (orders, customers, billing) rather than by technical layer only. Keep endpoints thin: they validate input, call an application service or handler, and map the result to an HTTP response. Business rules belong in the application or domain layer, where they can be tested without HTTP.
Minimal APIs with MapGroup keep related endpoints together; controllers remain a good choice for large APIs with many conventions. Pick one style per service and stay consistent.
2. Validate every input
- Validate request models at the edge — with data annotations, FluentValidation or the built-in validation support for Minimal APIs in recent ASP.NET Core versions.
- Never bind request bodies directly to database entities; use request and response DTOs to avoid over-posting.
- Constrain sizes: maximum page sizes, string lengths and request body limits.
3. Return consistent errors with ProblemDetails
Clients should be able to handle every error the same way. Use the Problem Details for HTTP APIs format everywhere:
builder.Services.AddProblemDetails();
var app = builder.Build();
app.UseExceptionHandler();
app.UseStatusCodePages();
Map domain errors to meaningful status codes — 400 for invalid input, 404 for missing resources, 409 for conflicts, 422 where you distinguish business-rule failures — and never leak stack traces or SQL errors in production responses.
4. Secure by default
- Authentication: JWT bearer tokens or OpenID Connect via an identity provider (Microsoft Entra ID, Auth0, Keycloak). Require authentication globally and opt out explicitly for public endpoints.
- Authorization: use policy-based authorization and check resource ownership (for example, that an order belongs to the caller's tenant) inside the handler, not only by role.
- Transport: HTTPS everywhere, HSTS, and a strict CORS policy listing known origins.
- Secrets: Azure Key Vault, AWS Secrets Manager or environment variables — never in source control.
- Rate limiting: the built-in rate limiting middleware protects login and expensive endpoints.
Microsoft's ASP.NET Core security documentation is the authoritative reference.
5. Design for evolution: versioning and OpenAPI
Publish an OpenAPI description for every API — ASP.NET Core can generate it natively — and treat it as a contract. Version from day one (for example /v1/), add fields rather than changing them, and give clients deprecation notice before removing anything. Our REST API design best practices article covers naming, pagination and versioning in more depth.
6. Performance
- Use
asyncall the way down; never block on.Resultor.Wait()in request code. - Paginate list endpoints and project only the columns you need with EF Core
Select. - Use
AsNoTrackingfor read-only queries and watch for N+1 queries. - Add output caching or response caching for data that changes rarely, and distributed caching (Redis) where multiple instances share state.
- Move slow work (emails, reports, AI calls) to background workers or queues.
7. Observability
You cannot fix what you cannot see. Use structured logging (Serilog or the built-in logger) with correlation IDs, add health checks for the database and key dependencies, and export traces and metrics with OpenTelemetry to Application Insights, Grafana or your provider of choice. Log enough to diagnose problems — and never log passwords, tokens or personal data.
8. Testing
- Unit tests for business rules.
- Integration tests with
WebApplicationFactorythat exercise the real HTTP pipeline, ideally against a real database in a container (Testcontainers) rather than an in-memory fake. - Contract tests or OpenAPI diff checks in CI to catch breaking changes.
9. Deployment
Build once, deploy the same artifact to every environment, keep configuration outside the build, and automate deployments with GitHub Actions or Azure DevOps. Containers make local, staging and production environments consistent. Run database migrations as a controlled pipeline step, not on application start in production.
Production checklist: feature-based structure · validated DTOs · ProblemDetails errors · auth required by default · secrets in a vault · versioned OpenAPI contract · async and paginated · structured logs, health checks and tracing · integration tests in CI · automated deployments.
Need an API built or reviewed? See our API and backend development services, or read about .NET development at Codebeck.



