In my previous article, I wrote about adding Serilog to the project and configuring it through the appsettings.json file. In this article, I'm going to add Swagger for API documentation and API versioning as well. All codes that I'm going to implement, will be added to the project I've created in the previous article.
Let's get started. As you might know, there are several ways to versioning API, by URL, HTTP header, etc. We are going to add API versioning by URL.
Step 1 - Install package
Open the cool-webpi project and Install Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer package
Step 2 - Add versioning configuration
Open the Startup.cs file and add the following configuration to the ConfigureServices method:
services.AddApiVersioning(options =>
{
// reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions"
options.ReportApiVersions = true;
});
services.AddVersionedApiExplorer(options =>
{
// add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
// note: the specified format code will format the version as "'v'major[.minor][-status]"
options.GroupNameFormat = "'v'VVV";
// note: this option is only necessary when versioning by url segment. the SubstitutionFormat
// can also be used to control the format of the API version in route templates
options.SubstituteApiVersionInUrl = true;
});
Now run the application and you'll get noticed that api-version input added to each API document. Now call WeatherForcast API without providing any value for api-version input:

You get an error indicates that the API version is required. Enter value 1 into the api-version input and call API again and you get the result.
Step 3 - Add versioning to APIs
Create a new folder at the project root and name it Apis. Add two more folders to the Apis folder, V1 and V2. Move Controllers folder to Apis\V1 folder:

Open WeatherForecastController file and add the ApiVersion attribute and modify the Route attribute value:
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class WeatherForecastController : ControllerBase
{
...
Again run the application and you see that api-version input no longer exists:

Now duplicate WeatherForecastController in V2 folder:

[ApiController]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class WeatherForecast2Controller : ControllerBase
{
...
Now we have 2 versions of WeatherForecast API, nevertheless, we are not able to find Swagger documentation for V2:

Check out the official API versioning Github repository to find out more information.
Step 4 - Add versioning to Swagger
When you create an ASP.NET Core Web API project, Swagger is installed by default unless you uncheck this tick:

We are going to change the default configuration of Swagger.
- Update
Swashbuckle.AspNetCoreto the latest version (6 and above). - Create a new folder at the project root and name it
Infrastructureand add another folderSwaggerto theInfrastructurefolder. - Add a new file
SwaggerDefaultValues.cstoSwaggerfolder and copy following codes:
/// <summary>
/// Represents the Swagger/Swashbuckle operation filter used to document the implicit API version parameter.
/// </summary>
/// <remarks>This <see cref="IOperationFilter"/> is only required due to bugs in the <see cref="SwaggerGenerator"/>.
/// Once they are fixed and published, this class can be removed.</remarks>
public class SwaggerDefaultValues : IOperationFilter
{
/// <summary>
/// Applies the filter to the specified operation using the given context.
/// </summary>
/// <param name="operation">The operation to apply the filter to.</param>
/// <param name="context">The current operation filter context.</param>
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.All(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);
parameter.Description ??= description.ModelMetadata.Description;
if (parameter.Schema.Default == null && description.DefaultValue != null)
{
// REF: https://github.com/Microsoft/aspnet-api-versioning/issues/429#issuecomment-605402330
var json = JsonSerializer.Serialize(description.DefaultValue, description.ModelMetadata.ModelType);
parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson(json);
}
parameter.Required |= description.IsRequired;
}
}
}
- Add another file
ConfigureSwaggerOptions.cstoSwaggerfolder and copy following codes:
/// <summary>
/// Configures the Swagger generation options.
/// </summary>
/// <remarks>This allows API versioning to define a Swagger document per API version after the
/// <see cref="IApiVersionDescriptionProvider"/> service has been resolved from the service container.</remarks>
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) => _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 info = new OpenApiInfo()
{
Title = "Cool Web API",
Version = description.ApiVersion.ToString(),
Description = "A Cool Web API Sample.",
Contact = new OpenApiContact { Name = "Mosi Esmailpour", Email = "mo.esmp@gmail.com" },
License = new OpenApiLicense { Name = "MIT", Url = new Uri("https://opensource.org/licenses/MIT") }
};
if (description.IsDeprecated)
info.Description += " This API version has been deprecated.";
return info;
}
}
- Open the
Startup.csfile and inConfigureServicesmethod delete the default swagger configuration:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "CoolWebApi", Version = "v1" });
});
- Add the following configuration:
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
services.AddSwaggerGen(options =>
{
// add a custom operation filter which sets default values
options.OperationFilter<SwaggerDefaultValues>();
});
- In
Configuremethod addIApiVersionDescriptionProviderparameter:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
- In
Configuremethod delete the Swagger UI default configuration:
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "CoolWebApi v1"));
- Add the following codes to configure Swagger UI:
app.UseSwagger(options => { options.RouteTemplate = "api-docs/{documentName}/docs.json"; });
app.UseSwaggerUI(options =>
{
options.RoutePrefix = "api-docs";
foreach (var description in provider.ApiVersionDescriptions)
options.SwaggerEndpoint($"/api-docs/{description.GroupName}/docs.json", description.GroupName.ToUpperInvariant());
});
I've changed the default Swagger route prefix from swagger to api-docs. Right-click the project and select Properties and in the Debug tab change Launch browser value to api-docs (if don't like to change the default swagger route, skip this).

