There are times where you want to add a new action with the same route but you don’t want to break backwards compatibility. One option that could help with this is to use a custom action constraint.
I’ve created a controller with two endpoints that have the same route. The Get action could be the original route. The new version of the endpoint is GetLatest which can be accessed if the client uses a custom header x-app-version=1.0
.
[Route("api/[controller]")]
[ApiController]
public class VersionTestController : ControllerBase
{
[HttpGet]
[Route("")]
public async Task<IActionResult> Get()
{
return Ok("not versioned");
}
[HttpGet]
[Route("")]
[VersionActionConstraint("1.0")]
public async Task<IActionResult> GetLatest()
{
return Ok("versioned");
}
}
To define this constraint, you just need to implement the IActionConstraint interface. You can just use it without having to register it in your startup.cs so its really easy to add.
I made this an attribute with the a string version passed into the constructor. It will then check if there is a x-app-version
header in the HttpContext request and compare it to the instance version.
public class VersionActionConstraintAttribute : Attribute, IActionConstraint
{
public int Order => 1;
private const string _versionHeader = "x-app-version";
private readonly string _version;
public VersionActionConstraintAttribute(string version)
{
_version = version;
}
public bool Accept(ActionConstraintContext context)
{
if (context.RouteContext.HttpContext.Request.Headers.TryGetValue(_versionHeader, out var value))
{
if(value == _version)
{
return true;
}
}
return false;
}
}