Skip to content

Example: ASP.NET Core API hosting pipeline

hosting-pipeline.md records how an ASP.NET Core Web API is hosted and how a request travels through the middleware pipeline before it reaches a controller action or a minimal API endpoint. It is the page where the SAIF CoreAPIStartup base class is named for what it is: a source of behavior that the repository does not show. Doc gen produces one per ASP.NET Core API at docs/apis/<api-slug>/hosting-pipeline.md.

What reviewers should check

  • The host model matches Program.cs. A generic host with UseStartup<T>, a minimal host (WebApplication.CreateBuilder) and a minimal host that drives a Startup instance by hand are three different shapes, and a repository that mixes them must say so. Never assume a Startup.cs exists.
  • Every base-class flag set in the startup constructor appears in the flags table, including the ones that are not set. EnableSecureEnvironmentClient, EnableNewtonsoftJsonSerializer, DisableAuthServices and the EnvironmentName override each change what the service does.
  • The middleware table follows source order exactly. Calls contributed by the base class or a package (base.Configure, ConfigureMiddleware declared only on the base) are one opaque row each. A pipeline written out as UseRouting -> UseAuthentication -> UseAuthorization -> UseEndpoints without a source line behind it is invented.
  • Package behavior carries the dagger marker, and the page carries the canonical dependency-doc note. A base-class contribution written as an unmarked application fact is a finding.
  • A commented-out middleware call is a row with Status "Commented out", not an omission. Absent and commented out have different consequences.
  • The versioning, OpenAPI, health check and telemetry tables each say what the source shows. "None found" and "Not determined from source" are valid answers; an invented ping route is not.
  • The Mermaid diagram uses a standard fence, is drawn in table order, and shows each opaque contribution as a single node.

Example

The document below is a complete, correct api-hosting-pipeline.md for a fictional ASP.NET Core API.

View rendered example — the same document as a real page, so you can compare rendering as well as source.

---
title: Hosting pipeline
summary: Host model, server, ordered middleware pipeline, API versioning, OpenAPI exposure, health checks, and telemetry.
status: draft
owner: Premium Systems Team
last_reviewed: 2026-10-21
---

# Hosting pipeline

`SAIF.Premium.Api` is an ASP.NET Core 8 Web API hosted in-process by IIS through
the ASP.NET Core Module V2. `Program.cs` uses the minimal host
(`WebApplication.CreateBuilder`) but hands service and middleware configuration
to a `Startup` type derived from the SAIF `CoreAPIStartup` base class, so the
pipeline is a mix of calls the repository makes and contributions the base class
makes that source does not show. A request passes the base class's opaque
`ConfigureMiddleware` step, Swagger, and the base class's opaque `Configure`
step before it reaches a controller action or the one minimal API group. API
versioning is read from the URL segment, so `v1` and `v2` are part of the route.

## Host model

| Field | Value |
| --- | --- |
| **Entry point** | `src/SAIF.Premium.Api/Program.cs` |
| **Host builder** | Minimal host - `WebApplication.CreateBuilder(args)` driving a `Startup` instance by hand |
| **Startup call chain** | `new Startup(builder.Configuration)` -> `startup.ConfigureServices(builder.Services)` -> `builder.Build()` -> `startup.ConfigureMiddleware(app)` -> `startup.Configure(app, app.Environment)` -> `app.MapGroup("/v1/RateTables").MapRateTableEndpoints()` -> `app.Run()` |
| **Host-builder extensions** | `builder.Host.ConfigureOpenTelemetry(o => o.Owner = "Premium Systems Team")` |
| **Startup type** | `Startup` (`src/SAIF.Premium.Api/Startup.cs`) |
| **Base class** | `CoreAPIStartup` (`SAIF.NetCore.ServiceExtensions` `8.2.1`) |
| **Target framework** | `net8.0` |

