| File: Routing\NavigationLock.cs | Web Access |
| Project: src\aspnetcore\src\Components\Web\src\Microsoft.AspNetCore.Components.Web.csproj (Microsoft.AspNetCore.Components.Web) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Globalization; using Microsoft.JSInterop; namespace Microsoft.AspNetCore.Components.Routing; /// <summary> /// A component that can be used to intercept navigation events. /// </summary> public sealed class NavigationLock : IComponent, IHandleAfterRender, IAsyncDisposable { private readonly string _id = Guid.NewGuid().ToString("D", CultureInfo.InvariantCulture); private RenderHandle _renderHandle; private IDisposable? _locationChangingRegistration; private bool _hasLocationChangingHandler; private bool _confirmExternalNavigation; private bool HasLocationChangingHandler => OnBeforeInternalNavigation.HasDelegate; [Inject] private IJSRuntime JSRuntime { get; set; } = default!; [Inject] private NavigationManager NavigationManager { get; set; } = default!; /// <summary> /// Gets or sets a callback to be invoked when an internal navigation event occurs. /// </summary> [Parameter] public EventCallback<LocationChangingContext> OnBeforeInternalNavigation { get; set; } /// <summary> /// Gets or sets whether a browser dialog should prompt the user to either confirm or cancel /// external navigations. /// </summary> [Parameter] public bool ConfirmExternalNavigation { get; set; } void IComponent.Attach(RenderHandle renderHandle) { _renderHandle = renderHandle; } Task IComponent.SetParametersAsync(ParameterView parameters) { foreach (var parameter in parameters) { if (parameter.Name.Equals(nameof(OnBeforeInternalNavigation), StringComparison.OrdinalIgnoreCase)) { OnBeforeInternalNavigation = (EventCallback<LocationChangingContext>)parameter.Value; } else if (parameter.Name.Equals(nameof(ConfirmExternalNavigation), StringComparison.OrdinalIgnoreCase)) { ConfirmExternalNavigation = (bool)parameter.Value; } else { throw new ArgumentException($"The component '{nameof(NavigationLock)}' does not accept a parameter with the name '{parameter.Name}'."); } } if (_hasLocationChangingHandler != HasLocationChangingHandler || _confirmExternalNavigation != ConfirmExternalNavigation) { _renderHandle.Render(static builder => { }); } return Task.CompletedTask; } async Task IHandleAfterRender.OnAfterRenderAsync() { if (_hasLocationChangingHandler != HasLocationChangingHandler) { _hasLocationChangingHandler = HasLocationChangingHandler; _locationChangingRegistration?.Dispose(); _locationChangingRegistration = _hasLocationChangingHandler ? NavigationManager.RegisterLocationChangingHandler(OnLocationChanging) : null; } if (_confirmExternalNavigation != ConfirmExternalNavigation) { _confirmExternalNavigation = ConfirmExternalNavigation; if (_confirmExternalNavigation) { await JSRuntime.InvokeVoidAsync(NavigationLockInterop.EnableNavigationPrompt, _id); } else { await JSRuntime.InvokeVoidAsync(NavigationLockInterop.DisableNavigationPrompt, _id); } } } private async ValueTask OnLocationChanging(LocationChangingContext context) { await OnBeforeInternalNavigation.InvokeAsync(context); } async ValueTask IAsyncDisposable.DisposeAsync() { _locationChangingRegistration?.Dispose(); if (_confirmExternalNavigation) { try { await JSRuntime.InvokeVoidAsync(NavigationLockInterop.DisableNavigationPrompt, _id); } catch (JSDisconnectedException) { // If the browser is gone, we don't need it to clean up any browser-side state } } } }