-
Notifications
You must be signed in to change notification settings - Fork 10.5k
API proposal for NavigateTo and NavLink with relative path
#64670
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
base: main
Are you sure you want to change the base?
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 |
|---|---|---|
| @@ -1 +1,3 @@ | ||
| #nullable enable | ||
| Microsoft.AspNetCore.Components.NavigationOptions.PathRelative.get -> bool | ||
| Microsoft.AspNetCore.Components.NavigationOptions.PathRelative.init -> void |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -886,7 +886,107 @@ public void OnNotFoundSubscriptionIsTriggeredWhenNotFoundCalled() | |||||||||||||||||||||||||||||||||||||
| // Assert | ||||||||||||||||||||||||||||||||||||||
| Assert.True(notFoundTriggered, "The OnNotFound event was not triggered as expected."); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| [Fact] | ||||||||||||||||||||||||||||||||||||||
| public void NavigateTo_WithPathRelative_ResolvesRelativeToCurrentPath() | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| var baseUri = "scheme://host/"; | ||||||||||||||||||||||||||||||||||||||
| var currentUri = "scheme://host/folder1/folder2/page.html"; | ||||||||||||||||||||||||||||||||||||||
| var testNavManager = new TestNavigationManagerWithNavigationTracking(baseUri, currentUri); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| testNavManager.NavigateTo("sibling.html", new NavigationOptions { PathRelative = true }); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Assert.Single(testNavManager.Navigations); | ||||||||||||||||||||||||||||||||||||||
| Assert.Equal("scheme://host/folder1/folder2/sibling.html", testNavManager.Navigations[0].uri); | ||||||||||||||||||||||||||||||||||||||
| Assert.True(testNavManager.Navigations[0].options.PathRelative); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| [Fact] | ||||||||||||||||||||||||||||||||||||||
| public void NavigateTo_WithPathRelative_HandlesQueryAndFragmentInCurrentUri() | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| var baseUri = "scheme://host/"; | ||||||||||||||||||||||||||||||||||||||
| var currentUri = "scheme://host/folder1/page.html?query=value#hash"; | ||||||||||||||||||||||||||||||||||||||
| var testNavManager = new TestNavigationManagerWithNavigationTracking(baseUri, currentUri); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| testNavManager.NavigateTo("other.html", new NavigationOptions { PathRelative = true }); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Assert.Single(testNavManager.Navigations); | ||||||||||||||||||||||||||||||||||||||
| Assert.Equal("scheme://host/folder1/other.html", testNavManager.Navigations[0].uri); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| [Fact] | ||||||||||||||||||||||||||||||||||||||
| public void NavigateTo_WithPathRelativeFalse_DoesNotResolve() | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| var baseUri = "scheme://host/base/"; | ||||||||||||||||||||||||||||||||||||||
| var currentUri = "scheme://host/base/folder1/page.html"; | ||||||||||||||||||||||||||||||||||||||
| var testNavManager = new TestNavigationManagerWithNavigationTracking(baseUri, currentUri); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| testNavManager.NavigateTo("relative.html", new NavigationOptions { PathRelative = false }); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Assert.Single(testNavManager.Navigations); | ||||||||||||||||||||||||||||||||||||||
| // When PathRelative is false, the URI is passed directly to NavigateToCore without resolution | ||||||||||||||||||||||||||||||||||||||
| Assert.Equal("relative.html", testNavManager.Navigations[0].uri); | ||||||||||||||||||||||||||||||||||||||
| Assert.False(testNavManager.Navigations[0].options.PathRelative); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| [Fact] | ||||||||||||||||||||||||||||||||||||||
| public void NavigateTo_WithPathRelative_AtRootLevel() | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| var baseUri = "scheme://host/"; | ||||||||||||||||||||||||||||||||||||||
| var currentUri = "scheme://host/page.html"; | ||||||||||||||||||||||||||||||||||||||
| var testNavManager = new TestNavigationManagerWithNavigationTracking(baseUri, currentUri); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| testNavManager.NavigateTo("other.html", new NavigationOptions { PathRelative = true }); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Assert.Single(testNavManager.Navigations); | ||||||||||||||||||||||||||||||||||||||
| Assert.Equal("scheme://host/other.html", testNavManager.Navigations[0].uri); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| [Fact] | ||||||||||||||||||||||||||||||||||||||
| public void NavigateTo_WithPathRelative_NestedPaths() | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| var baseUri = "scheme://host/"; | ||||||||||||||||||||||||||||||||||||||
| var currentUri = "scheme://host/a/b/c/d/page.html"; | ||||||||||||||||||||||||||||||||||||||
| var testNavManager = new TestNavigationManagerWithNavigationTracking(baseUri, currentUri); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| testNavManager.NavigateTo("sibling.html", new NavigationOptions { PathRelative = true }); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Assert.Single(testNavManager.Navigations); | ||||||||||||||||||||||||||||||||||||||
| Assert.Equal("scheme://host/a/b/c/d/sibling.html", testNavManager.Navigations[0].uri); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| [Fact] | ||||||||||||||||||||||||||||||||||||||
| public void NavigateTo_WithPathRelative_WithQueryStringPreservesPath() | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| var baseUri = "scheme://host/"; | ||||||||||||||||||||||||||||||||||||||
| var currentUri = "scheme://host/folder/page.html?param=value"; | ||||||||||||||||||||||||||||||||||||||
| var testNavManager = new TestNavigationManagerWithNavigationTracking(baseUri, currentUri); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| testNavManager.NavigateTo("other.html?new=param", new NavigationOptions { PathRelative = true }); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Assert.Single(testNavManager.Navigations); | ||||||||||||||||||||||||||||||||||||||
| Assert.Equal("scheme://host/folder/other.html?new=param", testNavManager.Navigations[0].uri); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| [Fact] | ||||||||||||||||||||||||||||||||||||||
| public void ResolveRelativeToCurrentPath_NoSlashFound_EdgeCase() | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| // This tests the defensive edge case where no slash is found in the URI | ||||||||||||||||||||||||||||||||||||||
| // We use reflection to set _uri to an invalid value (bypassing validation) | ||||||||||||||||||||||||||||||||||||||
| var baseUri = "scheme://host/"; | ||||||||||||||||||||||||||||||||||||||
| var testNavManager = new TestNavigationManager(baseUri, "scheme://host/page.html"); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Use reflection to set _uri to an invalid value that has no slash | ||||||||||||||||||||||||||||||||||||||
| var uriField = typeof(NavigationManager).GetField("_uri", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); | ||||||||||||||||||||||||||||||||||||||
| uriField.SetValue(testNavManager, "invaliduri"); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Call the internal method directly (accessible because of InternalsVisibleTo) | ||||||||||||||||||||||||||||||||||||||
| var result = testNavManager.ResolveRelativeToCurrentPath("page.html"); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // When no slash is found, it concatenates to the current URI | ||||||||||||||||||||||||||||||||||||||
| Assert.Equal("invaliduripage.html", result); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+890
to
+989
|
||||||||||||||||||||||||||||||||||||||
| [Fact] | |
| public void ResolveRelativeToCurrentPath_NoSlashFound_EdgeCase() | |
| { | |
| // This tests the defensive edge case where no slash is found in the URI | |
| // We use reflection to set _uri to an invalid value (bypassing validation) | |
| var baseUri = "scheme://host/"; | |
| var testNavManager = new TestNavigationManager(baseUri, "scheme://host/page.html"); | |
| // Use reflection to set _uri to an invalid value that has no slash | |
| var uriField = typeof(NavigationManager).GetField("_uri", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); | |
| uriField.SetValue(testNavManager, "invaliduri"); | |
| // Call the internal method directly (accessible because of InternalsVisibleTo) | |
| var result = testNavManager.ResolveRelativeToCurrentPath("page.html"); | |
| // When no slash is found, it concatenates to the current URI | |
| Assert.Equal("invaliduripage.html", result); | |
| } |
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 implementation of
PathRelativenavigation doesn't handle several important relative path patterns that developers might reasonably expect to work:"/about.html") - These start with/and should navigate to the root, but currently would be concatenated to the current directory path"../sibling.html") - The../pattern to navigate up directories is not handled"./page.html") - The./prefix is not explicitly handledConsider either:
PathRelativeproperty../,/, and./correctly)Standard URI path resolution is typically done using
new Uri(baseUri, relativeUri)which automatically handles these cases. If you want to avoid allocatingUriobjects for performance, you could implement the standard path resolution algorithm manually using spans.