Reputation: 21
We have an API controller class that lists all supported versions with ApiVersion attribute. Versioning format used is, versiongroup.minor-status
[ApiController]
[ApiVersion("2020-11-01-preview")]
[ApiVersion("2020-11-01.1-preview")]
[ApiVersion("2021-11-01.2-preview")]
[ApiVersion("2021-11-01.3-preview")]
[Route("/routeData")]
public class TestController { }
Now as the list of ApiVersion attributes might keep growing, wanted to do a customize check, where (Suppose for a given controller we maintain a list of supported versions) for an incoming request with apiversion in query param, we could check if its a supported version for the given controller or not? Something like
[ApiController]
[ValidateApiVersion]
[Route("/routeData")]
public class TestController { }
Want to know
I tried the following: Introducing
public sealed class ValidApiVersionsAttribute : Attribute, IActionConstraint
{
public bool Accept(ActionConstraintContext context) {..}
}
startup has: services.AddApiVersioning();
For some reason my 'Accept' method never gets invoked.
Upvotes: 0
Views: 791
Reputation: 4388
An API version is nothing more than metadata. It is not an IActionConstraint nor a route constraint. The attributes provided out-of-the-box are extensible and customizable.
For example:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ApplicationApiVersionsAttribute : ApiVersionsBaseAttribute
{
public ApplicationApiVersionsAttribute()
: base(new[]
{
new(new DateTime(2020, 11, 1), "preview"),
new(new DateTime(2020, 11, 1), 1, 0, "preview"),
new(new DateTime(2020, 11, 1), 2, 0, "preview"),
new(new DateTime(2020, 11, 1), 3, 0, "preview"),
}) { }
}
Note that the base attribute constructor takes ApiVersion. Attribute data is limited to simple types such as string and numeric literals. Decorating a controller is limited to magic strings by the language, but the underlying types always use actual ApiVersion instances.
You can then decorator your controller as follows:
[ApiController]
[ApplicationApiVersions]
[Route("/routeData")]
public class TestController : ControllerBase { }
Attributes are just one way that metadata can be exposed to API Versioning. While a few attributes are provided out-of-the-box for your convenience, what API Versioning really cares about is IApiVersionProvider, which those attributes implement. You can roll your own attributes from scratch if you prefer to as long as they implement IApiVersionProvider.
You can also use the conventions API so that you don't need attributes at all; for example:
services.AddApiVersioning(
options =>
{
options.Conventions
.Controller<TestController>()
.HasApiVersion(2020, 11, 01, "preview")
.HasApiVersion(2020, 11, 01, 1, 0, "preview")
.HasApiVersion(2020, 11, 01, 2, 0, "preview")
.HasApiVersion(2020, 11, 01, 3, 0, "preview");
}
That's just scratching the surface. There are many ways that conventions can be applied. You can also roll your own conventions.
public class ApplicationApiVersionsConvention : IControllerConvention
{
public bool Apply(
IControllerConventionBuilder controller,
ControllerModel controllerModel)
{
controller.HasApiVersion(2020, 11, 01, "preview")
.HasApiVersion(2020, 11, 01, 1, 0, "preview")
.HasApiVersion(2020, 11, 01, 2, 0, "preview")
.HasApiVersion(2020, 11, 01, 3, 0, "preview");
}
}
This would indiscriminately apply the specified API versions to all controllers. The controllerModel
can be used for filtering. To configure your convention, you just add it to the setup:
services.AddApiVersioning(
options => options.Conventions.Add(new ApplicationApiVersionsConvention());
Conventions can be very useful, but tend to be application-specific. The only provided convention is VersionByNamespaceConvention, which applies a single API version based on the .NET namespace that your controller resides in. That enables no attribution and one-line configuration.
A final aside. If you establish an API version policy - something like N-2
, that will implicitly help in managing your code because you won't have a list of versions to support. For previews, I would consider limiting it to just one if possible. New previews generally don't need side-by-side support. You application needs may be different, but having a sound versioning policy ahead of time will make maintenance much easier.
I can revise or expand the answer if that doesn't fully cover your use case.
Upvotes: 1