-
Notifications
You must be signed in to change notification settings - Fork 714
-
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!
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 1 comment
-
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.
Beta Was this translation helpful? Give feedback.