There are a lot of blog posts on API-Versioning like this and this.
Some people are even getting religious about it.
Because of all the existing posts I won’t go through that in this blog post. No more than saying that, I personally prefer including the versioning in the accept header, without using vendor specific custom media types. I’m not much for letting the version have its own header tag either.
// I like this
Header 'Accept: application/json; v=1.0'
// vs this
Header 'Accept: application/vnd.myapp.v1.user+json'
// or this
Header 'api-version: 1.0'
Love it or hate it, that is my preferred way.
So if I am not doing that, what is this blog post for then?
This blog post will show you how to easily setup API-Versioning and Swagger generation of OAS 3 in your ASP.Net Core API using Swashbuckle 5.0 (at the time of writing, this is in beta version) and Microsoft aspnet-api-versioning.
The assumptions
I am working from the assumption that you have created a basic startup project of .Net Core API and that you have at least a basic knowledge of .Net development and how to include nuget-packages.
What nugets you need to add
Microsoft.AspNetCore.Mvc.Versioning (3.1.1)
Microsoft.AspnetCore.MvcVersioning.ApiExplorer (3.1.0)
Swashbuckle.AspNetCore (5.0.0-beta)
Lets go to the code…
Getting this to work is fairly easy.
The following steps are needed:
- Add the classes needed to config the API-Version and Swagger.
- Include method calls in the Startup class.
So what are the classes needed to config?
Well I am fairly useless when it comes to naming stuff but I use 2 to 3 classes. 2 if I am doing versioning in the accept header and 3 if I am setting the version in a separate header. I will include both types and comment out the parts not needed for versioning in the accept header.
– ApiVersionSetupExtensionMethods
This static class only have one extension method that is used for the configuration of API-Versioning and it is pretty self explanatory, but I have kept and added some comments anyway.
public static class ApiVersionSetupExtensionMethods
{
public static void AddApiVersioningAndExplorer(this IServiceCollection services)
{
// Add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
// Note: the specified format code will format the version as "'v'major[.minor][-status]"
services.AddVersionedApiExplorer(
options =>
{
options.GroupNameFormat = "'v'VVV";
});
services.AddApiVersioning(
options =>
{
options.ReportApiVersions = true;
// Use this if you would like a new separate header to set the version in.
// Eg: Header 'api-version: 1.0'
//options.ApiVersionReader = new HeaderApiVersionReader("api-version");
// Use this if you would like to use the MediaType version header.
// Eg: Header 'Accept: application/json; v=1.0'
options.ApiVersionReader = new MediaTypeApiVersionReader();
// This is set to true so that we can set what version to select (default version)
// when no version has been selected.
options.AssumeDefaultVersionWhenUnspecified = true;
// And this is where we set how to select the default version.
options.ApiVersionSelector = new CurrentImplementationApiVersionSelector(options);
});
}
}
– SwaggerGenerationSetupExtensionMethods
This class is of course used for configuring the swagger/OAS3 generation options. Once again i have kept and added comments to explain some of the stuff.
public static class SwaggerGenerationSetupExtensionMethods
{
public static void AddSwaggerGeneration(this IServiceCollection services)
{
services.AddSwaggerGen(options =>
{
// Resolve the IApiVersionDescriptionProvider service
// Note: that we have to build a temporary service provider here because one has not been created yet
using (var serviceProvider = services.BuildServiceProvider())
{
var provider = serviceProvider.GetRequiredService<IApiVersionDescriptionProvider>();
// 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, description.CreateInfoForApiVersion());
}
}
// Add a custom operation filter which sets default values if you are using a separate header for version
//options.OperationFilter<SwaggerDefaultValues>();
// Integrate xml comments, add this when we have come that far.
//options.IncludeXmlComments(XmlCommentsFilePath);
// Describe all enums as strings instead of integers.
//options.DescribeAllEnumsAsStrings();
});
}
public static OpenApiInfo CreateInfoForApiVersion(this ApiVersionDescription description)
{
var info = new OpenApiInfo()
{
Title = $"Test API {description.ApiVersion}",
Version = description.ApiVersion.ToString(),
Description = "This is the swagger base info for API.",
Contact = new OpenApiContact() { Name = "Contact Name", Email = "some@email.com" },
//TermsOfService = "UnComment and put the URI here to the terms of service.",
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;
}
public static void UseSwaggerUIAndAddApiVersionEndPointBuilder(this IApplicationBuilder app, IApiVersionDescriptionProvider provider)
{
app.UseSwaggerUI(c =>
{
// Build a swagger endpoint for each discovered API version
foreach (var description in provider.ApiVersionDescriptions)
{
c.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
}
});
}
}
– SwaggerDefaultValues
This final class is used to document the implicit API-version parameter.
In other words, setup some of the parameter descriptions and default values for the generated API-Version parameter. This class is only needed if you are setting the version in a separate header.
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.Parameters == null)
{
return;
}
foreach (var parameter in operation.Parameters)
{
var description = context.ApiDescription
.ParameterDescriptions
.First(p => p.Name == parameter.Name);
var routeInfo = description.RouteInfo;
if (parameter.Description == null)
{
parameter.Description = description.ModelMetadata?.Description;
}
if (routeInfo == null)
{
continue;
}
if (parameter.In != ParameterLocation.Path && parameter.Schema.Default == null)
{
parameter.Schema.Default = new OpenApiString(routeInfo.DefaultValue.ToString());
}
parameter.Required |= !routeInfo.IsOptional;
}
}
Include in Startup class
To conclude, lets use our config classes from the Startup class.
I have included the whole thing so there are no misunderstandings.
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddApiVersioningAndExplorer();
services.AddSwaggerGeneration();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseSwagger();
app.UseSwaggerUIAndAddApiVersionEndPointBuilder(provider);
app.UseMvc();
}
}
Rounding off
There are other ways in witch to do this and there are definitely a lot more configuration options to use/include if needed. But this should get you started.
I will probably get back to this topic again…
One response to “ASP.Net Core base setup of Swashbuckle/Swagger OAS3 and API-Versioning”
[…] I wrote about how to generate Swagger/OAS 3 with versioning in an ASP.Net Core API.But why stop there, lets do some touch-ups on that […]
LikeLike