The repository uses both host styles: the host is a minimal host, and
`Program.cs` calls the `Startup` methods itself rather than through
`UseStartup<Startup>()`. `Startup.ConfigureServices` calls
`base.ConfigureServices(services)` as its first statement, before any local
registration. `Startup` declares no `ConfigureMiddleware` method;
`startup.ConfigureMiddleware(app)` resolves to a method that exists only on
`CoreAPIStartup`, so what it registers is not visible in source.

### Base-class flags

| Flag or call | Value | Set in | Effect (as far as source shows) |
| --- | --- | --- | --- |
| `EnableSecureEnvironmentClient<Startup>()` | Called | `src/SAIF.Premium.Api/Startup.cs:22` | Enables the secure `IEnvironmentClient`, which decrypts secure settings; the registration itself is opaque, contributed by `SAIF.NetCore.ServiceExtensions`† |
| `EnableNewtonsoftJsonSerializer` | Not set | - | The base class's default serializer stays in effect† |
| `DisableAuthServices` | Not set | - | The base class's authentication and authorization services stay registered; their configuration is opaque† |
| `WebHostEnvironment.EnvironmentName` | `Configuration["AppSettings:ApplicationHost"]` | `src/SAIF.Premium.Api/Startup.cs:23` | The environment name comes from `appsettings.json`, not from `ASPNETCORE_ENVIRONMENT` |

## Server

| Aspect | Finding |
| --- | --- |
| Server | IIS in-process (ASP.NET Core Module V2) |
| Hosting model evidence | `web.config` `<aspNetCore ... hostingModel="InProcess">` and `<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>` in `src/SAIF.Premium.Api/SAIF.Premium.Api.csproj` |
| `web.config` notes | The checked-in `arguments` value still references `bin\Debug\net6.0\SAIF.Premium.Api.dll` while the target framework is `net8.0`; recorded in [Tech debt](tech-debt.md) |
| Local launch profiles | `Properties\launchSettings.json` - profiles `SAIF.Premium.Api` (`Project`) and `IIS Express`, both with `launchUrl` = `swagger` (local development only) |

## Middleware pipeline

| Order | Middleware | Registered in | Condition | Status | Notes |
| --- | --- | --- | --- | --- | --- |
| 1 | `startup.ConfigureMiddleware(app)` | `src/SAIF.Premium.Api/Program.cs:19` | Always | Opaque | Declared only on `CoreAPIStartup` (`SAIF.NetCore.ServiceExtensions`)† - not visible in source; runs before `Startup.Configure` |
| 2 | `app.UseHttpsRedirection()` | `src/SAIF.Premium.Api/Startup.cs:50` | - | Commented out | Disabled in source, so this service applies no HTTPS redirection of its own |
| 3 | `app.UseSwagger()` | `src/SAIF.Premium.Api/Startup.cs:51` | Always | Active | Not gated by environment; the OpenAPI documents are served on every track |
| 4 | `app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "Premium API v1"); c.SwaggerEndpoint("/swagger/v2/swagger.json", "Premium API v2"); })` | `src/SAIF.Premium.Api/Startup.cs:52` | Always | Active | One endpoint per declared API version |
| 5 | `base.Configure(app, env)` | `src/SAIF.Premium.Api/Startup.cs:57` | Always | Opaque | Contributed by `CoreAPIStartup` (`SAIF.NetCore.ServiceExtensions`)† - not visible in source. Controller endpoint mapping happens inside this call; the relative order of routing, authentication and authorization is not visible in source |
| 6 | `app.MapGroup("/v1/RateTables").MapRateTableEndpoints()` | `src/SAIF.Premium.Api/Program.cs:22` | Always | Active | Minimal API group, registered after the `Startup` pipeline completes; see [Endpoints](endpoints.md) |

```mermaid
flowchart LR
    REQ["Request"] --> SRV["IIS in-process<br/>ASP.NET Core Module V2"]
    SRV --> CM["startup.ConfigureMiddleware<br/>opaque"]
    CM --> SW["app.UseSwagger"]
    SW --> SUI["app.UseSwaggerUI"]
    SUI --> BASE["base.Configure<br/>opaque"]
    BASE --> CTRL["Controller action"]
    BASE --> MIN["Minimal API endpoint<br/>MapGroup /v1/RateTables"]
```

