question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Support multiple version constructor in ApiVersionAttribute

See original GitHub issue

Since ApiVersionAttribute extends ApiVersionsBaseAttribute witch supports multiple versions in the constructor, would be great to made it visible in the ApiVersionAttribute.

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:10

github_iconTop GitHub Comments

2reactions
commonsensesoftwarecommented, Dec 9, 2021

@sdepouw The first issue that you are seeing is that ApiVersionAttribute is not inherited. This is by design. Honestly, I’m of the opinion that inheritance is the wrong approach for HTTP APIs, but the reasoning is more than my dogma. If API versions were inherited, you can easily get into a situation where 2 different super types implement the same versions and routes. While it’s true that it is technically possible to avoid that from happening it is not clear in code. You also cannot uninherit a method or API version if it was allowed. In the spirit of the Principal of Least Astonishment and intention-revealing code, API versions must be explicitly declared on the types that implement them. One of the original core design principles was that API version information should be pure metadata. In alignment to that, you can use whatever you want for inheritance, but then you must explicitly state which implementations have which versions.

I’ve outline some of the original history around the design of one value per attribute. I ultimately settled on a single value because it aligned with the attribute name (singular) and it is very easy to create your own multi-value attribute. The implementation shown above is correct, although you might prefer another name such as ApiVersionsAttribute. The built-in ApiVersionsBaseAttribute is the foundation for several of the built-in attributes, but it intentionally does not implement IApiVersionProvider because the implementation varies on the derived types. Again - the example above does illustrate putting that all together.

API Versioning does not have a concept of no version, unless the controller is completely ignored/filtered out (ex: a UI controller). As you’ve observed, if you do not specify any versions or mappings, there is an assumed default (e.g. ApiVersioningOptions.DefaultApiVersion). Once you apply an explicit mapping, any mapping, that convention is negated. This allows existing APIs to be grandfathered in without touching them. Once you touch anything, then all bets are off and you have to explicitly specify everything.

Based on your description of the problem, I think you would be better off using conventions as opposed to attributes. These are not mutually exclusive and you can use both, if you want. For example:

public sealed class MyAppConvention : IControllerConvention
{
    public bool Apply( IControllerConventionBuilder controller, ControllerModel controllerModel )
    {
        controller.HasApiVersion(1,0)
                  .HasApiVersion(2,0)
                  .HasApiVersion(2,1)
                  .HasApiVersion(3,0);
    }
}

Then, you’d just add it in the configuration:

services.AddApiVersioning(options =>  options.Conventions.Add( new MyAppConvention() );

You mentioned something about groups. This approach could be enhanced with your own attribute, metadata, or additional convention so that you apply a set of API versions to specific groups. An attribute would probably be the most appropriate and can be accessed via controllerModel.Attributes.

You can also map specific versions to methods this way; however, unless all of your controllers use the same names (which I suspect they don’t), it is probably messy. The best option here is probably to continue using [MapToApiVersion]. No built-in convention exists actions for that, but you could create one that works similar toVersionByNamespaceConvention. If your actions use suffixes such as GetV1, GetV2, and so on, you can figure out which action methods map to which version. If multiple versions map, then you might need something like GetV1V2. It’s easy to identify action methods because they must be public, not static, and have not have [NoAction] applied. You can probably even get the pre-filtered list from controllerModel.Actions without any additional work.

If conventions aren’t workable for you, then I would still recommend taking the attribute approach a step further. Rather than just list all of the versions for each application of the attribute, wrap it up in a single, more meaningful attribute. Something like:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public abstract class ApiVersionsAttribute : ApiVersionsBaseAttribute, IApiVersionProvider
{
    protected ApiVersionsAttribute(params string[] versions) : base (versions) { }
    bool IApiVersionProvider.AdvertiseOnly => false;
    public bool Deprecated { get; set; }
}

// NOTE: you can even change the behavior to allow inheritance; WARNING: There be dragons!
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class GroupXApiVersionsAttribute : ApiVersionsAttribute
{
    public GroupXApiVersionsAttribute() : base ("1.0", "2.0", "2.1", "3.0") { }
}

You could then apply this to your controllers, or even your base controller, with [GroupXApiVersions].

Regardless of which way you go, there is way that you an centrally manage your defined API versions. Careful consideration has to be made as to what is provided out-of-the-box and what you can customize or extend. There might not be a constructor for ApiVersionAttribute that allows multiple versions, but you can create one in ~10 lines of code. That’s a fairly low bar in my box. 😉

I hope that helps.

1reaction
jzeferinocommented, Jun 7, 2018
Read more comments on GitHub >

github_iconTop Results From Across the Web

Support multiple version constructor in ApiVersionAttribute
Since ApiVersionAttribute extends ApiVersionsBaseAttribute witch supports multiple versions in the constructor, would be great to made it ...
Read more >
How to use API versioning in ASP.NET Core
Note how the major version and minor version are passed to the constructor of the ApiVersion class at the time of assigning the...
Read more >
APIController checking for all supported versions without ...
1 Answer 1 ... An API version is nothing more than metadata. It is not an IActionConstraint nor a route constraint. The attributes...
Read more >
How To Support Multiple Versions of ASP.NET Core Web API
Learn how to implement versioning in ASP.NET Core Web APIs using query strings, request URIs, HTTP request headers, or media types.
Read more >
What every ASP.NET Core Web API project needs - Part 2
Open WeatherForecastController file and add the ApiVersion attribute and modify the Route attribute value: [ApiController] [ApiVersion("1.0")] ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found