ADR 0012: OpenAPI Spec via Microsoft.AspNetCore.OpenApi; Swagger UI via Swashbuckle
Status
Accepted
Context and Problem Statement
The API needs machine-readable documentation in two forms: a spec document for tooling, CI drift checks, and the documentation site; and an interactive browser UI for exploratory testing during local development.
In .NET 9/10, Microsoft.AspNetCore.OpenApi became a first-party built-in
that generates a spec document from the application's endpoint metadata at
runtime. Swashbuckle has historically handled both spec generation and UI
in a single package. The question is which library, or combination, to use.
Decision Drivers
- The spec document must be generated from the running application so that it reflects the actual endpoint configuration, not a manually maintained file.
- Enum values in the schema must appear as strings, consistent with the
runtime
JsonStringEnumConvertersetting (ADR 0003). - The interactive UI must only be available in the Development environment. It must not be accessible in production.
- The committed OpenAPI snapshot (ADR 0013) is generated by starting the API in Development mode and fetching the spec endpoint — the spec endpoint must therefore be reachable in Development.
Considered Options
Option A — Microsoft.AspNetCore.OpenApi for spec + Swashbuckle for UI (Selected)
Microsoft.AspNetCore.OpenApi generates the spec at /openapi/v1.json
(Development only). A custom StringEnumSchemaTransformer corrects enum
schemas to emit string values. Swashbuckle provides the Swagger UI at
/swagger (Development only), consuming its own spec endpoint at
/swagger/v1/swagger.json. The two libraries coexist: one for the
machine-readable artifact, one for the developer UI.
Option B — Swashbuckle only
A single library handles both spec generation and UI. Less configuration,
but Swashbuckle's spec generation is not the direction .NET 9/10 is moving.
The first-party Microsoft.AspNetCore.OpenApi is the recommended approach
for new projects on these runtimes, and Swashbuckle compatibility with
.NET 10 Minimal API features is a trailing concern.
Option C — NSwag
An alternative that handles both spec generation and UI, with additional code-generation tooling. Heavier than either Swashbuckle or the built-in option for a project that does not need client code generation.
Decision Outcome
Chosen option: Option A — Microsoft.AspNetCore.OpenApi for spec + Swashbuckle for UI.
Why this trade-off makes sense for this project
Microsoft.AspNetCore.OpenApiis the .NET 10 native path. It integrates directly with Minimal API endpoint metadata and is maintained by the ASP.NET Core team. Using it for spec generation aligns with the direction of the platform.- Swashbuckle fills the UI gap. The built-in library does not ship an interactive UI. Swashbuckle's UI is well-known and adds no meaningful runtime cost since it is gated to Development only.
StringEnumSchemaTransformerkeeps the schema consistent with the serialiser. Without it, the generated spec would describe enum properties as integers — contradicting the runtime behaviour whereJsonStringEnumConverterserialises them as strings. The transformer is afile-scoped class inOpenApi.cs(not a public type) because it is an implementation detail of the composition root.- Both endpoints are Development-only.
MapOpenApi()andUseSwaggerUI()are both gated byIsDevelopment. The spec endpoint at/openapi/v1.jsonis reachable in CI because the snapshot script setsDOTNET_ENVIRONMENT=Development. In production, no spec or UI surface is exposed.
Setup overview
Development Production
─────────────────────────── ───────────────
GET /openapi/v1.json (not mapped)
└─ Microsoft.AspNetCore.OpenApi
└─ StringEnumSchemaTransformer
GET /swagger (not mapped)
└─ Swashbuckle UI
└─ reads /swagger/v1/swagger.json
Consequences
Positive Consequences
- The spec is generated from live endpoint metadata — it cannot drift from the actual API surface without the CI drift check detecting it (ADR 0013).
- Enum schemas match the runtime serialiser behaviour exactly.
- The production surface is zero: no spec endpoint, no UI, no additional attack surface.
Negative Consequences
- Two libraries serve overlapping purposes (each can generate a spec). This
is intentional but can cause confusion:
/openapi/v1.jsonand/swagger/v1/swagger.jsonare different endpoints backed by different generators. The committed snapshot uses/openapi/v1.json(the built-in). StringEnumSchemaTransformermust be kept in sync with any enum-related serialisation policy changes. IfJsonStringEnumConverteris removed or scoped, the transformer becomes incorrect.
Re-evaluation Triggers
Revisit when:
Microsoft.AspNetCore.OpenApiships a built-in Swagger UI integration — the Swashbuckle dependency can then be dropped.- Client code generation is introduced — NSwag or the OpenAPI Generator may be preferred over Swashbuckle at that point.
Related
- ADR 0003 – RFC 9457 ProblemDetails (
JsonStringEnumConvertercontext) - ADR 0013 – OpenAPI Snapshot as a Committed Artifact with CI Drift Check
src/ServiceDeskLite.Api/Composition/OpenApi.cssrc/ServiceDeskLite.Api/Program.cs—AddSwaggerGen,UseSwaggerUI