OpenAPI and API Versioning

This section documents ASP.NET Core on .NET 10 (LTS), the current release — the minimal hosting model, the middleware pipeline, dependency injection, Minimal APIs, MVC & Razor Pages, Blazor with the current render modes, SignalR and gRPC, EF Core, ASP.NET Core Identity and policy-based authorization, output caching, rate limiting, and Native-AOT-aware building — as described by the official documentation at Microsoft Learn, which is the reference these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. .NET ships a major release every November and its APIs continue to evolve: the examples here target .NET 10 / C# 14.

This section’s bibliography lists the reference material consulted while preparing these pages.

The built-in Microsoft.AspNetCore.OpenApi package generates an OpenAPI document describing both Minimal API and controller endpoints, with no separate reflection-based tool required.

Adding OpenAPI generation

// dotnet add package Microsoft.AspNetCore.OpenApi
builder.Services.AddOpenApi();

var app = builder.Build();
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();                 // serves /openapi/v1.json
}

app.MapGet("/orders/{id:int}", GetOrder)
   .WithName("GetOrder")
   .WithSummary("Fetch a single order")
   .ProducesProblem(StatusCodes.Status404NotFound);

On controllers, [ProducesResponseType<OrderDto>(StatusCodes.Status200OK)] and [ProducesResponseType(StatusCodes.Status404NotFound)] describe responses the same way. See OpenAPI support in ASP.NET Core.

OpenAPI 3.1 and JSON Schema 2020-12

NET 10 generates OpenAPI 3.1 documents by default, whose schema dialect is JSON Schema 2020-12 rather

than the OpenAPI-specific subset earlier versions used — nullable types are expressed as a type array (["string", "null"]) instead of a separate nullable: true keyword, and constructs like examples (plural) and const are natively available. Pin an older version explicitly if a downstream tool still expects 3.0:

builder.Services.AddOpenApi(o => o.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_0);

YAML output

Request YAML instead of JSON by giving the endpoint a .yaml/.yml suffix:

app.MapOpenApi("/openapi/{documentName}.yaml");

Both formats describe the same document — pick whichever a consuming tool expects.

XML doc comments

Enable the project’s XML documentation file and .NET 10 folds <summary>, <param>, <returns>, and <remarks> comments into the generated document as operation/parameter descriptions:

<PropertyGroup>
  <GenerateDocumentationFile>true</GenerateDocumentationFile>
  <NoWarn>$(NoWarn);CS1591</NoWarn> <!-- don't warn on every undocumented public member -->
</PropertyGroup>
/// <summary>Fetches a single order by id.</summary>
/// <param name="id">The order id.</param>
/// <returns>The order, or 404 if it does not exist.</returns>
app.MapGet("/orders/{id:int}", GetOrder);

Document, operation, and schema transformers

Transformers post-process the generated document — add a security scheme, tweak a summary, redact a schema — without hand-editing the output:

builder.Services.AddOpenApi(o =>
{
    o.AddDocumentTransformer((document, context, ct) =>
    {
        document.Info.Title = "Orders API";
        document.Info.Version = "v1";
        return Task.CompletedTask;
    });

    o.AddOperationTransformer((operation, context, ct) =>
    {
        operation.Summary ??= context.Description.ActionDescriptor.DisplayName;
        return Task.CompletedTask;
    });

    o.AddSchemaTransformer((schema, context, ct) =>
    {
        if (context.JsonTypeInfo.Type == typeof(OrderDto))
        {
            schema.Description = "An order as returned to API consumers.";
        }
        return Task.CompletedTask;
    });
});

Implement IOpenApiDocumentTransformer, IOpenApiOperationTransformer, or IOpenApiSchemaTransformer as a class (resolved from DI, so it can take constructor dependencies) instead of a lambda when the logic grows. See Customize OpenAPI documents.

IOpenApiDocumentProvider

Inject IOpenApiDocumentProvider to obtain the generated OpenApiDocument in code — for example to write it to a file at build time, feed a client generator, or serve it from a non-default endpoint:

public sealed class OpenApiExportService(IOpenApiDocumentProvider provider)
{
    public async Task<string> GetJsonAsync(CancellationToken ct)
    {
        var document = await provider.GetOpenApiDocumentAsync(ct);
        return document.SerializeAsJson(Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_1);
    }
}

Serving a UI: Scalar and Swagger UI

The Microsoft.AspNetCore.OpenApi package produces the document only; add a UI on top of it:

// dotnet add package Scalar.AspNetCore
app.MapScalarApiReference();          // serves an interactive UI at /scalar/v1

Swagger UI (via Swashbuckle’s Swashbuckle.AspNetCore.SwaggerUI package) and NSwag’s bundled UI both work the same way, pointed at the /openapi/v1.json endpoint. See Using generated OpenAPI documents.

Generating clients

  • NSwag — generates a typed C#/TypeScript HTTP client from the OpenAPI document (nswag openapi2csclient).

  • Kiota — Microsoft’s cross-language client generator (kiota generate --openapi openapi.json --language CSharp), used heavily with Microsoft Graph-style APIs but works against any OpenAPI 3.x document.

  • Refit — an alternative that hand-declares the client interface rather than generating it from the document; see HTTP Client and Resilience.

API versioning

The Asp.Versioning.Mvc (controllers) and Asp.Versioning.Http (Minimal APIs) packages support versioning by URL segment, query string, header, or media type:

// dotnet add package Asp.Versioning.Mvc
builder.Services.AddApiVersioning(o =>
{
    o.DefaultApiVersion = new ApiVersion(1);
    o.AssumeDefaultVersionWhenUnspecified = true;
    o.ReportApiVersions = true;
}).AddMvc();
[ApiVersion(1)]
[ApiVersion(2)]
[Route("api/v{version:apiVersion}/[controller]")]
public sealed class OrdersController : ControllerBase
{
    [HttpGet, MapToApiVersion(2)]
    public IActionResult GetV2() => Ok(new { schema = "v2" });
}

Each versioned Asp.Versioning-aware endpoint can contribute its own OpenAPI document (one document per API version) via AddApiExplorer() combined with AddOpenApi(…​) per version group. See OpenAPI and API versioning and the Asp.Versioning project docs.