`app.UseHttpsRedirection()` is not drawn: it is commented out and does not run.

## API versioning

| Aspect | Finding |
| --- | --- |
| Package | `Asp.Versioning.Mvc` `8.1.0` and `Asp.Versioning.Mvc.ApiExplorer` `8.1.0` |
| Registration | `services.AddApiVersioning(...)` in `src/SAIF.Premium.Api/Startup.cs:33` |
| Default version | `1.0` |
| Assume default when unspecified | Yes |
| Report API versions | Yes |
| Version reader | `UrlSegmentApiVersionReader` |
| Versioned API explorer | `AddApiExplorer` - `GroupNameFormat = "'v'VVV"`, `SubstituteApiVersionInUrl = true` |
| Versions declared | `[ApiVersion("1.0")]` on `PremiumsController`; `[ApiVersion("1.0")]` and `[ApiVersion("2.0")]` on `ClassCodesController`; `AdminController` and the `RateTableEndpoints` group declare no version and use a literal `v1` route prefix |

## API documentation surface

| Aspect | Finding |
| --- | --- |
| Generator | Swashbuckle `AddSwaggerGen` |
| State | Active |
| Document(s) | `v1` and `v2`, one `SwaggerDoc` per declared API version |
| Exposed route | `swagger/v1/swagger.json` and `swagger/v2/swagger.json`, with Swagger UI at `swagger` |
| XML comments | `IncludeXmlComments("SAIF.Premium.Api.xml")` with `<GenerateDocumentationFile>true</GenerateDocumentationFile>` |
| Security definitions | None |
| Operation filters | None |
| Package and version | `Swashbuckle.AspNetCore` `6.5.0` |

## Health checks

| Aspect | Finding |
| --- | --- |
| ASP.NET Core health checks | None found |
| SAIF pingable registrations | `AddPingableTransient<IPremiumRepository, PremiumRepository>()` in `src/SAIF.Premium.Api/Startup.cs:41` |
| Ping endpoint | Not determined from source |

## Telemetry

| Aspect | Finding |
| --- | --- |
| OpenTelemetry | `.ConfigureOpenTelemetry(o => o.Owner = "Premium Systems Team")` on the host builder |
| Application Insights | None found |
| Logging configuration | `appsettings.json` `Logging:LogLevel` - `Default` = `Information`, `Microsoft.AspNetCore` = `Warning` |
| Registered in | `src/SAIF.Premium.Api/Program.cs:14` |

## Related documentation
- [API index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Endpoints](endpoints.md)
- [Authentication and authorization](authentication-authorization.md)
- [Dependencies](dependencies.md)
- [Configuration](configuration.md)
- [Operations and processes](operations-processes.md)
- [Business rules](business-rules.md)
- [Data model](data-model.md)
- [Operational runbook](operational-runbook.md)
- [Tech debt](tech-debt.md)

> **Note:** Statements marked with † were retrieved from dependency library documentation
> via the Legends CLI RAG service. They describe inherited behavior from the indicated
> origin library and should be verified if the dependency version changes.

## Sources
- `src/SAIF.Premium.Api/Program.cs`
- `src/SAIF.Premium.Api/Startup.cs`
- `src/SAIF.Premium.Api/SAIF.Premium.Api.csproj`
- `src/SAIF.Premium.Api/web.config`
- `src/SAIF.Premium.Api/Properties/launchSettings.json`
- `src/SAIF.Premium.Api/appsettings.json`
- `src/SAIF.Premium.Api/Controllers/PremiumsController.cs`
- `src/SAIF.Premium.Api/Controllers/ClassCodesController.cs`
- `src/SAIF.Premium.Api/Controllers/AdminController.cs`
- `src/SAIF.Premium.Api/Endpoints/RateTableEndpoints.cs`