Before we continue, let’s create another controller: CompaniesV2Controller
(for example’s sake), which will represent a new version of our existing one. It is going to have just one Get
action:
using Asp.Versioning; using CompanyEmployees.Core.Services.Abstractions; using Microsoft.AspNetCore.Mvc; namespace CompanyEmployees.Infrastructure.Presentation.Controllers; [ApiVersion("2.0")] [Route("api/companies")] [ApiController] public class CompaniesV2Controller : ControllerBase { private readonly IServiceManager _service; public CompaniesV2Controller(IServiceManager service) => _service = service; [HttpGet] public async Task<IActionResult> GetCompanies() { var companies = await _service.CompanyService .GetAllCompaniesAsync(trackChanges: false); return Ok(companies); } }
By using the [ApiVersion(“2.0”)]
attribute, we are stating this controller is version 2.0. After that, let’s version our original controller as well:
[ApiVersion("1.0")] [Route("api/companies")] [ApiController] public class CompaniesController : ControllerBase
If you remember, we configured versioning to use 1.0 as the default API version with this line: opt.AssumeDefaultVersionWhenUnspecified = true;
. Therefore, if a client doesn’t state the required version, our API will use this one:
curl --location 'https://localhost:5001/api/companies' --header 'Accept: application/json'
If we inspect the Headers tab of the response, we are going to find that controller V1 was assigned for this request:
Of course, you can place a breakpoint in GetCompanies
actions in both controllers and confirm which endpoint was hit. Now, let’s see how we can provide a version inside the request.
Using Query String
We can provide a version within the request using a query string in the URI. Let’s test this with an example:
curl --location 'https://localhost:5001/api/companies?api-version=2.0' --header 'Accept: application/json'
So, we get the same response body. But, we can inspect the response headers to make sure that version 2.0 is used:
Using URL Versioning
For the URL versioning to work, we have to modify the route in our controller:
[ApiVersion("2.0")] [Route("api/{v:apiversion}/companies")] [ApiController] public class CompaniesV2Controller : ControllerBase
Also, let’s just slightly modify the GetCompanies
action in this controller, so we could see the difference in Postman by just inspecting the response body:
[HttpGet] public async Task<IActionResult> GetCompanies() { var companies = await _service.CompanyService .GetAllCompaniesAsync(trackChanges: false); var companiesV2 = companies.Select(x => $"{x.Name} V2"); return Ok(companiesV2); }
We are creating a projection from our result collection by iterating through each element, modifying the Name
property to contain the V2 suffix, and extract it to a new collection companiesV2
. Now, we can test it:
curl --location 'https://localhost:5001/api/2.0/companies' --header 'Accept: application/json'
One thing to mention, we can’t use the query string pattern to call the CompaniesV2Controller
anymore. We can use it for version 1.0, though.
HTTP Header Versioning
If we don’t want to change the URI of the API, we can send the version in the HTTP Header. To enable this, we have to modify our configuration:
public static void ConfigureVersioning(this IServiceCollection services) { services.AddApiVersioning(opt => { opt.ReportApiVersions = true; opt.AssumeDefaultVersionWhenUnspecified = true; opt.DefaultApiVersion = new ApiVersion(1, 0); opt.ApiVersionReader = new HeaderApiVersionReader("api-version"); }).AddMvc(); }
And to revert the Route
change in our controller:
[ApiVersion("2.0")] [Route("api/companies")] [ApiController] public class CompaniesV2Controller : ControllerBase
Let’s test these changes:
curl --location 'https://localhost:5001/api/companies' --header 'Accept: application/json' --header 'api-version: 2.0'
If we want to support query string versioning, we should use a new QueryStringApiVersionReader
class instead:
opt.ApiVersionReader = new QueryStringApiVersionReader("api-version");