dotnet / dotnet/aspnetcore

MapGroup with RoutePattern with RequiredValues of RoutePattern.RequiredValueAny registers incorrect URL routing endpoints

Open
#53,104 0 comments 0 reactions 0 assignees View on GitHub
area-mvc
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.