the place where i create a lot of blog post drafts, that sometimes get published by accident

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”

  1. […] 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 […]

    Like