Swagger (OpenAPI) and Versioning – ASP.NET Core

With Swagger (OpenAPI) we can generate automatic interactive documentation of our Web APIs. Sometimes we will have versioned Web APIs which we want to document with Swagger. In this we will see how to do this.

Versions Used

  • ASP.NET Core 3.1
  • Swashbuckle.AspNetCore: 5.4.1

Github: https://github.com/gavilanch/ASPNETCoreSwaggerVersioning

Suppose we have a versioned Web API, where we use folders to separate the different versions of our controllers. Thus, we have V1 and V2 folders inside our Controllers folder, and a WeatherForecastController in each folder:

swag1

Therefore, the namespace of each controller corresponds to its folder, like this:

// Version 1
namespace WebApiSwaggerVersion.Controllers.V1
{
[ApiController]
[Route("api/v1/[controller]")]
public class WeatherForecastController : ControllerBase
{…}
}
// Version 2
namespace WebApiSwaggerVersion.Controllers.V2
{
[ApiController]
[Route("api/v2/[controller]")]
public class WeatherForecastController : ControllerBase
{…}
}

view raw
Controllers.cs
hosted with ❤ by GitHub

Problem

If we configure Swagger like this:

namespace WebApiSwaggerVersion
{
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.AddControllers();
services.AddSwaggerGen(config =>
{
config.SwaggerDoc("WebAPI", new OpenApiInfo
{
Title = "Movies API",
Description = "This is a Web API for Movies operations",
TermsOfService = new Uri("https://udemy.com/user/felipegaviln/"),
License = new OpenApiLicense()
{
Name = "MIT"
},
Contact = new OpenApiContact()
{
Name = "Felipe Gavilán",
Email = "felipe_gavilan887@hotmail.com",
Url = new Uri("https://gavilan.blog/")
}
});
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI(config =>
{
config.SwaggerEndpoint("/swagger/WebAPI/swagger.json", "MoviesAPI");
});
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}

view raw
Startup.cs
hosted with ❤ by GitHub

And we go to the swagger user interface by accessing the endpoint: /swagger/index.html, we come across the following:

swag2

Which can be a problem, because your users will see each endpoint repeated by each version of the Web API.

Solution

The solution is to inform Swagger that our Web API is versioned. We must also write code to tell Swagger how to differentiate one version from another. In our case it is according to the namespace where the controller is located.

To tell Swagger that the Web API is versioned, we create a Swagger document for each version, and add an endpoint for each version:

// Unnecessary code removed for brevity
namespace WebApiSwaggerVersion
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(config =>
{
var titleBase = "Movies API";
var description = "This is a Web API for Movies operations";
var TermsOfService = new Uri("https://udemy.com/user/felipegaviln/");
var License = new OpenApiLicense()
{
Name = "MIT"
};
var Contact = new OpenApiContact()
{
Name = "Felipe Gavilán",
Email = "felipe_gavilan887@hotmail.com",
Url = new Uri("https://gavilan.blog/")
};
config.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = titleBase + " v1",
Description = description,
TermsOfService = TermsOfService,
License = License,
Contact = Contact
});
config.SwaggerDoc("v2", new OpenApiInfo
{
Version = "v2",
Title = titleBase + " v2",
Description = description,
TermsOfService = TermsOfService,
License = License,
Contact = Contact
});
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI(config =>
{
config.SwaggerEndpoint("/swagger/v1/swagger.json", "MoviesAPI v1");
config.SwaggerEndpoint("/swagger/v2/swagger.json", "MoviesAPI v2");
});
}
}
}

view raw
Startup.cs
hosted with ❤ by GitHub

The next task is to tell Swagger how to differentiate one version of a controller apart from another. For that we must create a convention.

With a convention we inform Swagger about our architecture. This way we can control how Swagger generates the Swagger document, and therefore, the UI.

Create the following class:

public class GroupingByNamespaceConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
var controllerNamespace = controller.ControllerType.Namespace;
var apiVersion = controllerNamespace.Split(".").Last().ToLower();
if (!apiVersion.StartsWith("v")) { apiVersion = "v1"; }
controller.ApiExplorer.GroupName = apiVersion;
}
}

What we do is group controllers according to the last segment of their namespace. Thus, the controllers whose namespace ends in v1 will be grouped under the name “v1“, those that end in v2 are grouped as “v2“, etc.

Now we must apply the convention. For that we go to AddControllers in ConfigureServices and add the convention:

services.AddControllers(options =>
{
options.Conventions.Add(new GroupingByNamespaceConvention());
});

view raw
Startup.cs
hosted with ❤ by GitHub

And this is it. With this we can see in the Swagger UI that we have our endpoints separated by versions in different Swagger Docs:

swag3

Course

If you want to learn how to develop RESTful Web APIs with ASP.NET Core, get my udemy course today:

https://www.udemy.com/course/building-restful-web-apis-with-aspnet-core/?referralCode=DAFD27F4028D04B62181

Regards!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s