ASP.NET Core API Versioning

There are several versioning strategies that can be used to version ASP.NET Core APIs. In this post we will have a look at the various versioning strategies, how to implement them, and configure Swagger.

There are several versioning strategies that can be used to version ASP.NET Core APIs. In this post we will have a look at the various versioning strategies, how to implement them, and how to configure Swagger UI.

Why do we need version APIs in the first place? Simply, because things change and when they change we want to handle it in the best possible way. For example, we may want to introduce a change to the request or make a change to the response. And, when we change it, we don't want to force all the consumers of our API to upgrade - we want to give our consumers the freedom to upgrade when they are ready. We also have to remain backwards compatible for as long as possible. If we do introduce a breaking change, how do we communicate it to our consumers?

Through an effective versioning strategy we can introduce changes and give our consumers the freedom to upgrade when they are ready. We can also introduce a breaking change and have our consumers accept and change to accommodate the breaking change over time.

Versioning Strategies

In the table below there are several versioning strategies that can be used by APIs to introduce change. My personal preference is to use the query string parameter as it is explicit about the version we use and we can easily specify the default API version to use if it's not specified.

Versioning Strategy Example
Query String https://example.com/api/weather/za?api-version=2.1
URL Segment https://example.com/api/v2/weather/za
Header api-version: 2.0
Media Type Content-Type: application/json;v=2.0

How do we decide what the version number should be? We can use Semantic Versioning (MAJOR.MINOR) to decide whether it should be v3.0 or v3.24 by using the following criteria:

  • MAJOR version when you make incompatible API changes,
  • MINOR version when you add functionality in a backward compatible manner.

Alternatively, we can use version numbers like 1.1 (Beta) by using version format strings.

Installation

To add versioning to your API, add the following NuGet packages to your API project:

Configuration

To configure versioning for the API, add the following to the Program.cs file.

builder.Services.AddApiVersioning(opt =>
{
    opt.DefaultApiVersion = new ApiVersion(1, 0);
    opt.AssumeDefaultVersionWhenUnspecified = true;
    opt.ReportApiVersions = true;
    opt.ApiVersionReader = new QueryStringApiVersionReader();
    //opt.ApiVersionReader = new UrlSegmentApiVersionReader();
    //NOTE: update route to [Route("v{version:apiVersion}/[controller]")]
    //opt.ApiVersionReader = new HeaderApiVersionReader("api-version");
    //opt.ApiVersionReader = new MediaTypeApiVersionReader();
}).AddApiExplorer(opt =>
{
    opt.GroupNameFormat = "'v'VVV";
    opt.DefaultApiVersion = ApiVersion.Default;
});

Example configuration for a query string version reader

In the options, set ReportApiVersions to true to return the available API versions to the caller in the response headers, for example:

api-supported-versions: 1.0,2.0
content-type: application/json; charset=utf-8
transfer-encoding: chunked 

Example response headers for supported versions

Set the ApiVersionReader to one of the following to tell the API where to find the version specification:

  1. QueryStringApiVersionReader
  2. UrlSegmentApiVersionReader
  3. HeaderApiVersionReader
  4. MediaTypeVersionReader

The GroupNameFormat is the version format string to use for the API, for example: v'VVV.

DefaultApiVersion specifies the API version to assume when an API version is not given. The default value is derived from DefaultApiVersion.

Usage

Add the ApiVersion attribute to the controller specifying the API versions that are supported by the controller, for example:

[ApiVersion(1.0, Deprecated = false)]
[ApiVersion(2.0, Deprecated = false)]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
   [HttpGet]
   [MapToApiVersion(1.0)]
   public async Task<ObjectResult> GetForecast()
   {
     //... 
   }

   [HttpGet]
   [MapToApiVersion(2.0)]
   public async Task<ObjectResult> GetConvertedForecast(
     TemperatureUnit unit)
    {
      //...
    }
}

Example showing how to version an API and its controller actions

MapToApiVersion maps the controller action method to a particular group or several groups, where a group is a "version".

Setting Deprecated to true will advertise the API version as deprecated and disable it in Swagger UI (it can still be tried out in Swagger), for example:

api-deprecated-versions: 1.0  
api-supported-versions: 2.0
content-type: application/json; charset=utf-8
transfer-encoding: chunked

Response headers when an API version is deprecated

Note: When using the URL segment version reader you need to update the Controller route to [Route("v{version:apiVersion}/[controller]")]`.

Configuring Swagger UI

To get the API definitions in Swagger UI to display the different API versions we need to update the Swagger endpoint with the group names.

app.UseSwagger().UseSwaggerUI(options =>
{
  foreach (var description in app.DescribeApiVersions())
  {
    options.SwaggerEndpoint(
      $"/swagger/{description.GroupName}/swagger.json",
      description.GroupName
    );
  };
});

Example showing how to update Swagger UI with the different API versions

Version 1 of the WeatherForecast API:

Version 2 of the WeatherForecast API:

The example application in GitHub includes an OperationFilter to automatically set the version number in the operation to the selected version. It also includes a DocumentFilter to change the operations to lowercase.

Conclusion

In this post we showed the different ways in which a version can be applied to an API and how to configure Swagger so that the different versions of the API can be selected.

An example repository is available on GitHub.