Skip to content

Commit a8f3d68

Browse files
committed
[Blazor] Enable regex constraint in Blazor routing
This was discovered when working on the issue for #53138. The regex constraint is not enabled by default on the blazor router. As a result if a route uses a regex constraint, it will work on SSR but will fail the moment the Blazor router tries to construct the route. The fix enables the regex constraint on the blazor router, unconditionally for server, and behind a feature flag for webassembly. This is because enabling the regex constraint adds +80kb to the payload due to the inclusion of the System.Text.RegularExpressions assembly.
1 parent c99b166 commit a8f3d68

File tree

10 files changed

+99
-10
lines changed

10 files changed

+99
-10
lines changed

src/Components/Components/src/Microsoft.AspNetCore.Components.Routing.targets

-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
<Compile Include="$(RoutingSourceRoot)Constraints\**\*.cs" LinkBase="Routing\Constraints" />
3434

3535
<Compile Remove="$(RoutingSourceRoot)Constraints\HttpMethodRouteConstraint.cs" />
36-
<Compile Remove="$(RoutingSourceRoot)Constraints\RegexErrorStubRouteConstraint.cs" />
3736
<Compile Remove="$(RoutingSourceRoot)Constraints\RequiredRouteConstraint.cs" />
3837
<Compile Remove="$(RoutingSourceRoot)Constraints\StringRouteConstraint.cs" />
3938
</ItemGroup>

src/Components/Components/src/Properties/ILLink.Substitutions.xml

+3
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@
33
<type fullname="Microsoft.AspNetCore.Components.HotReload.HotReloadManager" feature="System.Reflection.Metadata.MetadataUpdater.IsSupported" featurevalue="false">
44
<method signature="System.Boolean get_MetadataUpdateSupported()" body="stub" value="false" />
55
</type>
6+
<type fullname="Microsoft.AspNetCore.Components.Routing.RegexConstraintSupport" feature="Microsoft.AspNetCore.Components.Routing.RegexConstraintSupport" featurevalue="false">
7+
<method signature="System.Boolean get_IsEnabled()" body="stub" value="false" />
8+
</type>
69
</assembly>
710
</linker>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Components.Routing;
5+
6+
internal static class RegexConstraintSupport
7+
{
8+
public static bool IsEnabled =>
9+
AppContext.TryGetSwitch("Microsoft.AspNetCore.Components.Routing.RegexConstraintSupport", out var enabled) && enabled;
10+
}

src/Components/Components/src/Routing/Resources.resx

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@
154154
<value>Invalid constraint type '{0}' registered as '{1}'. A constraint type must either implement '{2}', or inherit from '{3}'.</value>
155155
</data>
156156
<data name="RegexRouteContraint_NotConfigured" xml:space="preserve">
157-
<value>A route parameter uses the regex constraint, which isn't registered. If this application was configured using CreateSlimBuilder(...) or AddRoutingCore(...) then this constraint is not registered by default. To use the regex constraint, configure route options at app startup: services.Configure&lt;RouteOptions&gt;(options =&gt; options.SetParameterPolicy&lt;RegexInlineRouteConstraint&gt;("regex"));</value>
157+
<value>A route parameter uses the regex constraint, which isn't registered. To enable it add the property 'BlazorRoutingEnableRegexConstraint' to your project file inside a `PropertyGroup`.</value>
158158
</data>
159159
<data name="ArgumentMustBeGreaterThanOrEqualTo" xml:space="preserve">
160160
<value>Value must be greater than or equal to {0}.</value>

src/Components/Components/src/Routing/RouteTableFactory.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Microsoft.Extensions.Logging;
1414
using Microsoft.Extensions.Options;
1515
using static Microsoft.AspNetCore.Internal.LinkerFlags;
16+
using Microsoft.AspNetCore.Routing.Constraints;
1617

1718
namespace Microsoft.AspNetCore.Components;
1819

@@ -112,9 +113,14 @@ private static string[] GetTemplates(Type componentType)
112113
[UnconditionalSuppressMessage("Trimming", "IL2067", Justification = "Application code does not get trimmed, and the framework does not define routable components.")]
113114
internal static RouteTable Create(Dictionary<Type, string[]> templatesByHandler, IServiceProvider serviceProvider)
114115
{
116+
var routeOptions = Options.Create(new RouteOptions());
117+
if (!OperatingSystem.IsBrowser() || RegexConstraintSupport.IsEnabled)
118+
{
119+
routeOptions.Value.SetParameterPolicy("regex", typeof(RegexInlineRouteConstraint));
120+
}
115121
var builder = new TreeRouteBuilder(
116122
serviceProvider.GetRequiredService<ILoggerFactory>(),
117-
new DefaultInlineConstraintResolver(Options.Create(new RouteOptions()), serviceProvider));
123+
new DefaultInlineConstraintResolver(routeOptions, serviceProvider));
118124

