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:
Therefore, the namespace of each controller corresponds to its folder, like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
{…} | |
} |
Problem
If we configure Swagger like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
}); | |
} | |
} | |
} |
And we go to the swagger user interface by accessing the endpoint: /swagger/index.html, we come across the following:
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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"); | |
}); | |
} | |
} | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
services.AddControllers(options => | |
{ | |
options.Conventions.Add(new GroupingByNamespaceConvention()); | |
}); |
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:
Course
If you want to learn how to develop RESTful Web APIs with ASP.NET Core, get my udemy course today:
Regards!