VP.
Back to all articles
.NET

ASP.NET Core Best Practices for Production APIs

A focused checklist for designing observable, resilient, and maintainable ASP.NET Core APIs in production.

Vishal Parmar2 min read
Layered API nodes representing a resilient ASP.NET Core service

Production quality is less about a clever endpoint and more about predictable behavior under failure. A good API makes its contracts explicit, stops work when clients disconnect, and leaves enough evidence to diagnose the unexpected.

Keep transport and domain concerns separate

Controllers or route handlers should translate HTTP input into an application request. Business decisions belong in services or use cases that can be tested without constructing an HTTP context.

app.MapPost("/orders", async (
    CreateOrderRequest request,
    IOrderService orders,
    CancellationToken cancellationToken) =>
{
    var result = await orders.CreateAsync(request, cancellationToken);
    return Results.Created($"/orders/{result.Id}", result);
});

The handler owns status codes and locations. The service owns the rules for creating an order.

Validate at the boundary

Reject invalid input before it reaches domain logic. Use consistent problem details so clients can handle every validation failure through the same shape.

Return useful errors without leaking internals

An exception stack trace helps your logs, not your API consumer. Map known failures to stable problem types and keep unexpected details on the server.

FailureTypical response
Invalid request400 Bad Request
Missing resource404 Not Found
Version conflict409 Conflict
Unexpected failure500 Internal Server Error

Propagate cancellation tokens

Pass the request cancellation token through every asynchronous boundary, including database and outbound HTTP calls. This prevents abandoned requests from continuing expensive work.

var order = await db.Orders
    .AsNoTracking()
    .SingleOrDefaultAsync(x => x.Id == id, cancellationToken);

Make observability part of the design

Structured logs should identify the request and the business operation without recording secrets. Add health checks for real dependencies, emit useful metrics, and propagate trace context across service calls.

If a failure cannot be connected to a request, dependency, and deployment, it will take longer to fix than it should.

Test the contract

Unit tests protect domain rules. Integration tests protect routing, serialization, authentication, and database behavior. Both matter, but production incidents often hide in the seams that integration tests exercise.

A production API is trustworthy when its behavior stays clear under both normal load and partial failure.