119125
foreach (var (type, templates) in templatesByHandler)
120126
{

src/Components/test/E2ETest/ServerRenderingTests/UnifiedRoutingTests.cs

+10-6
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,12 @@ public void Routing_CanRenderPagesWithParameters_And_TransitionToInteractive(str
3939
ExecuteRoutingTestCore(url, expectedValue);
4040
}
4141

42-
[Fact]
43-
public void Routing_CanRenderPagesWithConstrainedParameters_And_TransitionToInteractive()
42+
[Theory]
43+
[InlineData("routing/constraints/5", "5")]
44+
[InlineData("%F0%9F%99%82/routing/constraints/http%3A%2F%2Fwww.example.com%2Flogin%2Fcallback", "http://www.example.com/login/callback")]
45+
public void Routing_CanRenderPagesWithConstrainedParameters_And_TransitionToInteractive(string url, string expectedValue)
4446
{
45-
ExecuteRoutingTestCore("routing/constraints/5", "5");
47+
ExecuteRoutingTestCore(url, expectedValue);
4648
}
4749

4850
[Theory]
@@ -73,10 +75,12 @@ public void Routing_CanRenderPagesWithCatchAllParameters_And_TransitionToInterac
7375
ExecuteRoutingTestCore(url, expectedValue);
7476
}
7577

76-
[Fact]
77-
public void Routing_CanRenderPagesWithConstrainedCatchAllParameters_And_TransitionToInteractive()
78+
[Theory]
79+
[InlineData("routing/constrained-catch-all/a/b", "a/b")]
80+
[InlineData("%F0%9F%99%82/routing/constrained-catch-all/http%3A%2F%2Fwww.example.com%2Flogin%2Fcallback/another", "http://www.example.com/login/callback/another")]
81+
public void Routing_CanRenderPagesWithConstrainedCatchAllParameters_And_TransitionToInteractive(string url, string expectedValue)
7882
{
79-
ExecuteRoutingTestCore("routing/constrained-catch-all/a/b", "a/b");
83+
ExecuteRoutingTestCore(url, expectedValue);
8084
}
8185

8286
private void ExecuteRoutingTestCore(string url, string expectedValue)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@page "/🙂/routing/constrained-catch-all/{*parameter:regex(http:%2F%2Fwww\\.example\\.com%2Flogin%2Fcallback\\/another)}"
2+
<h3>Parameters</h3>
3+
4+
<p id="parameter-value">@Parameter</p>
5+
6+
@if (_interactive)
7+
{
8+
<p id="interactive">Rendered interactive.</p>
9+
}
10+
11+
@code {
12+
private bool _interactive;
13+
14+
[Parameter] public string Parameter { get; set; }
15+
16+
protected override void OnAfterRender(bool firstRender)
17+
{
18+
if (firstRender)
19+
{
20+
_interactive = true;
21+
StateHasChanged();
22+
}
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@page "/🙂/routing/constraints/{parameter:regex(http:%2F%2Fwww\\.example\\.com%2Flogin%2Fcallback)}"
2+
<h3>Constrained Parameters</h3>
3+
4+
<p id="parameter-value">@Parameter</p>
5+
6+
@if (_interactive)
7+
{
8+
<p id="interactive">Rendered interactive.</p>
9+
}
10+
11+
@code {
12+
private bool _interactive;
13+
14+
[Parameter] public string Parameter { get; set; }
15+
16+
protected override void OnAfterRender(bool firstRender)
17+
{
18+
if (firstRender)
19+
{
20+
_interactive = true;
21+
StateHasChanged();
22+
}
23+
}
24+
}

src/Http/Routing/src/Constraints/RegexErrorStubRouteConstraint.cs

+12
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,30 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
#if !COMPONENTS
45
using Microsoft.AspNetCore.Http;
6+
#else
7+
using Microsoft.AspNetCore.Components.Routing;
8+
#endif
59

610
namespace Microsoft.AspNetCore.Routing.Constraints;
711

12+
#if !COMPONENTS
813
internal sealed class RegexErrorStubRouteConstraint : IRouteConstraint
14+
#else
15+
internal sealed class RegexErrorStubRouteConstraint : IRouteConstraint, IParameterPolicy
16+
#endif
917
{
1018
public RegexErrorStubRouteConstraint(string _)
1119
{
1220
throw new InvalidOperationException(Resources.RegexRouteContraint_NotConfigured);
1321
}
1422

23+
#if !COMPONENTS
1524
bool IRouteConstraint.Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
25+
#else
26+
bool IRouteConstraint.Match(string routeKey, RouteValueDictionary values)
27+
#endif
1628
{
1729
// Should never get called, but is same as throw in constructor in case constructor is changed.
1830
throw new InvalidOperationException(Resources.RegexRouteContraint_NotConfigured);

src/Http/Routing/src/RouteOptions.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
#if !COMPONENTS
55
using System.Diagnostics;
6+
#else
7+
using Microsoft.AspNetCore.Components.Routing;
68
#endif
79
using System.Diagnostics.CodeAnalysis;
810
using Microsoft.AspNetCore.Routing.Constraints;
@@ -126,8 +128,13 @@ private static IDictionary<string, Type> GetDefaultConstraintMap()
126128

127129
#if !COMPONENTS
128130
AddConstraint<RegexErrorStubRouteConstraint>(defaults, "regex"); // Used to generate error message at runtime with helpful message.
129-
130131
AddConstraint<RequiredRouteConstraint>(defaults, "required");
132+
#else
133+
// Check if the feature is not enabled in the browser context
134+
if (OperatingSystem.IsBrowser() && !RegexConstraintSupport.IsEnabled)
135+
{
136+
AddConstraint<RegexErrorStubRouteConstraint>(defaults, "regex"); // Used to generate error message at runtime with helpful message.
137+
}
131138
#endif
132139

133140
// Files

0 commit comments

Comments
 (0)