Skip to content

Conversation

@ilonatommy
Copy link
Member

Proposal for fixing #6818.

The description with justification will be provided soon.

@ilonatommy ilonatommy added this to the .NET 11 Planning milestone Dec 1, 2025
@ilonatommy ilonatommy self-assigned this Dec 1, 2025
@ilonatommy ilonatommy requested a review from a team as a code owner December 1, 2025 13:46
Copilot AI review requested due to automatic review settings December 1, 2025 13:46
@ilonatommy ilonatommy added the area-blazor Includes: Blazor, Razor Components label Dec 1, 2025
Copilot finished reviewing on behalf of ilonatommy December 1, 2025 13:51
Copy link
Contributor

Copilot AI left a 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 NavigationManagerOptions class with a PathBaseComparison property (defaults to StringComparison.Ordinal for backward compatibility)
  • Updates all NavigationManager implementations (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)
{
Copy link

Copilot AI Dec 1, 2025

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.

Suggested change
{
{
ArgumentNullException.ThrowIfNull(options);

Copilot uses AI. Check for mistakes.
public RemoteNavigationManager(ILogger<RemoteNavigationManager> logger)
/// <param name="options">The configured <see cref="NavigationManagerOptions"/>.</param>
public RemoteNavigationManager(ILogger<RemoteNavigationManager> logger, IOptions<NavigationManagerOptions> options)
{
Copy link

Copilot AI Dec 1, 2025

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.

Suggested change
{
{
ArgumentNullException.ThrowIfNull(options);

Copilot uses AI. Check for mistakes.
}

public void ApplyOptions(NavigationManagerOptions options)
{
Copy link

Copilot AI Dec 1, 2025

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.

Suggested change
{
{
ArgumentNullException.ThrowIfNull(options);

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +62
var navigationManagerOptions = services.GetService<IOptions<NavigationManagerOptions>>();
if (navigationManagerOptions is not null)
{
WebAssemblyNavigationManager.Instance.ApplyOptions(navigationManagerOptions.Value);
}
Copy link

Copilot AI Dec 1, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines +264 to +266
/// </summary>
protected internal StringComparison PathBaseComparison
{
Copy link

Copilot AI Dec 1, 2025

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.

Suggested change
/// </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;

Copilot uses AI. Check for mistakes.
Comment on lines +262 to +268
/// <summary>
/// Sets the string comparison used for base URI matching.
/// </summary>
protected internal StringComparison PathBaseComparison
{
set => _pathBaseComparison = value;
}
Copy link

Copilot AI Dec 1, 2025

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>

Copilot uses AI. Check for mistakes.
Comment on lines +88 to +101
[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);
}
Copy link

Copilot AI Dec 1, 2025

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:

  1. The trailing slash special case with case-insensitive matching (e.g., base URI "/App/" matching "/app")
  2. URIs with query strings and fragments using case-insensitive matching (e.g., "/App?query=1" with base URI "/app/")
  3. The internal ReadOnlySpan<char> overload of ToBaseRelativePath with case-insensitive matching
  4. The Validate method behavior with case-insensitive comparison during initialization

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +7
using System;
using Microsoft.AspNetCore.Components;
Copy link

Copilot AI Dec 1, 2025

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.

Copilot generated this review using guidance from repository custom instructions.
@ilonatommy
Copy link
Member Author

Closing in favor of a new approach: BasePath component.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-blazor Includes: Blazor, Razor Components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant