-
Couldn't load subscription status.
- Fork 716
Injecting ApiVersion as a parameter in a controller action method. #922
-
I'm currently migrating from Microsoft.AspnetCore.Versioning 5.0.0 to Asp.Versioning 7.0.0-preview.2
In the Index method of my HomeController I have an ApiVersion parameter that worked in the old version, but cannot be bound in the new version.
I'm using it to generate links in the response that point to the appropriate controllers which match the version supplied by the user.
This is my ConfigureServices method in Startup
//...
services
.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
// Default version
var defaultVersion = Configuration.GetValue("RESTworld:Versioning:DefaultVersion", "*");
if (defaultVersion == "*")
{
options.ApiVersionSelector = new LatestApiVersionSelector();
}
else
{
if (!ApiVersionParser.Default.TryParse(defaultVersion, out var parsedVersion) || parsedVersion is null)
throw new ArgumentOutOfRangeException("RESTworld:Versioning:DefaultVersion", defaultVersion, "The setting for \"RESTworld:Versioning:DefaultVersion\" was neither \"*\" nor a valid API version.");
options.DefaultApiVersion = parsedVersion;
}
// Version parameter
var allowQueryStringVersioning = Configuration.GetValue("RESTworld:Versioning:AllowQueryParameterVersioning", false);
if (allowQueryStringVersioning)
{
options.ApiVersionReader = ApiVersionReader.Combine(
new MediaTypeApiVersionReader(versionParameterName),
new QueryStringApiVersionReader(versionParameterName));
}
else
{
options.ApiVersionReader = new MediaTypeApiVersionReader(versionParameterName);
}
})
.AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
// Do not advertise versioning through query parameter as this is only intended for legacy clients and should not be visible as it is not considered RESTfull.
if (options.ApiVersionParameterSource is not MediaTypeApiVersionReader)
{
options.ApiVersionParameterSource = new MediaTypeApiVersionReader(versionParameterName);
}
})
.AddMvc();
//...
public class LatestApiVersionSelector : IApiVersionSelector
{
/// <inheritdoc/>
public ApiVersion SelectVersion(HttpRequest request, ApiVersionModel model)
=> model.ImplementedApiVersions.OrderByDescending(v => v).FirstOrDefault() ?? ApiVersion.Default ?? ApiVersion.Neutral;
}
[ApiController]
[Produces("application/hal+json")]
[Route("")]
[ApiVersionNeutral]
public class HomeController : ControllerBase
{
[HttpGet]
[ProducesResponseType(typeof(Resource), StatusCodes.Status200OK)]
public virtual IActionResult Index(ApiVersion version)
{
// ...
}
}
Beta Was this translation helpful? Give feedback.
All reactions
I still think this looks to be a strange setup, but I believe I was able to reproduce your scenario. Some of the core routing setup had to change starting in 6.0. There have been a couple edge case breaking behaviors that I haven't caught, but - in general - it's been for the better.
One of these edge cases is a version-neutral controller. It is necessary to have an API version to build out the route tree. Didn't realize there is a scenario where node could be built without all of the discovered endpoints. This leads to incomplete collation and an incomplete set of API versions, which can produce the behavior you're seeing. Version-neutral endpoints have a special mapping to ApiVersion.Ne...
Replies: 2 comments 7 replies
-
By not bound, I presume you mean that the action is being reached, but the version parameter is null? This is a bit of a strange setup, but it should work. I think I can create a repro, but you have something handy, it would speed things up.
Beta Was this translation helpful? Give feedback.
All reactions
-
I still think this looks to be a strange setup, but I believe I was able to reproduce your scenario. Some of the core routing setup had to change starting in 6.0. There have been a couple edge case breaking behaviors that I haven't caught, but - in general - it's been for the better.
One of these edge cases is a version-neutral controller. It is necessary to have an API version to build out the route tree. Didn't realize there is a scenario where node could be built without all of the discovered endpoints. This leads to incomplete collation and an incomplete set of API versions, which can produce the behavior you're seeing. Version-neutral endpoints have a special mapping to ApiVersion.Neutral, which is what is used when no version is specified.
A version-neutral endpoint can accept an API version, which appears to be what you expect. This is supposed to work, but since the collation is incomplete, not all of the versions are present. I have a pending fix and it will go into the final release (as well as 6.x). An important difference from previous implementations is that you can no longer use just any ol' API version for neutral endpoint. It must version defined somewhere in the application (ultimately because it's needed for routing).
I've seen people make mistakes in the past with version-neutral endpoints that accept a perfectly valid, but nonexistent version on a neutral endpoint. It was difficult to detect and track down. While the new behavior might be limiting, it's also more sane and likely what people want.
Look for the fix soon.
Beta Was this translation helpful? Give feedback.
All reactions
-
I'm still getting an error with the 7.0.0-rc.1 version.
[15:13:45 INF] Request starting HTTP/2 GET https://localhost:5432/ - -
[15:13:45 DBG] 1 candidate(s) found for the request path '/'
[15:13:45 DBG] Request matched endpoint '400 Invalid API Version'
[15:13:45 DBG] Static files was skipped as the request already matched an endpoint.
[15:13:45 INF] Executing endpoint '400 Invalid API Version'
[15:13:45 INF] Request contained the API version 'b3', which is not valid
[15:13:45 INF] Executed endpoint '400 Invalid API Version'
[15:13:45 INF] Request finished HTTP/2 GET https://localhost:5432/ - - - 400 0 - 19.8483ms
Beta Was this translation helpful? Give feedback.
All reactions
-
Hmmm... I'm not sure i have enough information to understand where b3 is coming from. That definitely would not be valid. A repro would be best, but the full HTTP request might be enough.
Beta Was this translation helpful? Give feedback.
All reactions
-
I found out, where b3 is coming from:
Chromium (Chrome, Edge) is sending the following Accept header:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
The part application/signed-exchange;v=b3 is required by the application to take part in "Signed HTTP Exchanges" defined here https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#section-8.6
It looks as if starting from a certain version, all Chromium Browsers are sending that header automatically.
I removed that header and then it worked.
Is there a possibility to configure that unknown versions should be treated as ApiVersion.Neutral or still hit the defined IApiVersionSelector?
Beta Was this translation helpful? Give feedback.
All reactions
-
When versioning by media type, the MediaTypeApiVersionReader uses a default parameter name of v but that isn't required. There are several options:
- Use a different parameter name for versioning that will not conflict; for example,
application/json; api-version=1.0
a. Configure this vianew MediaTypeApiVersionReader( "api-version" )or whatever name you want - Use the new
MediaTypeApiVersionReaderBuilderto include or exclude media types - Extend
MediaTypeApiVersionReaderor roll yourIApiVersionReader
Someone else recently just ran into this exact same issue with this header. The new MediaTypeApiVersionReaderBuilder provides a couple of ways you can deal with it.
Mutually Inclusive
var reader = new MediaTypeApiVersionReaderBuilder() .Parameter( "v" ) .Include( "application/json" ) // other media types are ignored .Build();
Mutually Exclusive
var reader = new MediaTypeApiVersionReaderBuilder() .Parameter( "v" ) .Exclude( "application/signed-exchange" ) // ignore this media type .Build();
The MediaTypeApiVersionReader doesn't know or care about a specific media type so that it can work with any of them.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
The "Mutually Exclusive" way was exactly what I needed.
Thanks!
Beta Was this translation helpful? Give feedback.