Change in an API is inevitable as our knowledge and experience of a system improve, but implementing those changes on a system might break existing client interaction. To provide longterm support on constantly evolving APIs, we need versioning. Project versioning is typically accomplished by creating different versions of build packages (assemblies), but REST API versioning is harder because we need to support multiple API versions together in the same project.
Microsoft’s ASP.NET versioning NuGet package (Microsoft.AspNetCore.Mvc.Versioning
) gives a powerful, and easy-to-use methods for adding API versioning semantics to the existing REST APIs. Besides, it is compliant with the versioning semantics outlined by the Microsoft REST API. Also, it supports four kinds of API versioning and they are listed below:
Query String Versioning
It is the default pattern enabled in the versioning package and it is quite simple and easy to configure.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
services.AddApiVersioning(config =>
{
config.DefaultApiVersion = new ApiVersion(2, 0);
config.AssumeDefaultVersionWhenUnspecified = true;
});
}
DemoController.cs
[ApiController]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
[Route("api/[controller]")]
public class DemoController : ControllerBase
{
[HttpGet]
[MapToApiVersion("1.0")]
public string GetV1() => "Version 1.0";
[HttpGet]
[MapToApiVersion("2.0")]
public string GetV2() => "Version 2.0";
}
HTTP Calls
GET /api/demo?api-version=1.0 HTTP/1.1
Host: localhost:8029
Version 1.0
GET /api/demo?api-version=2.0 HTTP/1.1
Host: localhost:8029
Version 2.0
GET /api/demo HTTP/1.1
Host: localhost:8029
Version 2.0
In the above approach, api-version
is used as default query string parameter, but we can customize this name as like below:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddApiVersioning(config =>
{
config.DefaultApiVersion = new ApiVersion(2, 0);
config.AssumeDefaultVersionWhenUnspecified = true;
config.ApiVersionReader = new QueryStringApiVersionReader("v");
});
}
HTTP Header Versioning
In this approach, version information is sent via HTTP header. We can customize this HTTP header name as like below:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
services.AddApiVersioning(config =>
{
config.DefaultApiVersion = new ApiVersion(2, 0);
config.AssumeDefaultVersionWhenUnspecified = true;
config.ApiVersionReader = new HeaderApiVersionReader("x-version");
});
}
HTTP Calls
GET /api/demo HTTP/1.1
Host: localhost:8029
x-version: 1.0
Version 1.0
GET /api/demo HTTP/1.1
Host: localhost:8029
x-version: 2.0
Version 2.0
GET /api/demo HTTP/1.1
Host: localhost:8029
Version 2.0
Media Type Versioning
The parameters used in media types for content negotiation can contain custom input that can be used to drive API versioning.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
services.AddApiVersioning(config =>
{
config.DefaultApiVersion = new ApiVersion(2, 0);
config.AssumeDefaultVersionWhenUnspecified = true;
config.ApiVersionReader = new MediaTypeApiVersionReader("v");
});
}
HTTP Calls
Note: If we sent both Accept
and Content-Type
header together, it will read the version information from Accept
header.
GET /api/demo HTTP/1.1
Host: localhost:8029
Accept: text/plain;v=1.0
Version 1.0
GET /api/demo HTTP/1.1
Host: localhost:8029
Accept: text/plain;v=2.0
Version 2.0
GET /api/demo HTTP/1.1
Host: localhost:8029
Content-Type: application/json;v=1.0
Version 1.0
GET /api/demo HTTP/1.1
Host: localhost:8029
Content-Type: application/json;v=2.0
Version 2.0
URL Path Versioning
It is also the most commonly used pattern but it depends on routing logic.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
services.AddApiVersioning(config =>
{
config.DefaultApiVersion = new ApiVersion(2, 0);
config.AssumeDefaultVersionWhenUnspecified = true;
config.ApiVersionReader = new UrlSegmentApiVersionReader();
});
}
DemoController.cs
[ApiController]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class DemoController : ControllerBase
{
...
}
HTTP Calls
GET /api/v1/demo HTTP/1.1
Host: localhost:8029
Version 1.0
GET /api/v2/demo HTTP/1.1
Host: localhost:8029
Version 2.0
By using, ApiVersionReader.Combine()
method, we can enable more than one type of versioning in our application. In the below code, I have combined all four types of versioning in a single project.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
services.AddApiVersioning(config =>
{
config.DefaultApiVersion = new ApiVersion(2, 0);
config.AssumeDefaultVersionWhenUnspecified = true;
config.ApiVersionReader = ApiVersionReader.Combine(
new HeaderApiVersionReader("x-version"),
new QueryStringApiVersionReader("version"),
new UrlSegmentApiVersionReader(),
new MediaTypeApiVersionReader("v"));
});
}
You can find the complete source code of this project in my GitHub repository.
In my next blog, I will explain how to configure Swagger along with this ASP.NET versioning package.