Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

How to Make no-specific-version Controller Support Any Version? #1111

Unanswered
SuyliuMS asked this question in Q&A
Discussion options

After we upgrading Asp.Versioning.OData and Asp.Versioning.OData.ApiExplorer from 6.0.0 to 8.0.0, we found that if we didn't specify api version in controller, 6.0.0 would allow any version to hit controller, but 8.0.0 would only allow default version.
We set default api version as 1.0.0:

IApiVersioningBuilder apiVersioningBuilder = services.AddApiVersioning(options =>
{
        options.ReportApiVersions = true;
        options.AssumeDefaultVersionWhenUnspecified = true;
        options.DefaultApiVersion = new ApiVersion(1, 0);
}).AddApiExplorer(options =>
{
        options.GroupNameFormat = "'v'VVV";
        options.SubstituteApiVersionInUrl = true;
});

This is a sample controller:

[ApiController]
public class APIVersioningTestController : ControllerBase
{
    [HttpGet]
    [Route("Get1")]
    [AllowAnonymous]
    public string Get1()
    {
        return "Hello World 1!";
    }
}

In 6.0.0, if I call it with ~/Get1?api-version=2.0 or any other versions, it will hit Get1 method.
In 8.0.0, if I call it with ~/Get1?api-version=2.0, it will return 400 bad request. Only ~/Get1?api-version=1.0 is allowed.

I didn't see this behavior is mentioned in release note.
What is your suggest to make the controller can handle any version? We have some proxy controllers, they don't care version.

Thanks!

You must be logged in to vote

Replies: 1 comment

Comment options

There was a bug that could allow dispatching to a versioned endpoint that was only mapped, but not declared. Without digging back through some commits, I don't remember exactly what version that was in. I think that would have shown up in the release notes 🤞🏽. It would have manifest as a fix not as a change in behavior.

There are several possible options for a controller to be excluded from versioning:

Option 1

Mark the controller as API version-neutral. This likely aligns to your intent. A version-neutral API can match any defined API version, including none at all. That may negate the need to use AssumeDefaultVersionWhenUnspecified = true as well, which is only intended for backward compatibility, but is highly abused for other purposes.

[ApiController]
[ApiVersionNeutral]
public class APIVersioningTestController : ControllerBase
{
    [HttpGet]
    [Route("[action]")]
    [AllowAnonymous]
    public string Get() => "Hello World 1!";
}

If an API version is requested by a client, it must exist in at least one API. This avoids clients from requesting ?api-version=42.0 which doesn't exist anywhere.

Option 2

Remove [ApiController] from the controller. API Versioning cannot tell whether ControllerBase or Controller is only for an API or whether it is for a UI. This is determined by the IApiControllerFilter service which excludes non-controllers. The default implementation accepts a collection of IApiControllerSpecification instances. There is a built-in specification which matches the presences of [ApiController].

Option 3

You can replace the IApiControllerFilter service. By default, a controller is accepted if any of the specifications match. You could have your own filter.

Let's say you add an attribute like so:

[AttributeUsage(AttributeTargets.Class, AllowMultiple  = false, Inherited = false)]
internal sealed class UnversionedAttribute : Attribute
{
}

You could then have a filter like:

public sealed class MyControllerFilter : IApiControllerFilter
{
    private readonly IApiControllerSpecification[] specifications;

    public MyControllerFilter( IEnumerable<IApiControllerSpecification> specifications ) =>
        this.specifications = specifications.ToArray();

    public IList<ControllerModel> Apply( IList<ControllerModel> controllers )
    {
          var filtered = controllers.ToList();

          for ( var i = filtered.Count - 1; i >= 0; i-- )
          {
              if ( !IsApiController( filtered[i] ) )
              {
                  filtered.RemoveAt( i );
              }
          }

          return filtered;
    }

    private bool IsApiController( ControllerModel controller )
    {
        // skip unversioned controllers
        if ( controller.Attributes.TypeOf<UnversionedAttribute>().Any() )
        {
            return false;
        }   

        for ( var i = 0; i < specifications.Count; i++ )
        {
            if ( specifications[i].IsSatisfiedBy( controller ) )
            {
                return true;
            }
        }

        return false;
    }
}

then you register in DI with:

builder.Services.AddTransient<IApiControllerFilter, MyControllerFilter>();

If your controller is now decorated as:

[Unversioned]
[ApiController]
public class APIVersioningTestController : ControllerBase
{
    [HttpGet]
    [Route("[action]")]
    [AllowAnonymous]
    public string Get() => "Hello World 1!";
}

it will not be subject to the constraints of API Versioning.

You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
🙏
Q&A
Labels
None yet
2 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.