diff --git a/src/Components/Components/src/NavigationManager.cs b/src/Components/Components/src/NavigationManager.cs
index 0ad565fb54c0..dc864e7e5b61 100644
--- a/src/Components/Components/src/NavigationManager.cs
+++ b/src/Components/Components/src/NavigationManager.cs
@@ -11,6 +11,25 @@ namespace Microsoft.AspNetCore.Components
///
public abstract class NavigationManager
{
+ ///
+ /// An event that fires when the navigation location is about to change.
+ ///
+ public event EventHandler LocationChanging
+ {
+ add
+ {
+ AssertInitialized();
+ _locationChanging += value;
+ }
+ remove
+ {
+ AssertInitialized();
+ _locationChanging -= value;
+ }
+ }
+
+ private EventHandler _locationChanging;
+
///
/// An event that fires when the navigation location has changed.
///
@@ -93,7 +112,10 @@ protected set
public void NavigateTo(string uri, bool forceLoad = false)
{
AssertInitialized();
- NavigateToCore(uri, forceLoad);
+ if (CanNavigateTo(uri, forceLoad))
+ {
+ NavigateToCore(uri, forceLoad);
+ }
}
///
@@ -224,6 +246,20 @@ private void AssertInitialized()
}
}
+ private bool CanNavigateTo(string uri, bool forceLoad)
+ {
+ try
+ {
+ var args = new LocationChangingEventArgs(uri, forceLoad);
+ _locationChanging?.Invoke(this, args);
+ return !args.IsNavigationPrevented;
+ }
+ catch (Exception ex)
+ {
+ throw new LocationChangeException("An exception occurred while dispatching a location changed event.", ex);
+ }
+ }
+
private static bool TryGetLengthOfBaseUriPrefix(Uri baseUri, string uri, out int length)
{
if (uri.StartsWith(baseUri.OriginalString, StringComparison.Ordinal))
diff --git a/src/Components/Components/src/Routing/LocationChangingEventArgs.cs b/src/Components/Components/src/Routing/LocationChangingEventArgs.cs
new file mode 100644
index 000000000000..0765e0ff1670
--- /dev/null
+++ b/src/Components/Components/src/Routing/LocationChangingEventArgs.cs
@@ -0,0 +1,47 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Components.Routing
+{
+ ///
+ /// for .
+ ///
+ public class LocationChangingEventArgs : EventArgs
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The location of the navigation request.
+ /// If true, the requested navigation will bypass client-side routing and force the browser to load the new page from the server, whether or not the URI would normally be handled by the client-side router.
+ public LocationChangingEventArgs(string location, bool forceLoad)
+ {
+ Location = location;
+ }
+
+ ///
+ /// Gets the changed location.
+ ///
+ public string Location { get; }
+
+ ///
+ /// Gets a value that determines if navigation should be prevented.
+ ///
+ public bool IsNavigationPrevented { get; private set; }
+
+ ///
+ /// If true, the requested navigation will bypass client-side routing and force the browser to load the new page from the server, whether or not the URI would normally be handled by the client-side router.
+ ///
+ public bool ForceLoad { get; }
+
+ ///
+ /// Indicates to the
+ /// that the navigation should be prevented.
+ ///
+ public void PreventNavigation()
+ {
+ IsNavigationPrevented = true;
+ }
+ }
+}
diff --git a/src/Components/Components/test/NavigationManagerTest.cs b/src/Components/Components/test/NavigationManagerTest.cs
index 9b857bb04477..018581ef4b70 100644
--- a/src/Components/Components/test/NavigationManagerTest.cs
+++ b/src/Components/Components/test/NavigationManagerTest.cs
@@ -92,10 +92,36 @@ public void ToBaseRelativePath_ThrowsForInvalidBaseRelativePaths(string baseUri,
ex.Message);
}
+ [Theory]
+ [InlineData("scheme://host/allowed", true)]
+ [InlineData("scheme://host/prevented", false)]
+ public void OnLocationChanging_CanPreventNavigation(string uri, bool allowNavigation)
+ {
+ bool hasNavigated = false;
+ var navigationManager = new TestNavigationManager(() => hasNavigated = true);
+ navigationManager.Initialize(uri, uri);
+ navigationManager.LocationChanging += (sender, args) =>
+ {
+ if (!allowNavigation)
+ args.PreventNavigation();
+ };
+ navigationManager.NavigateTo(uri);
+
+ Assert.Equal(allowNavigation, hasNavigated);
+ }
+
private class TestNavigationManager : NavigationManager
{
+ private Action NavigateAction;
+
public TestNavigationManager()
{
+ NavigateAction = () => throw new NotImplementedException();
+ }
+
+ public TestNavigationManager(Action navigateAction)
+ {
+ NavigateAction = navigateAction;
}
public TestNavigationManager(string baseUri = null, string uri = null)
@@ -110,7 +136,7 @@ public TestNavigationManager(string baseUri = null, string uri = null)
protected override void NavigateToCore(string uri, bool forceLoad)
{
- throw new System.NotImplementedException();
+ NavigateAction();
}
}
}