dotnet / aspnet-api-versioning

Provides a set of libraries which add service API versioning to ASP.NET Web API, OData with ASP.NET Web API, and ASP.NET Core.
MIT License
3.06k stars 703 forks source link

ApiVersionMatcherPolicy Invalidates valid candidates #1101

Open bergmania opened 2 months ago

bergmania commented 2 months ago

Is there an existing issue for this?

Describe the bug

I'm experiencing an issue where ApiVersionMatcherPolicy invalidates a valid candidate, from what looks to be an attempt to optimize performance.

The setup is quite cumbersome, but I managed to setup a minimal solution to verify it (See below). The use case is when I have a plain old WebAPI without any versioning and I have two endpoints with the same route. Furthermore I need to have a dynamic route, to even hit this code in the ApiVersionMatcherPolicy.

When I try to call ny POST endpoint, it returns 404, because it have been invalidated by ApiVersionMatcherPolicy that finds the GET endpoint as the bestMatch. This is a problem because the ApiVersionMatcherPolicy.Order is hardcoded to be lower than HttpMethodMatcherPolicy.Order.

The issue is originally reported in Umbraco here: https://github.com/umbraco/Umbraco-CMS/issues/16434

Expected Behavior

My GET endpoint is hit, like if ApiVersionMatcherPolicy is removed from the service collection.

Steps To Reproduce

I have setup a minimal setup, that shows the issue.

https://github.com/bergmania/DemoIssueWithAPIVersioning

On a new MVC project, I added a dependency

<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />

I added the following in program.cs

builder.Services.AddApiVersioning(x=>x.AssumeDefaultVersionWhenUnspecified = true).AddApiExplorer();
builder.Services.AddSingleton<MyDynamicControllerRoute>();

Before var app = builder.Build(); and the following afterwards

app.MapDynamicControllerRoute<MyDynamicControllerRoute>("/{**slug}");

Furtheremore I have a single file with two classes

using System.Net.Mime;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;

namespace WebApplication5;

public class MyDynamicControllerRoute : DynamicRouteValueTransformer
{
    public override ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
    {
        return ValueTask.FromResult(values);
    }
}

[ApiController]
[Route("api/")]
public class ShowBugController : Controller
{
    [HttpPost("test")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [Consumes(MediaTypeNames.Application.Json)] // This is important because it leads to a lower (better) score when the request enter the ApiVersionMatcherPolicy, even when requested with http GET
    public IActionResult MyPost()
    {
        return Ok("OK POST");
    }

    [HttpGet("test")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    public IActionResult MyGet()
    {

        return Ok("OK GET");
    }
}

Exceptions (if any)

No response

.NET Version

8.0.303

Anything else?

No response

bergmania commented 2 months ago

Just to verify, I tried to downgrade to 6.0 before this PR https://github.com/dotnet/aspnet-api-versioning/commit/3857a332057d970ad11bac0edfdbff8a559a215d

And it works as expected in that version