-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Blazor base path case sensitivity can be configured #64581
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces configurable case sensitivity for Blazor base path matching to address issue #6818. It allows applications hosted at paths like /app/ to work correctly when accessed via different casing (e.g., /APP/), which is particularly important on case-insensitive file systems like Windows.
Key changes:
- Introduces
NavigationManagerOptionsclass with aPathBaseComparisonproperty (defaults toStringComparison.Ordinalfor backward compatibility) - Updates all
NavigationManagerimplementations (WebAssembly, Server, Endpoints) to use the configured comparison mode - Adds infrastructure to apply options across all Blazor hosting models
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Components/Components/src/NavigationManagerOptions.cs | New options class for configuring NavigationManager behavior with PathBaseComparison property |
| src/Components/Components/src/NavigationManager.cs | Core implementation: adds PathBaseComparison property and updates all URI comparison operations to use configurable comparison |
| src/Components/Components/src/PublicAPI.Unshipped.txt | Documents new public API surface for NavigationManagerOptions and PathBaseComparison setter |
| src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationManager.cs | Adds ApplyOptions method to configure PathBaseComparison for WebAssembly scenarios |
| src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs | Registers NavigationManagerOptions in DI for WebAssembly |
| src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHost.cs | Applies configured options to WebAssemblyNavigationManager singleton during host construction |
| src/Components/Server/src/Circuits/RemoteNavigationManager.cs | Updates constructor to accept IOptions and apply PathBaseComparison |
| src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs | Registers NavigationManagerOptions in DI for Server-Side Blazor |
| src/Components/Endpoints/src/DependencyInjection/HttpNavigationManager.cs | Updates constructor to accept IOptions and apply PathBaseComparison |
| src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs | Registers NavigationManagerOptions in DI for Razor Components endpoints |
| src/Components/Components/test/NavigationManagerTest.cs | Adds unit test verifying case-insensitive path matching behavior |
| src/Components/test/E2ETest/Tests/HostedInAlternativeBasePathTest.cs | Converts to Theory test with /app/ and /APP/ paths to verify E2E functionality |
| src/Components/WebAssembly/testassets/HostedInAspNet.Server/Startup.cs | Configures PathBaseComparison to OrdinalIgnoreCase when UseAlternativeBasePath is enabled |
| internal sealed class HttpNavigationManager : NavigationManager, IHostEnvironmentNavigationManager | ||
| { | ||
| public HttpNavigationManager(IOptions<NavigationManagerOptions> options) | ||
| { |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing null argument validation. The options parameter should be validated for null before dereferencing options.Value. Add ArgumentNullException.ThrowIfNull(options); at the start of the constructor to prevent potential NullReferenceException.
| { | |
| { | |
| ArgumentNullException.ThrowIfNull(options); |
| public RemoteNavigationManager(ILogger<RemoteNavigationManager> logger) | ||
| /// <param name="options">The configured <see cref="NavigationManagerOptions"/>.</param> | ||
| public RemoteNavigationManager(ILogger<RemoteNavigationManager> logger, IOptions<NavigationManagerOptions> options) | ||
| { |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing null argument validation. The options parameter should be validated for null before dereferencing options.Value. Add ArgumentNullException.ThrowIfNull(options); at the start of the constructor to prevent potential NullReferenceException.
| { | |
| { | |
| ArgumentNullException.ThrowIfNull(options); |
| } | ||
|
|
||
| public void ApplyOptions(NavigationManagerOptions options) | ||
| { |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing null argument validation. The options parameter should be validated for null before dereferencing options.PathBaseComparison. Add ArgumentNullException.ThrowIfNull(options); at the start of the method to prevent potential NullReferenceException.
| { | |
| { | |
| ArgumentNullException.ThrowIfNull(options); |
| var navigationManagerOptions = services.GetService<IOptions<NavigationManagerOptions>>(); | ||
| if (navigationManagerOptions is not null) | ||
| { | ||
| WebAssemblyNavigationManager.Instance.ApplyOptions(navigationManagerOptions.Value); | ||
| } |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential race condition: Options are applied to the WebAssemblyNavigationManager.Instance singleton after it's been registered in DI (line 322 of WebAssemblyHostBuilder.cs). If any service resolved during scope creation (line 313) accesses the NavigationManager, it will use the default StringComparison.Ordinal instead of the configured value. Consider applying options immediately after registering the NavigationManager in InitializeDefaultServices(), or document that NavigationManager should not be accessed during service construction.
| /// </summary> | ||
| protected internal StringComparison PathBaseComparison | ||
| { |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PathBaseComparison property lacks a getter, making it write-only. This prevents users from inspecting the current configuration and makes testing/debugging more difficult. Consider adding get => to make this property read-write, which would align with typical options patterns and allow verification of the configured value.
| /// </summary> | |
| protected internal StringComparison PathBaseComparison | |
| { | |
| /// </summary> | |
| /// <summary> | |
| /// Gets or sets the string comparison used for base URI matching. | |
| /// </summary> | |
| protected internal StringComparison PathBaseComparison | |
| { | |
| get => _pathBaseComparison; |
| /// <summary> | ||
| /// Sets the string comparison used for base URI matching. | ||
| /// </summary> | ||
| protected internal StringComparison PathBaseComparison | ||
| { | ||
| set => _pathBaseComparison = value; | ||
| } |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
XML documentation is missing for the PathBaseComparison property setter. Since this is part of the public API surface (as evidenced by PublicAPI.Unshipped.txt), it should include XML doc comments explaining when and how this property is used. Consider adding documentation similar to: /// <summary>Sets the string comparison used for base URI matching. This is typically configured via <see cref="NavigationManagerOptions.PathBaseComparison"/>.</summary>
| [Fact] | ||
| public void ToBaseRelativePath_HonorsConfiguredPathBaseComparison() | ||
| { | ||
| var navigationManager = new TestNavigationManager("https://example.com/dashboard/", "https://example.com/dashboard/"); | ||
|
|
||
| var ex = Assert.Throws<ArgumentException>(() => navigationManager.ToBaseRelativePath("https://example.com/DaShBoArD")); | ||
| Assert.Equal("The URI 'https://example.com/DaShBoArD' is not contained by the base URI 'https://example.com/dashboard/'.", ex.Message); | ||
|
|
||
| navigationManager.SetPathBaseComparison(StringComparison.OrdinalIgnoreCase); | ||
|
|
||
| var result = navigationManager.ToBaseRelativePath("https://example.com/DaShBoArD"); | ||
|
|
||
| Assert.Equal(string.Empty, result); | ||
| } |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test coverage is incomplete for the case-insensitive path matching feature. The test only covers the basic scenario where the path matches exactly. Consider adding test cases for:
- The trailing slash special case with case-insensitive matching (e.g., base URI "/App/" matching "/app")
- URIs with query strings and fragments using case-insensitive matching (e.g., "/App?query=1" with base URI "/app/")
- The internal
ReadOnlySpan<char>overload ofToBaseRelativePathwith case-insensitive matching - The
Validatemethod behavior with case-insensitive comparison during initialization
| using System; | ||
| using Microsoft.AspNetCore.Components; |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using directives are placed inside the namespace declaration. According to the repository's coding guidelines (from .editorconfig), prefer placing using directives outside namespace declarations for consistency. Move lines 6-7 above line 4.
|
Closing in favor of a new approach: |
Proposal for fixing #6818.
The description with justification will be provided soon.