aspnet-api-versioning icon indicating copy to clipboard operation
aspnet-api-versioning copied to clipboard

ApiVersionMatcherPolicy Invalidates valid candidates

Open bergmania opened this issue 1 year ago • 1 comments
trafficstars

Is there an existing issue for this?

  • [X] I have searched the existing issues

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 avatar Aug 06 '24 11:08 bergmania

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

bergmania avatar Aug 06 '24 12:08 bergmania