Skip to content

Commit 6bbd520

Browse files
Removing from API Description parameters when inferred (FromPath) and not in the route (#39607)
* Changing from Path to ModelBinding when inferred * Removing whitespace * Removing the inferred paraemter when not in the route * Avoiding extra allocation * Remove extra line * Updating comment * Updating test to use BindingSource.Path * Remove extra line * Update src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs Co-authored-by: Stephen Halter <[email protected]> Co-authored-by: Stephen Halter <[email protected]>
1 parent 209ff13 commit 6bbd520

File tree

2 files changed

+72
-3
lines changed

2 files changed

+72
-3
lines changed

src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs

+21-3
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Linq;
5+
using Microsoft.AspNetCore.Http.Metadata;
56
using Microsoft.AspNetCore.Mvc.Abstractions;
67
using Microsoft.AspNetCore.Mvc.ActionConstraints;
78
using Microsoft.AspNetCore.Mvc.Controllers;
89
using Microsoft.AspNetCore.Mvc.Formatters;
910
using Microsoft.AspNetCore.Mvc.Infrastructure;
1011
using Microsoft.AspNetCore.Mvc.ModelBinding;
12+
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
1113
using Microsoft.AspNetCore.Routing;
1214
using Microsoft.AspNetCore.Routing.Template;
1315
using Microsoft.Extensions.Options;
@@ -239,11 +241,13 @@ private void ProcessRouteParameters(ApiParameterContext context)
239241
routeParameters.Add(routeParameter.Name!, CreateRouteInfo(routeParameter));
240242
}
241243

242-
foreach (var parameter in context.Results)
244+
for (var i = context.Results.Count - 1; i >= 0; i--)
243245
{
246+
var parameter = context.Results[i];
247+
244248
if (parameter.Source == BindingSource.Path ||
245-
parameter.Source == BindingSource.ModelBinding ||
246-
parameter.Source == BindingSource.Custom)
249+
parameter.Source == BindingSource.ModelBinding ||
250+
parameter.Source == BindingSource.Custom)
247251
{
248252
if (routeParameters.TryGetValue(parameter.Name, out var routeInfo))
249253
{
@@ -258,6 +262,20 @@ private void ProcessRouteParameters(ApiParameterContext context)
258262
parameter.Source = BindingSource.Path;
259263
}
260264
}
265+
else
266+
{
267+
if (parameter.Source == BindingSource.Path &&
268+
parameter.ModelMetadata is DefaultModelMetadata defaultModelMetadata &&
269+
!defaultModelMetadata.Attributes.Attributes.OfType<IFromRouteMetadata>().Any())
270+
{
271+
// If we didn't see the parameter in the route and no FromRoute metadata is set, it probably means
272+
// the parameter binding source was inferred (InferParameterBindingInfoConvention)
273+
// probably because another route to this action contains it as route parameter and
274+
// will be removed from the API description
275+
// https://github.com/dotnet/aspnetcore/issues/26234
276+
context.Results.RemoveAt(i);
277+
}
278+
}
261279
}
262280
}
263281

src/Mvc/Mvc.ApiExplorer/test/DefaultApiDescriptionProviderTest.cs

+51
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,57 @@ public void GetApiDescription_PopulatesParametersThatAppearOnRouteTemplate_AndHa
243243
}
244244
}
245245

246+
[Fact]
247+
public void GetApiDescription_WithInferredBindingSource_ExcludesPathParametersWhenNotPresentInRoute()
248+
{
249+
// Arrange
250+
var action = CreateActionDescriptor(nameof(FromModelBinding));
251+
action.AttributeRouteInfo = new AttributeRouteInfo { Template = "api/products" };
252+
253+
action.Parameters[0].BindingInfo = new BindingInfo()
254+
{
255+
BindingSource = BindingSource.Path
256+
};
257+
258+
// Act
259+
var descriptions = GetApiDescriptions(action);
260+
261+
// Assert
262+
var description = Assert.Single(descriptions);
263+
Assert.Empty(description.ParameterDescriptions);
264+
}
265+
266+
[Theory]
267+
[InlineData("api/products/{id}")]
268+
[InlineData("api/products/{id?}")]
269+
[InlineData("api/products/{id=5}")]
270+
[InlineData("api/products/{id:int}")]
271+
[InlineData("api/products/{id:int?}")]
272+
[InlineData("api/products/{id:int=5}")]
273+
[InlineData("api/products/{*id}")]
274+
[InlineData("api/products/{*id:int}")]
275+
[InlineData("api/products/{*id:int=5}")]
276+
public void GetApiDescription_WithInferredBindingSource_IncludesPathParametersWhenPresentInRoute(string template)
277+
{
278+
// Arrange
279+
var action = CreateActionDescriptor(nameof(FromModelBinding));
280+
action.AttributeRouteInfo = new AttributeRouteInfo { Template = template };
281+
282+
action.Parameters[0].BindingInfo = new BindingInfo()
283+
{
284+
BindingSource = BindingSource.Path
285+
};
286+
287+
// Act
288+
var descriptions = GetApiDescriptions(action);
289+
290+
// Assert
291+
var description = Assert.Single(descriptions);
292+
var parameter = Assert.Single(description.ParameterDescriptions);
293+
Assert.Equal(BindingSource.Path, parameter.Source);
294+
Assert.Equal("id", parameter.Name);
295+
}
296+
246297
[Fact]
247298
public void GetApiDescription_ParameterDescription_IncludesParameterDescriptor()
248299
{

0 commit comments

Comments
 (0)