These days REST APIs are used everywhere. As applications begin to mature, there are numerous times when APIs need to be updated. These need to be done systematically, ensuring that the existing consumer clients continue to work as usual without breaking the existing functionality. At the same time, it is necessary to update the new APIs and publish them.
This is where API Versioning plays a crucial role. If done properly, it can help avoid chaos and maintain consistency across different API integrations. As part of this article, I'll explain the different versioning approaches that can be used for API versioning in ASP.Net Core.
There are 4 out of box approaches to version APIs...
Query string parameter
URL path segment
HTTP header
Media type parameter
Of these, the query string parameter is the default approach to versioning. One can also combine API versioning approaches or define one's custom method of API versioning. We'll check each of this one by one. Also, we'll use the default template of the web API project that comes with asp net core to understand each of these.
Creating a Web API project using ASPNet Core
Assuming you have AspNet Core installed on your machine, we'll create a folder RESTAPIVersioning
and creating a web API project within this using
dotnet new webapi
Open the ValuesController.cs
file in this project. We'll demonstrate the Query Parameter API versioning approach using this controller. To make this simpler, we'll keep only the Get
action method in this controller and remove all the others.
Now, that this is done, we'll start with the first approach of versioning, using Query string parameters.
1. API versioning using Query String Parameter
We'll first install the NuGet package Microsoft.AspNetCore.Mvc.Versioning
which will help in API versioning.
dotnet add .\QueryStringParam.csproj package Microsoft.AspNetCore.Mvc.Versioning
To enable API versioning, add the following in ConfigureServices
method in Startup.cs
.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
//Check https://github.com/Microsoft/aspnet-api-versioning/wiki/API-Versioning-Options for details on these properties
services.AddApiVersioning(options =>
{
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1,0);
});
}
Now, ASP.Net Core provides an interface IApiVersionReader
which defines the behavior of how an API version is read in its RAW unparsed form from the current HTTP request. For the Query string approach, ASPNet Core provides QueryStringApiVersionReader
implementation out of the box which expects the query string parameter name as api-version
. The default case can be configured in ConfigureServices
method as...
services.AddApiVersioning(options =>
{
options.ApiVersionReader = new QueryStringApiVersionReader();
});
If you plan to provide a custom API version parameter, say v
then,
services.AddApiVersioning(options =>
{
options.ApiVersionReader = new QueryStringApiVersionReader("v");
});
We can now split the controllers by version numbers to support each API version.
namespace RESTAPIVersioning.Controllers
{
[ApiVersion("1.0")]
[Route("api/values")]
public class ValuesV1Controller : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "v1-value1", "v1-value2" };
}
}
[ApiVersion("2.0")]
[Route("api/values")]
public class ValuesV2Controller : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "v2-value1", "v2-value2" };
}
}
}
We can now test the APIs using the query parameter api-version
as ...
Both URLs, http://localhost:5000/api/values
and http://localhost:5000/api/values?api-version=1.0
give the response
[
"v1-value1",
"v1-value2"
]
This is because if api-version
is not specified the default version of 1.0
is considered.
While http://localhost:5000/api/values?api-version=2.0
gives the response
[
"v2-value1",
"v2-value2"
]
2. API versioning using URL path segment
In this approach, versioning is explicitly specified in the URL path itself. Here, it is not possible to have a default API version for a URL segment. Let's create a new controller, to demonstrate this, say ColoursController.cs
.
namespace RESTAPIVersioning.Controllers
{
//Versioning by Query Parameters
[ApiVersion("1.0")]
[Route("api/v{ver:apiVersion}/colours")]
public class ColoursV1Controller : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "v1-red", "v1-orange" };
}
}
[ApiVersion("2.0")]
[Route("api/v{ver:apiVersion}/colours")]
public class ColoursV2Controller : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "v2-red", "v2-orange" };
}
}
}
Now, calling the API http://localhost:5000/api/v1.0/colours
would give the response
[
"v1-red",
"v1-orange"
]
while calling the API http://localhost:5000/api/v2.0/colours
would give
[
"v2-red",
"v2-orange"
]
We could slightly update the above and also support version 3 in the 2nd controller (i.e. version the Action method) as ...
namespace RESTAPIVersioning.Controllers
{
//Versioning by URL path segments
[ApiVersion("1.0")]
[Route("api/v{ver:apiVersion}/colours")]
public class ColoursV1Controller : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "v1-red", "v1-orange" };
}
}
[ApiVersion("2.0")]
[ApiVersion("3.0")]
[Route("api/v{ver:apiVersion}/colours")]
public class ColoursV2Controller : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "v2-red", "v2-orange" };
}
[HttpGet, MapToApiVersion("3.0")]
public IEnumerable<string> GetV3()
{
return new string[] { "v3-red", "v3-orange" };
}
}
}
With this, calling API http://localhost:5000/api/v3.0/colours
would give a response...
[
"v3-red",
"v3-orange"
]
3. API versioning using HTTP Header
In this case, we'll have to provide the API version parameter as part of the HTTP request headers. Let's, understand this better using the CarsController as follows...
namespace RESTAPIVersioning.Controllers
{
//Versioning by HTTP headers
[ApiVersion("1.0")]
[Route("api/cars")]
public class CarssV1Controller : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "v1-bmw", "v1-mercedes" };
}
}
[ApiVersion("2.0")]
[Route("api/cars")]
public class CarsV2Controller : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "v2-bmw", "v2-mercedes" };
}
}
}
Now to enable HTTP header API versioning add the following in the ConfigureServices
method in Startup.cs
services.AddApiVersioning(options =>
{
options.ApiVersionReader = new HeaderApiVersionReader( "api-version" );
});
Unlike, the Query parameter approach there is no standard or default HTTP header here. You will have to explicitly state this.
Now, calling the API http://localhost:5000/api/cars
with api-version = 1.0
in the Request HTTP header would give the response
[
"v1-bmw",
"v1-mercedes"
]
while calling the API http://localhost:5000/api/cars
with api-version = 2.0
in Request HTTP header would give
[
"v2-bmw",
"v2-mercedes"
]
4. API versioning using Media type parameter
Here we'll have to pass the versioning parameter along with the media types for content negotiation. Let's create a MangoesController.cs
file to demonstrate this.
namespace RESTAPIVersioning.Controllers
{
//Versioning by HTTP headers
[ApiVersion("1.0")]
[Route("api/mangoes")]
public class MangoesV1Controller : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "v1-alphanso", "v1-kesar" };
}
}
[ApiVersion("2.0")]
[Route("api/mangoes")]
public class MangoesV2Controller : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "v2-alphanso", "v2-kesar" };
}
}
}
Now to enable Media Type API versioning add the following in the ConfigureServices
method in Startup.cs
services.AddApiVersioning(options =>
{
options.ApiVersionReader = new MediaTypeApiVersionReader();
});
Now, calling the API http://localhost:5000/api/mangoes
without any media type header gives the values...
[
"v1-alphanso",
"v1-kesar"
]
while calling the API http://localhost:5000/api/cars
with media type set to accept: text/plain;v=1.0
in Request HTTP header would give the same response.
[
"v1-alphanso",
"v1-kesar"
]
Also, calling it with media type set to accept: text/plain;v=2.0;
would give...
[
"v2-alphanso",
"v2-kesar"
]
With this, we have covered all the widely used API versioning approaches. You may download the code from my GitHub repository at this link. Hope this was useful!