dotnet / dotnet/aspnetcore

EndpointMetadataApiDescriptionProvider Misses Route Parameters

Open
#41,773 7 comments 2 reactions 0 assignees View on GitHub
area-minimal bug feature-openapi
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

### Is there an existing issue for this?

- [X] I have searched the existing issues

### Describe the bug

`EndpointMetadataApiDescriptionProvider` misses creating `ApiDescriptionParameter` instances that are defined as `BindingSource.Custom` or `BindingSource.Special`. As seen [here](../blob/release/6.0/src/Mvc/Mvc.ApiExplorer/src/EndpointMetadataApiDescriptionProvider.cs#L103), ``EndpointMetadataApiDescriptionProvider`` only considers parameter candidates via the `ParameterInfo` acquired through Reflection.

Some `BindingSource.Special` parameters _should_ be ignored, such as `CancellationToken`. API Versioning defines the `ApiVersion` as `BindingSource.Special` because the value can typically come from one or more places (even zero is technically possible). The value of `ApiVersion` is set and retrieved through `IApiVersioningFeature`.

This behavior is a deviation from `DefaultApiDescriptionProvider`, which considers and creates an `ApiParameterDescription` for route parameters, even if they don't have a formal `ParameterInfo` partner (as seen [here](../blob/release/6.0/src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs#L233)).

Declaring the formal parameter `ApiVersion version` will not work because `TryParse` is delegated to `IApiVersionParser` so that a developer can opt to change the behavior if they want to. Parsing the value at this point in the pipeline is also too late and affects routing or would result in parsing the value more than once.

Ultimately, `EndpointMetadataApiDescriptionProvider` should create an `ApiParameterDescription` for every route parameter in the `RoutePattern` (e.g. template); regardless of whether there is a corresponding method parameter.

>It should be noted that this behavior does **not** affect routing and it only happens when a developer elects to version solely by URL segment.

### Expected Behavior

`EndpointMetadataApiDescriptionProvider` should have parity with `DefaultApiDescriptionProvider` wherever possible (obviously modeling binding isn't available for _Minimal APIs_).

Consider the following:

```c#
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddApiVersioning(options => options.ApiVersionReader = new UrlSegmentApiVersionReader())
.AddApiExplorer(options => options.options.SubstituteApiVersionInUrl = true);

app.MapGet( "v{version:apiVersion}/weatherforecast/{city}", () =>
{
return Enumerable.Range( 1, 5 ).Select( index =>
new WeatherForecast
(
DateTime.Now.AddDays( index ),
Random.Shared.Next( -20, 55 ),
summaries[Random.Shared.Next( summaries.Length )]
) );
} )
.WithApiVersionSet(app.NewApiVersionSet().HasApiVersion(1.0).Build());
```

This should produce `ApiDescription.RelativePath` with `v1/weatherforecast/{city}`, but instead produces `v{version}/weatherforecast/{city}`. Worse still, there is no `ApiParameterDescription` for `{version}`. API Versioning relies on the existence of the parameter description and substitutes the token in the template with the corresponding API version value.

This behavior can be reproduced without API Versioning from a pure API Explorer perspective, but the API will not be reachable since the route parameter wouldn't be matched.

### Steps To Reproduce

_No response_

### Exceptions (if any)

_No response_

### .NET Version

6.0.300

### Anything else?

This was first reported in dotnet/aspnet-api-versioning#830.

ASP.NET Core 6.0

```
.NET SDK (reflecting any global.json):
Version: 6.0.300
Commit: 8473146e7d

Runtime Environment:
OS Name: Windows
OS Version: 10.0.22000
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\6.0.300\

Host (useful for support):
Version: 6.0.5
Commit: 70ae3df4a6

.NET SDKs installed:
6.0.202 [C:\Program Files\dotnet\sdk]
6.0.203 [C:\Program Files\dotnet\sdk]
6.0.300 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
Microsoft.AspNetCore.App 3.1.25 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 3.1.25 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 6.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.