Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removing from API Description parameters when inferred (FromPath) and not in the route #39607

Merged
merged 9 commits into from
Jan 25, 2022
24 changes: 24 additions & 0 deletions src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq;
using Microsoft.AspNetCore.Http.Metadata;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Template;
using Microsoft.Extensions.Options;
Expand Down Expand Up @@ -239,6 +241,7 @@ private void ProcessRouteParameters(ApiParameterContext context)
routeParameters.Add(routeParameter.Name!, CreateRouteInfo(routeParameter));
}

var inferredPathParametersToRemove = new List<ApiParameterDescription>();
foreach (var parameter in context.Results)
{
if (parameter.Source == BindingSource.Path ||
Expand All @@ -258,9 +261,30 @@ private void ProcessRouteParameters(ApiParameterContext context)
parameter.Source = BindingSource.Path;
}
}
else
{
if (parameter.Source == BindingSource.Path &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: else if?

parameter.ModelMetadata is DefaultModelMetadata defaultModelMetadata &&
!defaultModelMetadata.Attributes.Attributes.OfType<IFromRouteMetadata>().Any())
{
// If we didn't see the parameter in the route and no FromRoute metadata is set, it probably means
// the parameter binding source was inferred (InferParameterBindingInfoConvention)
// probably because another route to this action contains it as route parameter.
// let's add it to be removed from the API description
inferredPathParametersToRemove.Add(parameter);
brunolins16 marked this conversation as resolved.
Show resolved Hide resolved
}

}
}
}

// Remove all parameters detected as Inferred FromPath
// but that are not in the route
foreach (var inferredParameter in inferredPathParametersToRemove)
{
context.Results.Remove(inferredParameter);
}

// Lastly, create a parameter representation for each route parameter that did not find
// a partner.
foreach (var routeParameter in routeParameters)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,46 @@ public void GetApiDescription_PopulatesParametersThatAppearOnRouteTemplate_AndHa
}
}

[Theory]
[InlineData("api/products/{id}", nameof(BindingSource.Path), true)]
[InlineData("api/products", nameof(BindingSource.Path), false)]
[InlineData("api/products", nameof(BindingSource.Body), true)]
[InlineData("api/products", nameof(BindingSource.Query), true)]
public void GetApiDescription_WithInferredBindingSource(
string template,
string inferredBindingSourceName,
bool inferredParameterShouldBeIncluded)
brunolins16 marked this conversation as resolved.
Show resolved Hide resolved
{
// Arrange
var action = CreateActionDescriptor(nameof(FromModelBinding));
action.AttributeRouteInfo = new AttributeRouteInfo { Template = template };

foreach (var actionParameter in action.Parameters)
{
actionParameter.BindingInfo = new BindingInfo()
{
BindingSource = new BindingSource(inferredBindingSourceName, displayName: null, isGreedy: false, isFromRequest: true)
};
}

var expectedBindingSource = new BindingSource(inferredBindingSourceName, displayName: null, isGreedy: false, isFromRequest: true);

// Act
var descriptions = GetApiDescriptions(action);

// Assert
var description = Assert.Single(descriptions);

Assert.Equal(inferredParameterShouldBeIncluded, description.ParameterDescriptions.Count == 1);

if (inferredParameterShouldBeIncluded)
{
var parameter = Assert.Single(description.ParameterDescriptions);
Assert.Equal(expectedBindingSource, parameter.Source);
Assert.Equal("id", parameter.Name);
}
}

[Fact]
public void GetApiDescription_ParameterDescription_IncludesParameterDescriptor()
{
Expand Down