Browse pages
.NET HTTP API
Each HTTP operation in a .NET backend runs through one endpoint class in its use case folder, and the OpenAPI contract is generated from those classes at build time. This page states the endpoint contract, the metadata each operation declares, how transport models express closed sets, and how the global handler derives HTTP statuses from refusal codes. The shared transport rules live on HTTP API; this page holds only their .NET form.
Stack: dotnet
Layer: web-api
Concepts#
Endpoint Classes#
A WebApi endpoint is a small class that owns one route operation: it converts HTTP input to an Application command or query, sends it through the mediator, and maps the result back to HTTP. Each endpoint implements the product-owned IEndpoint contract, so the host finds every endpoint by scanning the modules assembly through its one public marker and maps each one exactly once, in type-name order.
One global handler maps each thrown failure to its HTTP status and Problem Details body, so endpoints carry no error mapping.
Operation Metadata#
The OpenAPI generator builds the published contract from metadata declared on each operation: a stable name, authorization, request and success shapes, failure statuses, and a summary. Comments on route lambdas never reach the contract, so the metadata is declared explicitly with typed results or Produces entries.
Standards#
Use One Endpoint per Operation#
ruleruleguidancelitespec/backend/dotnet/api#use-one-endpoint-per-operationA WebApi endpoint MUST implement IEndpoint, map one route operation, convert input to an Application message, dispatch through its mediator, and map the result.
Applies toartifact: endpoint
Why
When one class owns one route, a reviewer finds the input mapping, the dispatch, and the result mapping for that route in one place, and no route drifts into a second pattern unnoticed. Without that rule, operations share classes or helpers, and a change to one route silently affects another. This provision binds litespec/backend/api#map-one-operation-per-endpoint for .NET.
Example
The WebApi project owns this contract:
internal interface IEndpoint
{
void MapEndpoint(IEndpointRouteBuilder endpoints);
}At startup, the host scans the modules assembly through its one public marker for non-abstract IEndpoint implementations, registers each one once as a singleton, and calls MapEndpoint in type-name order. The scan uses framework reflection and dependency injection, so it needs no extra scanning package.
Checka reviewer answers
Reviewed forWhich mapped routes escape one IEndpoint type converting input, dispatching, and mapping the result? Look at the endpoints in the use case folders.
Marked guidance: a reviewer answers this question.
Exclude MVC Controllers from the Stack#
rulerulecheckablelitespec/backend/dotnet/api#exclude-mvc-controllers-from-the-stackWebApi code MUST NOT route requests through MVC controllers or ControllerBase.
Applies toartifact: any
Why
Controllers and endpoint classes route, filter, and map errors through different models. When both exist, a reviewer checks two mappings for every behavior, and error responses drift apart. One model means one place defines how requests reach handlers and how failures reach clients.
Checkchecked by a tool
Checked bydotnet build reports LS2003 for a WebApi class deriving from ControllerBase or carrying ApiController.
Marked checkable: the tool named here checks it.
Declare Operation Metadata in the Contract#
ruleruleguidancelitespec/backend/dotnet/api#declare-operation-metadata-in-the-contractAn operation MUST declare a stable operationId, its authorization, request, success, and Problem Details metadata, and typed results or Produces metadata, and a summary.
Applies toartifact: endpoint
Why
Generated clients name their calls from operationId, so an unnamed or unstable operation breaks every client regeneration. Authorization and Problem Details metadata tell a client which policies apply and which failures to handle; without them the client guesses. Route-lambda comments never reach the contract, so the summary and description are declared explicitly. This provision binds litespec/backend/api#declare-stable-operation-metadata-in-the-contract for .NET.
Checka reviewer answers
Reviewed forWhich operations miss a stable operationId, authorization, request, success, or Problem Details metadata, typed results or Produces entries, or a summary? Look at each operation.
Marked guidance: a reviewer answers this question.
Derive HTTP Statuses from Refusal Codes#
ruleruleguidancelitespec/backend/dotnet/api#derive-http-statuses-from-refusal-codesWebApi MUST map each refusal code to its HTTP status in one global handler.
Applies toartifact: any
Why
The specification records each refusal with its stable code and no transport status, so the stack derives the status its transport needs. The handler uses ASP.NET Core AddProblemDetails with one IExceptionHandler: validation failures answer 400, missing targets answer through the operation's 404 policy, forbidden failures answer 403 or the declared 404 disclosure policy, conflicts and state rejections answer 409, and unexpected failures answer 500 with code internal_error. The derived invalid-input refusal answers 400 under the project's refusals.invalidInput code, and the derived forbidden refusal answers 403 or the declared 404 under the project's refusals.forbidden code. Known Domain exception types map individually, and a cancellation from a disconnected request is not reported as an application error. The outcome-to-status assignment lives in litespec/backend/api#use-consistent-status-codes; this provision binds it for .NET.
Example
The handler registers through the framework's Problem Details services:
builder.Services.AddProblemDetails();
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();The handler maps each known exception type to its status and code, and answers 500 with code internal_error for the rest.
Checka reviewer answers
Reviewed forWhich refusal codes escape the one IExceptionHandler, or map outside their outcome-table status? Look at the WebApi global handler.
Marked guidance: a reviewer answers this question.
Mark Polymorphic Transport Models with Discriminators#
ruleruleguidancelitespec/backend/dotnet/api#mark-polymorphic-transport-models-with-discriminatorsA data-bearing set MUST map to an abstract base record with one sealed record per case, published as oneOf with a string discriminator.
Applies toartifact: endpoint
Why
A concrete base record cannot mark the discriminator as required, so a client could send a payload that names no case. String discriminators carry the Domain union's stable case codes, so the wire value survives renames of code identifiers. Without the discriminator, deserialization picks a case by shape, and two cases with similar fields become ambiguous. This provision binds litespec/backend/api#mark-polymorphic-models-with-discriminators for .NET.
Example
- Declare an abstract base record carrying
JsonPolymorphicand oneJsonDerivedTypeper case. - Carry the metadata on the type, not only in host options.
- Name the discriminator through
TypeDiscriminatorPropertyName, using a name such astypeoroutcome. - Opt derived types in explicitly, so an unregistered runtime subtype fails serialization.
- Complete discriminator metadata that the generator omits with a document or schema transformer.
Checka reviewer answers
Reviewed forWhich data-bearing sets escape the abstract base record with one sealed record per case and the oneOf string discriminator? Look at each data-bearing set.
Marked guidance: a reviewer answers this question.
Conventions#
Place Endpoints in Their Use Case Folders#
conventiondefaultguidancelitespec/backend/dotnet/api#place-endpoints-in-their-use-case-foldersPlace each endpoint with its request and response models and mappings flat in its use case folder inside the UseCases/ folder of its module and aggregate.
ReplacementA project names its endpoint layout in its WebApi documentation.
Applies toartifact: endpoint
Why
The folder path matches the specification path, so a reader locates an operation from its use-case identifier without searching. Transport types take the names their boundary roles assign under litespec/backend/api#name-transport-types-by-their-boundary-role, and transport mapping lives in WebApi under litespec/backend/application#keep-mappings-at-the-owning-boundary. Response models carry the query's declared include groups. Transport types used by two or more use cases of one aggregate live in the aggregate WebApi folder, and those used by two or more aggregates in the module WebApi folder. Module nesting, including the single-aggregate flat form, follows litespec/backend/architecture#organize-code-by-module-aggregate-and-use-case.
Example
The endpoint layout is:
{Product}.Modules/
Posts/ single aggregate whose name matches the module
WebApi/
PostLinks.cs transport types two or more Posts use cases name
UseCases/
CreateDraft/
CreateDraftEndpoint.cs
CreateDraftRequestModel.cs
CreateDraftResponseModel.cs
CreateDraftApiMappings.cs
GetPost/
GetPostEndpoint.cs
GetPostResponseModel.cs
GetPostApiMappings.cs
Customers/ two aggregates: use cases nest under each aggregate UseCases folder
CustomerAccounts/
UseCases/
RestrictAccount/
RestrictAccountEndpoint.cs
RestrictAccountRequestModel.cs
RestrictAccountApiMappings.cs
Consents/
UseCases/
GrantConsent/
GrantConsentEndpoint.cs
GrantConsentRequestModel.cs
GrantConsentApiMappings.cs
{Product}.WebApi/
Errors/
GlobalExceptionHandler.cs
ProblemDetailsApiMappings.cs
Security/
CurrentActor.cs
OpenApi/
Program.csChecka reviewer answers
Reviewed forWhich use case folders miss the endpoint, request and response models, or mappings named for the use case? Look at each use case folder.
Marked guidance: a reviewer answers this question.
Keep Program.cs as Composition#
conventiondefaultguidancelitespec/backend/dotnet/api#keep-programcs-as-compositionProgram.cs holds module registration, middleware order, endpoint discovery, health endpoints, OpenAPI, and host startup, ending with an empty partial Program class.
ReplacementA project names its startup layout in its WebApi documentation.
Applies toartifact: any
Why
Middleware runs in registration order, so authentication registered after endpoints never runs. When one file shows the whole sequence, a reviewer sees the order at a glance; when registration scatters across files, a misplaced call hides. This file binds litespec/backend/architecture#compose-each-process-explicitly for WebApi.
Example
The visible order is trusted forwarded headers, exception handling, transport security, authentication, authorization, endpoints, and then health. Forwarded headers appear only when the deployment boundary requires trusted proxies. OpenAPI and Scalar map only in Development.
Checka reviewer answers
Reviewed forWhich Program.cs files break the recorded composition order or miss a step? Look at Program.cs.
Marked guidance: a reviewer answers this question.