Mirality.Blazor.Routing 1.0.2

dotnet add package Mirality.Blazor.Routing --version 1.0.2                
NuGet\Install-Package Mirality.Blazor.Routing -Version 1.0.2                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Mirality.Blazor.Routing" Version="1.0.2" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Mirality.Blazor.Routing --version 1.0.2                
#r "nuget: Mirality.Blazor.Routing, 1.0.2"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Mirality.Blazor.Routing as a Cake Addin
#addin nuget:?package=Mirality.Blazor.Routing&version=1.0.2

// Install Mirality.Blazor.Routing as a Cake Tool
#tool nuget:?package=Mirality.Blazor.Routing&version=1.0.2                

.NET 6 NuGet version (Mirality.Blazor.Routing)

This is a custom Blazor router implementation that allows navigation to be cancelled (for example, to prevent data loss if a form has unsaved changes).

It is inspired by Blazr.Demo.Routing and uses a similar technique to hook the routing and actually cancel navigation; though it does not copy any of the actual code directly, and it uses a completely different front-end that I happen to prefer over that version.

It embeds (almost entirely verbatim) a large chunk of ASP.NET 6 Router code, since these were implemented as internal and there isn't currently any other way to hook it.

Currently, I only test this with Blazor WebAssembly, although it should theoretically work with Blazor Server as well. Let me know if there are any issues.

Basic Setup

  1. Either use the precompiled package from nuget.org; or optionally, download this project and unpack it to a suitable location in your source tree (or reference it as a git submodule).

  2. Add it to your solution if needed, and add it as a project reference to your application.

  3. Optionally, add @using Mirality.Blazor.Routing to your _Imports.razor file (otherwise you'll need to add this to any file that uses components from this library).

  4. In your Program.cs, add this near the top of your services (and add the corresponding using to the top of the file):

    builder.Services.AddLockableNavigationManager();
    
  5. In your App.razor, swap Router to CustomRouter (adding the @using if you didn't already).

  6. Optional, but recommended: swap anywhere you're injecting NavigationManager with ICustomNavigationManager. (See below for more details.)

Controlling Navigation Blocking

The Simple Way

Simply add the following to any page where you want to be able to block navigation:

<PageLocker IsLocked="@_IsLocked" NavigationBlocked="OnNavigationBlocked" />

In your code-behind, declare:

private bool _IsLocked;

private async Task OnNavigationBlocked(LocationChangedEventArgs e)
{
    //...
}

(changing names if you prefer.) Then simply set this field to true whenever you want to block navigation and to false when you want to enable it again. You'll receive a callback if navigation is attempted while locked.

The actual lock/unlock is deferred until the end of the current event (whenever it next synchronises parameters), so this is not suitable if you want to unlock navigation and then immediately NavigateTo, for example.

You can also use any other valid syntax for passing parameters to a child component, as you'd expect; for example, you can use some existing property of your model or call a method instead of introducing a new field.

The Advanced Way

If you need more control over locking than the above provides (for example, if you want to unlock-and-navigate as mentioned above), then instead of doing the above, do this:

@inject ILockableNavigationManager Nav

or

[Inject] private ILockableNavigationManager Nav { get; set; } = default!;

and then also declare

private IDisposable? _Lock;

and then finally whenever you want to change the state, use:

Nav.SetLockState(ref _Lock, true/false);

Note that when using this method, you must remember to implement IDisposable and/or IAsyncDisposable and either dispose the lock or set it to false when your component is disposed. (While ordinarily you would not expect your component to be disposed when navigation is locked, it's not entirely impossible. And it's just good code hygiene.)

Blocking closing tabs

By default, nothing stops closing the browser tab, even if navigation is locked. If you want to intercept that as well, then include this component somewhere:

<BrowserExitLock />

Usually the best place to put this is in your MainLayout.razor, but you could put it in your App or in your individual page or subcomponent if you like. But there can only be one such component at a time -- it's ok to have one in each page, but it's not ok to have one in the layout and one in the page, for example.

Also note that browsers are free to ignore this if they feel like it, and in particular will usually prompt the user to confirm exiting or not before this gets any say in the matter -- so you will typically only receive the corresponding event after the user has already agreed to remain on the page; thus there's usually no need to actually react to it.

(If you're migrating from Blazr.Demo.Routing, note that this is the approximate equivalent of the PageLocker component from that project -- the PageLocker here serves a different role.)

The regular NavigationManager methods and properties are also available from both ICustomNavigationManager and ILockableNavigationManager, so you can (mostly) use them interchangeably, including calling NavigateTo on any of them.

One caveat is the LocationChanged event -- due to how things work internally, if you listen directly to NavigationManager.LocationChanged then you will receive some spurious "navigated away and then back" notifications whenever navigation is cancelled, because the default manager does not (yet) support actual cancellation. It's strongly recommended to subscribe to the event on either of the interfaces instead, which will only receive the non-cancelled navigations.

Additional events

ILockableNavigationManager implements some additional properties and events that may be of interest.

The main one is the event NavigationBlocked, which is raised when a navigation is attempted while locked; you can use this to display some kind of warning or confirmation.

Another is the event BrowserExitBlocked, which is raised when the browser tab is closed while locked and the user has already confirmed that they want to stay on the page. As noted above, this is usually less interesting in most cases, but is available in case it is needed.

To subscribe to these, just inject the interface as shown above. Don't forget to unsubscribe on Dispose too.

Advanced usage

If you want to use the custom router but don't want the lockable navigation for some reason, you can call AddDefaultNavigationManager instead of AddLockableNavigationManager in your Program.cs. Both interfaces will still exist in this case, but locking is disabled and does nothing.

You can alternatively implement your own version of any of these interfaces or components and they should be able to talk to each other as you'd expect.

You can even make your own custom navigation manager and delegate to the ILockableNavigationManager interface to combine your own logic with lockability.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.2 8,277 6/15/2022
1.0.1 589 5/20/2022