Heavily based on the sample from the docs. Install Asp.Versioning.Http and Asp.Versioning.Mvc.ApiExplorer nugets, add following infrastrucutre classes:
public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
{
private readonly IApiVersionDescriptionProvider provider;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigureSwaggerOptions"/> class.
/// </summary>
/// <param name="provider">The <see cref="IApiVersionDescriptionProvider">provider</see> used to generate Swagger documents.</param>
public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider ) => this.provider = provider;
/// <inheritdoc />
public void Configure( SwaggerGenOptions options )
{
// add a swagger document for each discovered API version
// note: you might choose to skip or document deprecated API versions differently
foreach ( var description in provider.ApiVersionDescriptions )
{
options.SwaggerDoc( description.GroupName, CreateInfoForApiVersion( description ) );
}
}
private static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription description )
{
var text = new StringBuilder( "An example application with OpenAPI, Swashbuckle, and API versioning." );
var info = new OpenApiInfo()
{
Title = "Example API",
Version = description.ApiVersion.ToString(),
};
info.Description = text.ToString();
return info;
}
}
and
public class SwaggerDefaultValues : IOperationFilter
{
/// <inheritdoc />
public void Apply( OpenApiOperation operation, OperationFilterContext context )
{
var apiDescription = context.ApiDescription;
operation.Deprecated |= apiDescription.IsDeprecated();
// REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1752#issue-663991077
foreach ( var responseType in context.ApiDescription.SupportedResponseTypes )
{
// REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/b7cf75e7905050305b115dd96640ddd6e74c7ac9/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs#L383-L387
var responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString();
var response = operation.Responses[responseKey];
foreach ( var contentType in response.Content.Keys )
{
if ( !responseType.ApiResponseFormats.Any( x => x.MediaType == contentType ) )
{
response.Content.Remove( contentType );
}
}
}
if ( operation.Parameters == null )
{
return;
}
// REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/412
// REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/413
foreach ( var parameter in operation.Parameters )
{
var description = apiDescription.ParameterDescriptions.First( p => p.Name == parameter.Name );
if ( parameter.Description == null )
{
parameter.Description = description.ModelMetadata?.Description;
}
if ( parameter.Schema.Default == null &&
description.DefaultValue != null &&
description.DefaultValue is not DBNull &&
description.ModelMetadata is ModelMetadata modelMetadata )
{
// REF: https://github.com/Microsoft/aspnet-api-versioning/issues/429#issuecomment-605402330
var json = JsonSerializer.Serialize( description.DefaultValue, modelMetadata.ModelType );
parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson( json );
}
parameter.Required |= description.IsRequired;
}
operation.Parameters.Add(new OpenApiParameter
{
In = ParameterLocation.Header,
Name = "X-Api-Version",
Example = new OpenApiString(apiDescription.GroupName)
});
}
}
Add them to registration:
builder.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
builder.Services.AddSwaggerGen(options => options.OperationFilter<SwaggerDefaultValues>());
Register versioning/api explorer infrastructure:
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddApiVersioning(options =>
{
options.ReportApiVersions = true;
options.ApiVersionReader = new HeaderApiVersionReader("X-Api-Version");
})
.AddApiExplorer()
.EnableApiVersionBinding();
Add minimal API routes with versioning:
var versionSet = app.NewApiVersionSet()
.HasApiVersion(new ApiVersion(1))
.HasApiVersion(new ApiVersion(2))
.ReportApiVersions()
.Build();
app.MapGet("/weatherforecast", () =>
{
return "1.0";
})
.WithApiVersionSet(versionSet)
.HasApiVersion(1)
.MapToApiVersion(1)
.WithOpenApi();
app.MapGet("/weatherforecast", () => "2.0")
.WithApiVersionSet(versionSet)
.HasApiVersion(2)
.MapToApiVersion(2)
.WithOpenApi();
add swagger AFTER routes:
app.UseSwagger();
app.UseSwaggerUI(options =>
{
var descriptions = app.DescribeApiVersions();
// build a swagger endpoint for each discovered API version
foreach ( var description in descriptions )
{
var url = $"/swagger/{description.GroupName}/swagger.json";
var name = description.GroupName.ToUpperInvariant();
options.SwaggerEndpoint( url, name );
}
});
Result:

Issues I was not able to figure out:
- Version header is required, without it endpoint is not reachable
- I use parameter with default value to make swagger pass the version (
Example = new OpenApiString(apiDescription.GroupName)) which arguably is not the cleanest approach
Full code @github.