Now run the application and you can see both APIs documents:

Step 5 - Add XML comments to API documentation
Sometimes it would be helpful to add extra information to the APIs. To add XML comments:
- Right-click the project in Solution Explorer and select Edit
CoolWebApi.csprojor double click on it - Add the following lines:
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
- - Open the
Startup.csfile and inConfigureServicesmethod add following codes toservices.AddSwaggerGen:
services.AddSwaggerGen(options =>
{
// add a custom operation filter which sets default values
options.OperationFilter<SwaggerDefaultValues>();
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath);
});
- Open
WeatherForecastControllerclass add following XML comment to theGETmethod:
/// <summary>
/// This API returns list weather forecast.
/// </summary>
[HttpGet]
public IEnumerable<WeatherForecast> Get()
Run the application and can see the preceding comment in front API URL:

Additionally, We can use <remarks> element. The <remarks> element content can consist of text, JSON, or XML:
/// <summary>
/// This API returns list weather forecast.
/// </summary>
/// <remarks>
/// Possible values could be:
///
/// "Freezing", "Bracing", "Chilly", "Cool", "Mild",
/// "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
///
/// Just for demonstration
///
/// GET api/v1/WeatherForecast
/// {
/// }
/// curl -X GET "https://server-url/api/v1/WeatherForecast" -H "accept: text/plain"
///
/// </remarks>
[HttpGet]
public IEnumerable<WeatherForecast> Get()

The response types and error codes are denoted in the XML comments and data annotations:
/// <response code="200">Returns list of weather forecast</response>
/// <response code="400">Noway, just for demonstration</response>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public IEnumerable<WeatherForecast> Get()

For more information about [ProducesResponseType], see API conventions.
Step 6 - Hide a property from Swagger
Sometimes you want to hide some properties of the model and you don't want to be visible in Swagger. The only thing that you need to do is decorating the property with [System.Text.Json.Serialization.JsonIgnore] attribute.
public class DummyModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
[JsonIgnore]
public string FullName { get; set; }
}
Step 7 - Enable JWT Bearer Authorization
To enable Authrozie button in swagger add the following codes:
services.AddSwaggerGen(options =>
{
...
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
},
new List<string>()
}
});
});
Step 8 - Lowercase API URL
To generate lowercase API URLs add this to ConfigureServices method:
services.Configure<RouteOptions>(options => { options.LowercaseUrls = true; });
You can find the source code for this walkthrough on Github.
Top comments (4)
Wow! All things I've just implemented and crawled together from 3-4 different blogposts in just a single straight-forward one - without conflicts.
Great work.
Hey Mohsen,
thanks for the article, can you take a look to this question:
stackoverflow.com/questions/698997...
I haven't tried .NET 6 yet but I will and inform you. Also take look at this article Error Message Reusability and Localization, I passed culutre via http header with swagger default value.
I saw that, but I really need to put the language in the path.