CSharpEssentials.AspNetCore.OpenApi
6.5.2
dotnet add package CSharpEssentials.AspNetCore.OpenApi --version 6.5.2
NuGet\Install-Package CSharpEssentials.AspNetCore.OpenApi -Version 6.5.2
<PackageReference Include="CSharpEssentials.AspNetCore.OpenApi" Version="6.5.2" />
<PackageVersion Include="CSharpEssentials.AspNetCore.OpenApi" Version="6.5.2" />
<PackageReference Include="CSharpEssentials.AspNetCore.OpenApi" />
paket add CSharpEssentials.AspNetCore.OpenApi --version 6.5.2
#r "nuget: CSharpEssentials.AspNetCore.OpenApi, 6.5.2"
#:package CSharpEssentials.AspNetCore.OpenApi@6.5.2
#addin nuget:?package=CSharpEssentials.AspNetCore.OpenApi&version=6.5.2
#tool nuget:?package=CSharpEssentials.AspNetCore.OpenApi&version=6.5.2
CSharpEssentials.AspNetCore.OpenApi
Microsoft.AspNetCore.OpenApi integration for the CSharpEssentials enum conventions.
One host, one OpenAPI package: reference either
CSharpEssentials.AspNetCore.OpenApi(Microsoft.AspNetCore.OpenApi) orCSharpEssentials.AspNetCore.Swashbuckle(Swashbuckle), never both. Both useMicrosoft.OpenApi2.x.
Installation
dotnet add package CSharpEssentials.AspNetCore.OpenApi
Targets net10.0 with Microsoft.AspNetCore.OpenApi 10.x and Microsoft.OpenApi 2.x. A net11.0 target with Microsoft.AspNetCore.OpenApi 11.x needs Microsoft.OpenApi 3.x and will come later as a non-breaking addition.
Enum schemas
using CSharpEssentials.AspNetCore; // AddEnumConventions for IServiceCollection and OpenApiOptions
builder.Services.AddEnumConventions();
builder.Services.AddOpenApi("v1", o => o.AddEnumConventions());
builder.Services.AddOpenApi("v2", o => o.AddEnumConventions());
app.MapOpenApi();
AddEnumConventions adds a schema, an operation and a document transformer. Enums handled by the enum conventions ([StringEnum] or generated metadata) are described the way they are written:
"OrderStatus": {
"type": "string",
"description": "Order lifecycle state.\n\n| value | number | description |\n|---|---|---|\n| `pending` | 0 | Created, waiting for payment. |\n| `unknown` | 99 | Response only: a value this API version does not know. |",
"enum": [ "pending", "unknown" ],
"x-enum-varnames": [ "Pending", "Unknown" ],
"x-enum-descriptions": [ "Created, waiting for payment.", "Response only: a value this API version does not know." ],
"x-enum-numeric-values": [ 0, 99 ]
}
- One component per enum. Your description (XML
<summary>or[Description]) is kept and the value table is appended. Obsolete members are marked(deprecated), the[EnumFallback]memberResponse only:. Aliases are accepted but not listed. defaultis the wire name ([DefaultValue]on a property, a default parameter value).[Flags]enums are arrays withuniqueItems: true; collections are arrays of the component.- Nullable is written where the enum is used, by the document's OpenAPI version:
allOf+nullable: truefor 3.0,oneOfwith{ "type": "null" }for 3.1. - A document whose operations all write numbers (
WithEnumWireFormat(EnumWireFormat.Number)on the group,[EnumWireFormat]on the controller) describes the enums asinteger(int32/int64by underlying type). A document that mixes formats describes strings and marks each number operation withx-enum-wire-format: numberand a description note; it also logs one warning per enum used in both formats (once per document). One document per API version never mixes. - Operations with a header selected format (
WithEnumWireFormat("X-Enum-Format", ...)) show the format of a request without the header, plusx-enum-wire-format-headerand a note. A selector that throws for such a request is described with the globalWriteAsformat and logged once as a warning. - Enums without
[StringEnum]or metadata keep the framework schema; nothing throws.
The Swashbuckle package produces the same enum schemas (both are compared with the same golden files).
Optional route parameters
OpenAPI requires every path parameter to be required: true, so a route with an optional segment ({id?}, {id:int?}, {page=1}) cannot be described on one path. AddOptionalRouteParameters (from 6.2.0, opt-in) describes it on several paths, one per form:
using CSharpEssentials.AspNetCore; // AddOptionalRouteParameters, OpenApiOptionalRouteParameterMode
builder.Services.AddOpenApi("v1", o => o.AddOptionalRouteParameters());
[HttpGet("api/archive/{year?}/{month?}", Name = "GetArchive")]
| Path | operationId |
|---|---|
/api/archive |
GetArchiveWithoutYearAndMonth |
/api/archive/{year} |
GetArchiveWithoutMonth |
/api/archive/{year}/{month} |
GetArchive |
- Each form is a full copy of the operation (tags, security, request body, responses, extensions) without the omitted parameters; every path parameter is
required: true, withoutallowEmptyValue, anulltype or default, or a{"type": "null"}member inoneOforanyOf(a nullable enum such as{status?}in OpenAPI 3.1). - Only optional parameters at the end of the route are split, up to three. An optional parameter followed by a required segment (
api/{tenant?}/orders), a route with more than three trailing optional parameters and a catch-all parameter ({*rest},{**path}) keep one path with the parameters marked required. - A route with more than three trailing optional parameters is logged as a warning (once per document, path and method) through the host's
ILoggerFactory. Without anILoggerFactorynothing is logged. - A form whose path and method another operation already has (
[HttpGet("api/orders")]next to[HttpGet("api/orders/{id?}")]) is skipped; the other operation stays. - The full form keeps the operationId; a shorter form gets
{operationId}Without{Param}(Andbetween parameters), or none when the full form has none. A generated operationId that another operation already uses throwsInvalidOperationExceptionwhen the document is generated; rename one of them or pass a selector. - Controller and Minimal API routes are handled the same way, on the paths
Microsoft.AspNetCore.OpenApiwrites (/minimal/{id}for/minimal/{id:int?}).
// Split paths with your own operationIds for the shorter forms
builder.Services.AddOpenApi("v1", o => o.AddOptionalRouteParameters(
operationIdSelector: (operationId, omitted) => $"{operationId}_{string.Join("_", omitted)}"));
// Or keep one path per operation and only mark the parameters required
builder.Services.AddOpenApi("v1", o => o.AddOptionalRouteParameters(OpenApiOptionalRouteParameterMode.RequiredOnly));
A later call on the same options replaces the mode and selector of an earlier one; the transformer keeps the pipeline position of the first call. Each document is described from its own operations, so documents split by GroupName ([ApiExplorerSettings(GroupName = "v2")]) only get the forms of their own routes. Without AddOptionalRouteParameters, the document keeps the framework output: one path per operation (/api/archive/{year}/{month}). The Swashbuckle package describes the same routes the same way.
Scalar
No helper is needed; list the documents:
app.MapOpenApi();
app.MapScalarApiReference(o => o.AddDocument("v1").AddDocument("v2"));
Interceptors namespace
Microsoft.AspNetCore.OpenApi intercepts AddOpenApi and reads XML doc comments through generated code. Its build file enables that only for a project that references Microsoft.AspNetCore.OpenApi itself, and this package brings it in as a transitive dependency. A host that does not reference Microsoft.AspNetCore.OpenApi directly fails with CS9137 (the interceptors feature is not enabled in this namespace), on Microsoft.NET.Sdk.Web and on Microsoft.NET.Sdk alike. Enable the namespace in the host project (or add a direct PackageReference to Microsoft.AspNetCore.OpenApi):
<InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated</InterceptorsNamespaces>
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Asp.Versioning.Abstractions (>= 8.1.0)
- Asp.Versioning.Http (>= 8.1.0)
- Asp.Versioning.Mvc (>= 8.1.0)
- Asp.Versioning.Mvc.ApiExplorer (>= 8.1.0)
- CSharpEssentials.AspNetCore (>= 6.5.2)
- Microsoft.AspNetCore.OpenApi (>= 10.0.0 && < 11.0.0)
- Microsoft.OpenApi (>= 2.7.5 && < 3.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.