Skip to content

Fix: Mark catch-all route parameters as optional (#60392) #60544

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

Merged

Conversation

mapedersen
Copy link
Contributor

Fix: Catch-All Route Parameters Incorrectly Marked as Required in ApiExplorer

Description

This pull request addresses an issue in ASP.NET Core’s ApiExplorer where catch-all route parameters (e.g., {**catchAllParameter}) are erroneously marked as required, despite being optional in practice. At runtime, catch-all parameters match all values—including empty strings—so they should be considered optional. This discrepancy results in misleading API metadata, negatively impacting API documentation and client generation.

Changes Made

  • Added a new unit test in DefaultApiDescriptionProviderTest to validate that catch-all route parameters are reported as optional.
  • Followed a Test-Driven Development (TDD) approach: the test initially failed (confirming the bug) and then served as a baseline for the fix.
  • Updated the ProcessIsRequired method in DefaultApiDescriptionProvider:
    • Now examines the route template for a catch-all marker ({**).
    • If a catch-all marker is detected, the parameter’s IsRequired property is set to false.
    • Otherwise, the existing behavior is maintained.
  • Ensures that ApiExplorer metadata aligns with actual routing semantics.

Testing

  • The new unit test confirms that when a controller action defines a catch-all route parameter, it is correctly marked as optional.
  • The complete test suite was executed to verify that the changes do not negatively impact other aspects of ApiExplorer functionality.

Impact

  • This change is limited to API metadata generation logic and does not affect the runtime behavior of existing applications.
  • By accurately reflecting the optional nature of catch-all parameters, this fix improves:
    • The quality of generated API documentation (e.g., OpenAPI/Swagger).
    • The reliability of client libraries and automated testing tools by minimizing potential errors.

- Added a unit test in DefaultApiDescriptionProviderTest to validate that catch-all route parameters (e.g., "{**catchAllParameter}") are reported as optional.
- Modified ProcessIsRequired in DefaultApiDescriptionProvider to detect catch-all parameters (by checking for the "{**" pattern in the route template) and mark them as not required.
- This change aligns ApiExplorer metadata with the actual runtime behavior, ensuring more accurate API documentation.
- Follows TDD practices by first writing a failing test and then implementing the fix.
@dotnet-issue-labeler dotnet-issue-labeler bot added the area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates label Feb 21, 2025
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Feb 21, 2025
@mapedersen
Copy link
Contributor Author

@dotnet-policy-service agree

parameter.IsRequired = true;
// If the route template contains a catch-all parameter marker ("{**"), treat it as optional.
var template = context.ActionDescriptor.AttributeRouteInfo?.Template;
if (!string.IsNullOrEmpty(template) && template.Contains("{**"))
Copy link
Member

Choose a reason for hiding this comment

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

Does this effectively end up setting IsRequired = false for route parameters that appear in a route with a catch-all? For example, /products/{category}/items/{group}/inventory/{**any} would end up treating category and group as optional with this logic?

I wonder if there is a way we can use the ApiParameterContext.RouteParameters property to check if a route template part is a catch-all and only set it for the matching parameter source.

How does that sound?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the feedback!

I have updated the implementation to use the ApiParameterContext.RouteParameters property along with the IsCatchAll flag to accurately determine which parameter is a catch-all.

With this change, only the parameter explicitly marked as catch-all is treated as optional. This ensures that in a route such as: /products/{category}/items/{group}/inventory/{**any}

only the {**any} parameter is considered optional, while {category} and {group} remain required.

I appreciate your suggestion, as this update better aligns the ApiExplorer behavior with the intended routing semantics.

Please let me know if you have any further feedback.

…ers 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.
Copy link
Member

@captainsafia captainsafia left a comment

Choose a reason for hiding this comment

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

Thanks for addressing the feedback -- LGTM!

@captainsafia captainsafia merged commit e01e85f into dotnet:main Feb 24, 2025
27 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the 10.0-preview3 milestone Feb 24, 2025
@mapedersen mapedersen deleted the fix-catch_all-optional-issue_60392 branch February 26, 2025 10:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants