After many tries and read articles I decided to place my issue here. What I want is the following: I am working on api-versioning of an application. A supported version format by .NET Core (Microsoft.AspNetCore.Mvc.Versioning
package) is Major.Minor, and this is what I want to use in the project I work on. What I want is to have is a fall-back version in case when the minor version is not specified by the client.
I am using .NET core 2.2, and using api-version
specified in the header. The corresponding API versioning config looks like this:
services.AddApiVersioning(options => {
options.ReportApiVersions = true;
options.ApiVersionReader = new HeaderApiVersionReader("api-version");
options.ErrorResponses = new ApiVersioningErrorResponseProvider();
});
I have the following two controllers for each version: (the controllers are simplified for the sake of this SO question):
[ApiVersion("1.0")]
[Route("api/[controller]")]
public class ValueControllerV10 : Controller
{
[HttpGet(Name = "collect")]
public String Collect()
{
return "Version 1.0";
}
}
[ApiVersion("1.1")]
[Route("api/[controller]")]
public class ValueControllerV11 : Controller
{
[HttpGet(Name = "collect")]
public String Collect()
{
return "Version 1.1";
}
}
If the client specifies api-version=1.0
then the ValueControllerV10 is used. And of course if the client specifies api-version=1.1
, then the ValueControllerV11 is used, as expected.
And now comes my problem. If the client specifies api-version=1
(so only the major version without the minor version), then the ValueControllerV10 is used. It is because ApiVersion.Parse("1")
is equal to ApiVersion.Parse("1.0")
, if i am not mistaken. But what I want in this case is to invoke the latest version of the given major version, which is 1.1 in my example.
My attempts:
First: Specifying [ApiVersion("1")]
at ValueControllerV11
[ApiVersion("1")]
[ApiVersion("1.1")]
[Route("api/[controller]")]
public class ValueControllerV11 : Controller
{
[HttpGet(Name = "collect")]
public String Collect()
{
return "Version 1.1";
}
}
It does not work, it leads
AmbiguousMatchException: The request matched multiple endpoints
To solve this, I have came up with the second approach:
Second: using custom IActionConstraint
. For this I followed these articles:
- https://stevenknox.net/aspnet-core-mvc-action-priority-using-actionconstraints/
- https://www.strathweb.com/2017/06/using-iactionconstraints-in-asp-net-core-mvc/
I have then created the following class:
[AttributeUsage(AttributeTargets.Method)]
public class HttpRequestPriority : Attribute, IActionConstraint
{
public int Order
{
get
{
return 0;
}
}
public bool Accept(ActionConstraintContext context)
{
var requestedApiVersion = context.RouteContext.HttpContext.GetRequestedApiVersion();
if (requestedApiVersion.MajorVersion.Equals(1) && !requestedApiVersion.MinorVersion.HasValue)
{
return true;
}
return false;
}
}
And used at ValueControllerV11
:
[ApiVersion("1")]
[ApiVersion("1.1")]
[Route("api/[controller]")]
public class ValueControllerV11 : Controller
{
[HttpGet(Name = "collect")]
[HttpRequestPriority]
public String Collect()
{
return "Version 1.1";
}
}
Well, it solves the AmbiguousMatchException
, but overrides the default behaviour of Microsoft.AspNetCore.Mvc.Versioning
package so if the client uses api-version 1.1
, then she get a 404 Not Found back, which is understandable according to the implementation of HttpRequestPriority
Third: Using MapSpaFallbackRoute
in Startup.cs
, conditionally:
app.MapWhen(x => x.GetRequestedApiVersion().Equals("1") && x.GetRequestedApiVersion().MinorVersion == null, builder =>
{
builder.UseMvc(routes =>
{
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new {controller = nameof(ValueControllerV11), action = "Collect"});
});
});
app.UseMvc();
It does not work either, no any impact. The name MapSpaFallbackRoute
gives me also a feeling that it is not what I need to use...
So my question is: How can I introduce a fallback 'use latest' behaviour for the case when the minor version is not specified in api-version
? Thanks in advance!