MapGroup with RoutePattern with RequiredValues of RoutePattern.RequiredValueAny registers incorrect URL routing endpoints
- 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
When adding a `RoutePattern.RequiredValueAny` value to `RoutePattern.RequiredValues` used for `MapGroup()`, the route parameter corresponding to that route pattern segment in the incoming request routing DFA generation in `Microsoft.AspNetCore.Routing.Matching.DfaMatcherBuilder+DfaBuilderWorker.ProcessSegment` will create a `Microsoft.AspNetCore.Routing.Patterns.RoutePattern+RequiredValueAnySentinal` literal value segment instead of a parameter segment.
https://github.com/dotnet/aspnetcore/blob/3f1acb59718cadf111a0a796681e3d3509bb3381/src/Http/Routing/src/Matching/DfaMatcherBuilder.cs#L300-L307
### Expected Behavior
When using a `RoutePattern` with `RoutePattern.RequiredValueAny`, the DFA builder for incoming request URL matching should interpret it as a parameterized segment instead of a literal segment, subject to whatever constraints were included in the segment in the call to `RoutePatternFactory.Parse`.
### Steps To Reproduce
1. Create a new ASP.NET Core MVC project using `dotnet new webapp`
1. Modify `Program.cs` to add a route pattern prefix group with a required value:
```csharp
var prefix = Microsoft.AspNetCore.Routing.Patterns.RoutePatternFactory.Parse(
pattern: "{prefix}",
defaults: default,
parameterPolicies: default,
requiredValues: new RouteValueDictionary
{
["prefix"] = Microsoft.AspNetCore.Routing.Patterns.RoutePattern.RequiredValueAny
});
app.MapGroup(prefix).MapRazorPages();
```
1. Run application and attempt to navigate to `/foo/privacy`, result in 404 instead of `GET /Privacy` Razor page with `HttpContext.GetRouteValue("prefix") == "foo"`
Note: Since the `RequiredValueAny` is converted to a string literal, and has no `object.ToString` override, the route uses the type name, so navigating to `/Microsoft.AspNetCore.Routing.Patterns.RoutePattern+RequiredValueAnySentinal/privacy` will result in executing the `GET /Privacy` Razor page with a 200 result, but no other route will work.
### Exceptions (if any)
_No response_
### .NET Version
8.0.100
### Anything else?
There's a specific reason why `RoutePattern.RequiredValueAny` is being added to the `requiredValues` parameter for `RoutePatternFactory.Parse` instead of just using a plain template string without it: to declaratively set the URL generation process to use the ambient route value even when changing a "built-in" route value like `area`/`page` for Razor Pages or `area`/`controller`/`action` for MVC.
See for more information; in summary, the "special" route values (`area`/`page`/`controller`/`action`) are mapped into the `requiredValues` parameter of the `RoutePatternFactory.Parse` that is used to generate the route pattern by the endpoint builder factories for Razor Pages and MVC, and the `DfaMatcherBuilder` seems to assume that the route values will passed will always be constant literals as a result.
There is a workaround of generating two `RoutePattern` instances for `MapGroup`: one for URL generation (with the `requiredValues`) and one for incoming request routing (without the `requiredValues`), but it is not at all intuitive to specify endpoint groups twice for this purpose.
Contributor guide
Assessment
This issue has not been assessed yet.