-
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -256,15 +256,26 @@ public Uri ToAbsoluteUri(string? relativeUri) | |
| return new Uri(_baseUri!, relativeUri); | ||
| } | ||
|
|
||
| /// <summary>Holds the active <see cref="StringComparison"/> used for base URI matching.</summary> | ||
| private StringComparison _pathBaseComparison = StringComparison.Ordinal; | ||
|
|
||
| /// <summary> | ||
| /// Sets the string comparison used for base URI matching. | ||
| /// </summary> | ||
| protected internal StringComparison PathBaseComparison | ||
| { | ||
| set => _pathBaseComparison = value; | ||
| } | ||
|
Comment on lines
+262
to
+268
|
||
|
|
||
| /// <summary> | ||
| /// Given a base URI (e.g., one previously returned by <see cref="BaseUri"/>), | ||
| /// converts an absolute URI into one relative to the base URI prefix. | ||
| /// </summary> | ||
| /// <param name="uri">An absolute URI that is within the space of the base URI.</param> | ||
| /// <returns>A relative URI path.</returns> | ||
| /// <returns>The portion of <paramref name="uri"/> that follows the <see cref="BaseUri"/> prefix.</returns> | ||
| public string ToBaseRelativePath(string uri) | ||
| { | ||
| if (uri.StartsWith(_baseUri!.OriginalString, StringComparison.Ordinal)) | ||
| if (uri.StartsWith(_baseUri!.OriginalString, _pathBaseComparison)) | ||
| { | ||
| // The absolute URI must be of the form "{baseUri}something" (where | ||
| // baseUri ends with a slash), and from that we return "something" | ||
|
|
@@ -273,7 +284,7 @@ public string ToBaseRelativePath(string uri) | |
|
|
||
| var pathEndIndex = uri.AsSpan().IndexOfAny('#', '?'); | ||
| var uriPathOnly = pathEndIndex < 0 ? uri : uri.AsSpan(0, pathEndIndex); | ||
| if (_baseUri.OriginalString.EndsWith('/') && uriPathOnly.Equals(_baseUri.OriginalString.AsSpan(0, _baseUri.OriginalString.Length - 1), StringComparison.Ordinal)) | ||
| if (_baseUri.OriginalString.EndsWith('/') && uriPathOnly.Equals(_baseUri.OriginalString.AsSpan(0, _baseUri.OriginalString.Length - 1), _pathBaseComparison)) | ||
| { | ||
| // Special case: for the base URI "/something/", if you're at | ||
| // "/something" then treat it as if you were at "/something/" (i.e., | ||
|
|
@@ -290,7 +301,7 @@ public string ToBaseRelativePath(string uri) | |
|
|
||
| internal ReadOnlySpan<char> ToBaseRelativePath(ReadOnlySpan<char> uri) | ||
| { | ||
| if (MemoryExtensions.StartsWith(uri, _baseUri!.OriginalString.AsSpan(), StringComparison.Ordinal)) | ||
| if (MemoryExtensions.StartsWith(uri, _baseUri!.OriginalString.AsSpan(), _pathBaseComparison)) | ||
| { | ||
| // The absolute URI must be of the form "{baseUri}something" (where | ||
| // baseUri ends with a slash), and from that we return "something" | ||
|
|
@@ -299,7 +310,7 @@ internal ReadOnlySpan<char> ToBaseRelativePath(ReadOnlySpan<char> uri) | |
|
|
||
| var pathEndIndex = uri.IndexOfAny('#', '?'); | ||
| var uriPathOnly = pathEndIndex < 0 ? uri : uri[..pathEndIndex]; | ||
| if (_baseUri.OriginalString.EndsWith('/') && MemoryExtensions.Equals(uriPathOnly, _baseUri.OriginalString.AsSpan(0, _baseUri.OriginalString.Length - 1), StringComparison.Ordinal)) | ||
| if (_baseUri.OriginalString.EndsWith('/') && MemoryExtensions.Equals(uriPathOnly, _baseUri.OriginalString.AsSpan(0, _baseUri.OriginalString.Length - 1), _pathBaseComparison)) | ||
| { | ||
| // Special case: for the base URI "/something/", if you're at | ||
| // "/something" then treat it as if you were at "/something/" (i.e., | ||
|
|
@@ -553,9 +564,9 @@ private void AssertInitialized() | |
| } | ||
| } | ||
|
|
||
| private static bool TryGetLengthOfBaseUriPrefix(Uri baseUri, string uri, out int length) | ||
| private bool TryGetLengthOfBaseUriPrefix(Uri baseUri, string uri, out int length) | ||
| { | ||
| if (uri.StartsWith(baseUri.OriginalString, StringComparison.Ordinal)) | ||
| if (uri.StartsWith(baseUri.OriginalString, _pathBaseComparison)) | ||
| { | ||
| // The absolute URI must be of the form "{baseUri}something" (where | ||
| // baseUri ends with a slash), and from that we return "something" | ||
|
|
@@ -565,7 +576,7 @@ private static bool TryGetLengthOfBaseUriPrefix(Uri baseUri, string uri, out int | |
|
|
||
| var pathEndIndex = uri.AsSpan().IndexOfAny('#', '?'); | ||
| var uriPathOnly = pathEndIndex < 0 ? uri : uri.AsSpan(0, pathEndIndex); | ||
| if (baseUri.OriginalString.EndsWith('/') && uriPathOnly.Equals(baseUri.OriginalString.AsSpan(0, baseUri.OriginalString.Length - 1), StringComparison.Ordinal)) | ||
| if (baseUri.OriginalString.EndsWith('/') && uriPathOnly.Equals(baseUri.OriginalString.AsSpan(0, baseUri.OriginalString.Length - 1), _pathBaseComparison)) | ||
| { | ||
| // Special case: for the base URI "/something/", if you're at | ||
| // "/something" then treat it as if you were at "/something/" (i.e., | ||
|
|
@@ -581,7 +592,7 @@ private static bool TryGetLengthOfBaseUriPrefix(Uri baseUri, string uri, out int | |
| return false; | ||
| } | ||
|
|
||
| private static void Validate(Uri? baseUri, string uri) | ||
| private void Validate(Uri? baseUri, string uri) | ||
| { | ||
| if (baseUri == null || uri == null) | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| namespace Microsoft.AspNetCore.Components; | ||
|
|
||
| /// <summary> | ||
| /// Provides configuration for <see cref="NavigationManager"/> behavior. | ||
| /// </summary> | ||
| public class NavigationManagerOptions | ||
| { | ||
| /// <summary> | ||
| /// Gets or sets the string comparison used when comparing URIs against the base URI. | ||
| /// The default is <see cref="System.StringComparison.Ordinal"/> for backward compatibility. | ||
| /// Set to <see cref="System.StringComparison.OrdinalIgnoreCase"/> to enable case-insensitive matching. | ||
| /// </summary> | ||
| public StringComparison PathBaseComparison { get; set; } = StringComparison.Ordinal; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,6 @@ | ||
| #nullable enable | ||
| Microsoft.AspNetCore.Components.NavigationManagerOptions | ||
| Microsoft.AspNetCore.Components.NavigationManagerOptions.NavigationManagerOptions() -> void | ||
| Microsoft.AspNetCore.Components.NavigationManagerOptions.PathBaseComparison.get -> System.StringComparison | ||
| Microsoft.AspNetCore.Components.NavigationManagerOptions.PathBaseComparison.set -> void | ||
| Microsoft.AspNetCore.Components.NavigationManager.PathBaseComparison.set -> void |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System; | ||
| using System.Buffers; | ||
| using System.Diagnostics; | ||
| using System.Net.Http; | ||
|
|
@@ -84,6 +85,21 @@ public void ToBaseRelativePath_ThrowsForInvalidBaseRelativePaths(string baseUri, | |
| ex.Message); | ||
| } | ||
|
|
||
| [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); | ||
| } | ||
|
Comment on lines
+88
to
+101
|
||
|
|
||
| [Theory] | ||
| [InlineData("scheme://host/?full%20name=Bob%20Joe&age=42", "scheme://host/?full%20name=John%20Doe&age=42")] | ||
| [InlineData("scheme://host/?fUlL%20nAmE=Bob%20Joe&AgE=42", "scheme://host/?full%20name=John%20Doe&AgE=42")] | ||
|
|
@@ -906,6 +922,11 @@ public TestNavigationManager(string baseUri = null, string uri = null) | |
| public async Task<bool> RunNotifyLocationChangingAsync(string uri, string state, bool isNavigationIntercepted) | ||
| => await NotifyLocationChangingAsync(uri, state, isNavigationIntercepted); | ||
|
|
||
| public void SetPathBaseComparison(StringComparison comparison) | ||
| { | ||
| PathBaseComparison = comparison; | ||
| } | ||
|
|
||
| protected override void NavigateToCore(string uri, bool forceLoad) | ||
| { | ||
| throw new System.NotImplementedException(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -1,13 +1,19 @@ | ||||||||
| // Licensed to the .NET Foundation under one or more agreements. | ||||||||
| // The .NET Foundation licenses this file to you under the MIT license. | ||||||||
|
|
||||||||
| using Microsoft.AspNetCore.Components.Routing; | ||||||||
| using System.Diagnostics.CodeAnalysis; | ||||||||
| using Microsoft.AspNetCore.Components.Routing; | ||||||||
| using Microsoft.Extensions.Options; | ||||||||
|
|
||||||||
| namespace Microsoft.AspNetCore.Components.Endpoints; | ||||||||
|
|
||||||||
| internal sealed class HttpNavigationManager : NavigationManager, IHostEnvironmentNavigationManager | ||||||||
| { | ||||||||
| public HttpNavigationManager(IOptions<NavigationManagerOptions> options) | ||||||||
| { | ||||||||
|
||||||||
| { | |
| { | |
| ArgumentNullException.ThrowIfNull(options); |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |||||||
| using System.Diagnostics.CodeAnalysis; | ||||||||
| using Microsoft.AspNetCore.Components.Routing; | ||||||||
| using Microsoft.Extensions.Logging; | ||||||||
| using Microsoft.Extensions.Options; | ||||||||
| using Microsoft.JSInterop; | ||||||||
| using Interop = Microsoft.AspNetCore.Components.Web.BrowserNavigationManagerInterop; | ||||||||
|
|
||||||||
|
|
@@ -30,9 +31,11 @@ internal sealed partial class RemoteNavigationManager : NavigationManager, IHost | |||||||
| /// Creates a new <see cref="RemoteNavigationManager"/> instance. | ||||||||
| /// </summary> | ||||||||
| /// <param name="logger">The <see cref="ILogger{TCategoryName}"/>.</param> | ||||||||
| public RemoteNavigationManager(ILogger<RemoteNavigationManager> logger) | ||||||||
| /// <param name="options">The configured <see cref="NavigationManagerOptions"/>.</param> | ||||||||
| public RemoteNavigationManager(ILogger<RemoteNavigationManager> logger, IOptions<NavigationManagerOptions> options) | ||||||||
| { | ||||||||
|
||||||||
| { | |
| { | |
| ArgumentNullException.ThrowIfNull(options); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ | |
| using Microsoft.Extensions.Configuration; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.Logging; | ||
| using Microsoft.Extensions.Options; | ||
|
|
||
| namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting; | ||
|
|
||
|
|
@@ -53,6 +54,12 @@ internal WebAssemblyHost( | |
| _configuration = builder.Configuration; | ||
| _rootComponents = builder.RootComponents; | ||
| _persistedState = persistedState; | ||
|
|
||
| var navigationManagerOptions = services.GetService<IOptions<NavigationManagerOptions>>(); | ||
| if (navigationManagerOptions is not null) | ||
| { | ||
| WebAssemblyNavigationManager.Instance.ApplyOptions(navigationManagerOptions.Value); | ||
| } | ||
|
Comment on lines
+58
to
+62
|
||
| } | ||
|
|
||
| /// <summary> | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -26,6 +26,11 @@ public WebAssemblyNavigationManager(string baseUri, string uri) | |||||||
| Initialize(baseUri, uri); | ||||||||
| } | ||||||||
|
|
||||||||
| public void ApplyOptions(NavigationManagerOptions options) | ||||||||
| { | ||||||||
|
||||||||
| { | |
| { | |
| ArgumentNullException.ThrowIfNull(options); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,9 @@ | |
|
|
||
| namespace HostedInAspNet.Server; | ||
|
|
||
| using System; | ||
| using Microsoft.AspNetCore.Components; | ||
|
Comment on lines
+6
to
+7
|
||
|
|
||
| public class Startup | ||
| { | ||
| public Startup(IConfiguration configuration) | ||
|
|
@@ -16,6 +19,14 @@ public Startup(IConfiguration configuration) | |
| // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 | ||
| public void ConfigureServices(IServiceCollection services) | ||
| { | ||
| var mapAlternativePathApp = Configuration.GetValue<bool>("UseAlternativeBasePath"); | ||
| if (mapAlternativePathApp) | ||
| { | ||
| services.Configure<NavigationManagerOptions>(options => | ||
| { | ||
| options.PathBaseComparison = StringComparison.OrdinalIgnoreCase; | ||
| }); | ||
| } | ||
| services.AddSingleton<BootResourceRequestLog>(); | ||
| } | ||
|
|
||
|
|
||
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
PathBaseComparisonproperty lacks a getter, making it write-only. This prevents users from inspecting the current configuration and makes testing/debugging more difficult. Consider addingget =>to make this property read-write, which would align with typical options patterns and allow verification of the configured value.