# .NET HTTP API

**Stack:** dotnet

**Layer:** web-api

## Intent

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](https://litespec.dev/4.0.0/backend/api.md); this page holds only their .NET form.

## 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

**ID:** `litespec/backend/dotnet/api#use-one-endpoint-per-operation`

**Strength:** rule

**Applies to:** artifact: endpoint

**Statement:** A WebApi endpoint MUST implement `IEndpoint`, map one route operation, convert input to an Application message, dispatch through its mediator, and map the result.

**Rationale:** 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:

```csharp
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.

**Reviewed for:** Which mapped routes escape one `IEndpoint` type converting input, dispatching, and mapping the result? Look at the endpoints in the use case folders.

### Exclude MVC Controllers from the Stack

**ID:** `litespec/backend/dotnet/api#exclude-mvc-controllers-from-the-stack`

**Strength:** rule

**Applies to:** artifact: any

**Statement:** WebApi code MUST NOT route requests through MVC controllers or `ControllerBase`.

**Rationale:** 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.

**Checked by:** `dotnet build` reports `LS2003` for a WebApi class deriving from `ControllerBase` or carrying `ApiController`.

### Declare Operation Metadata in the Contract

**ID:** `litespec/backend/dotnet/api#declare-operation-metadata-in-the-contract`

**Strength:** rule

**Applies to:** artifact: endpoint

**Statement:** An operation MUST declare a stable `operationId`, its authorization, request, success, and Problem Details metadata, and typed results or `Produces` metadata, and a summary.

**Rationale:** 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.

**Reviewed for:** Which operations miss a stable `operationId`, authorization, request, success, or Problem Details metadata, typed results or `Produces` entries, or a summary? Look at each operation.

### Derive HTTP Statuses from Refusal Codes

**ID:** `litespec/backend/dotnet/api#derive-http-statuses-from-refusal-codes`

**Strength:** rule

**Applies to:** artifact: any

**Statement:** WebApi MUST map each refusal code to its HTTP status in one global handler.

**Rationale:** 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:

```csharp
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.

**Reviewed for:** Which refusal codes escape the one `IExceptionHandler`, or map outside their outcome-table status? Look at the WebApi global handler.

### Mark Polymorphic Transport Models with Discriminators

**ID:** `litespec/backend/dotnet/api#mark-polymorphic-transport-models-with-discriminators`

**Strength:** rule

**Applies to:** artifact: endpoint

**Statement:** A data-bearing set MUST map to an abstract base record with one sealed record per case, published as `oneOf` with a string discriminator.

**Rationale:** 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 `JsonPolymorphic` and one `JsonDerivedType` per case.
- Carry the metadata on the type, not only in host options.
- Name the discriminator through `TypeDiscriminatorPropertyName`, using a name such as `type` or `outcome`.
- 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.

**Reviewed for:** Which 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.

## Conventions

### Place Endpoints in Their Use Case Folders

**ID:** `litespec/backend/dotnet/api#place-endpoints-in-their-use-case-folders`

**Strength:** default

**Applies to:** artifact: endpoint

**Default:** Place 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.

**Replacement:** A project names its endpoint layout in its WebApi documentation.

**Rationale:** 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:

```text
{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.cs
```

**Reviewed for:** Which use case folders miss the endpoint, request and response models, or mappings named for the use case? Look at each use case folder.

### Keep Program.cs as Composition

**ID:** `litespec/backend/dotnet/api#keep-programcs-as-composition`

**Strength:** default

**Applies to:** artifact: any

**Default:** `Program.cs` holds module registration, middleware order, endpoint discovery, health endpoints, OpenAPI, and host startup, ending with an empty partial `Program` class.

**Replacement:** A project names its startup layout in its WebApi documentation.

**Rationale:** 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.

**Reviewed for:** Which `Program.cs` files break the recorded composition order or miss a step? Look at `Program.cs`.
