Skip to content

Commit 4de59a5

Browse files
committed
Fix: Use route parameter metadata to correctly mark catch-all parameters as optional
Previously, ApiExplorer marked all parameters as optional if the route template contained a catch-all marker ("{**"). This update refines the logic by leveraging ApiParameterContext.RouteParameters and the IsCatchAll flag to identify and mark only the catch-all parameter as optional. This change aligns the API metadata with the actual routing semantics and addresses dotnet#60392.
1 parent eec0e99 commit 4de59a5

File tree

2 files changed

+26
-10
lines changed

2 files changed

+26
-10
lines changed

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,9 +317,12 @@ internal static void ProcessIsRequired(ApiParameterContext context, MvcOptions m
317317

318318
if (parameter.Source == BindingSource.Path && parameter.RouteInfo != null)
319319
{
320-
// If the route template contains a catch-all parameter marker ("{**"), treat it as optional.
321-
var template = context.ActionDescriptor.AttributeRouteInfo?.Template;
322-
if (!string.IsNullOrEmpty(template) && template.Contains("{**"))
320+
// Locate the corresponding route parameter metadata.
321+
var routeParam = context.RouteParameters
322+
.FirstOrDefault(rp => string.Equals(rp.Name, parameter.Name, StringComparison.OrdinalIgnoreCase));
323+
324+
// If the parameter is defined as a catch-all, mark it as optional.
325+
if (routeParam != null && routeParam.IsCatchAll)
323326
{
324327
parameter.IsRequired = false;
325328
}

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

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,33 @@ namespace Microsoft.AspNetCore.Mvc.Description;
3333
public class DefaultApiDescriptionProviderTest
3434
{
3535
[Fact]
36-
public void CatchAllParameter_IsReportedAsOptional()
36+
public void OnlyCatchAllParameter_IsReportedAsOptional()
3737
{
38-
// Arrange: Create an action descriptor with a catch-all route template.
38+
// Arrange: Create an action descriptor with a multi-parameter route template.
3939
var action = CreateActionDescriptor();
40-
action.AttributeRouteInfo = new AttributeRouteInfo { Template = "{**catchAllParameter}" };
40+
action.AttributeRouteInfo = new AttributeRouteInfo
41+
{
42+
Template = "/products/{category}/items/{group}/inventory/{**any}"
43+
};
4144

4245
// Act: Get the API descriptions using the existing helper.
4346
var descriptions = GetApiDescriptions(action);
4447

45-
// Assert: There should be one description, with one parameter named "catchAllParameter"
46-
// and its IsRequired property should be false.
48+
// Assert: Only the 'any' parameter should be optional.
4749
var description = Assert.Single(descriptions);
48-
var parameter = Assert.Single(description.ParameterDescriptions, p => p.Name == "catchAllParameter");
49-
Assert.False(parameter.IsRequired);
50+
var categoryParameter = Assert.Single(description.ParameterDescriptions,
51+
p => string.Equals(p.Name, "category", StringComparison.OrdinalIgnoreCase));
52+
var groupParameter = Assert.Single(description.ParameterDescriptions,
53+
p => string.Equals(p.Name, "group", StringComparison.OrdinalIgnoreCase));
54+
var anyParameter = Assert.Single(description.ParameterDescriptions,
55+
p => string.Equals(p.Name, "any", StringComparison.OrdinalIgnoreCase));
56+
57+
// The non-catch-all parameters should be required.
58+
Assert.True(categoryParameter.IsRequired);
59+
Assert.True(groupParameter.IsRequired);
60+
61+
// The catch-all parameter should be optional.
62+
Assert.False(anyParameter.IsRequired);
5063
}
5164

5265
[Fact]

0 commit comments

Comments
 (0)