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(); } } }