This content originally appeared on Telerik Blogs and was authored by Jon Hilton
No one wants to see their work go up in smoke. .NET 7’s new location handling logic makes it easy to check with your users before they depart for another site and lose all their changes.
We’ve all been there—you spend ages typing information into a web app only to accidentally navigate away from the page. In horror, you watch as a new page appears. You hit the back button, desperately hoping your work will reappear, but alas, when the page finally loads your work is gone and you’ve no option but to start from scratch.
When you’re building a Blazor web app, you probably want to save your users from such a fate.
A common requirement is to prompt the user for confirmation when they attempt to leave a page with unsaved changes. This was a non-trivial task in .NET 6 and below, but .NET 7 introduces two new related features which make it much easier to detect when navigation is about to occur and run any logic you see fit (including canceling the navigation entirely).
The New LocationChanging Event
The new LocationChanging
event runs just before Blazor navigates to a new “page.”
To use it, you need an instance of NavigationManager
.
@inject NavigationManager NavigationManager
@implements IDisposable
You can then register an event handler for LocationChanging
by overriding OnAfterRender
or its async equivalent OnAfterRenderAsync
and calling RegisterLocationChangingHandler
on NavigationManager
.
@code {
private IDisposable registration;
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
registration = NavigationManager.RegisterLocationChangingHandler(LocationChangingHandler);
}
}
private ValueTask LocationChangingHandler(LocationChangingContext arg)
{
Console.WriteLine("Location is changing...");
return ValueTask.CompletedTask;
}
public void Dispose()
{
registration?.Dispose();
}
}
There are two important details here:
- We only want to register the handler once, so we check if
firstRender
is true before registering our handler. - We implement
@IDisposable
and use theDispose
method to ensure we dispose of the event registration when our component is disposed.
This last step is important. It ensures we don’t inadvertently create a memory leak in our application by leaving orphaned/unused event handlers lying around in memory.
With this, we’ll see a message in the browser’s console (if running Blazor WASM) or our application logs (if Blazor Server) every time we attempt to navigate anywhere within our Blazor app.
In our handler for LocationChanging
, we can then choose to cancel the navigation using the PreventNavigation
method on the passed in LocationChangingContext
.
private ValueTask LocationChangingHandler(LocationChangingContext arg)
{
Console.WriteLine("Location is changing...");
arg.PreventNavigation();
return ValueTask.CompletedTask;
}
This is where we’d likely want to use a boolean
value to track whether the user has unsaved changes.
We could then check the boolean here in our LocationChangingHandler
, cancel the navigation as needed, and show something in the UI to let the user know what’s going on (more on this shortly).
It’s worth knowing the limitations of the LocationChanging
event—the main one being that it doesn’t always fire when you might want/need it to.
The LocationChanging
event will fire for:
- Any navigation triggered within your Blazor application to another “page” within your Blazor application
- Any navigation which is initiated via
NavigationManager
(using itsNavigateTo
method), including links to external sites
LocationChanging
will not fire:
- When the user enters an address directly into the Navigation bar of their browser
- When your users follow direct links to external sites (which don’t go via
NavigationManager
)
But fear not—if you want those last two scenarios handled, there is another new .NET 7 feature you can lean on.
Navigation Lock for External Links
With the NavigationLock
component, you can block your users from leaving your application when they have unsaved changes for both internal and external links.
It uses LocationChanging
under the hood, plus a little bit of JavaScript to catch every feasible scenario where your users could be leaving your page (or site).
Here’s how to use it on your page/component.
<NavigationLock ConfirmExternalNavigation="@changed"/>
With this, any attempt by the user to head to an external URL (an address outside your application) will be blocked if thechanged
boolean is true.
Unlike LocationChanging
, this will kick in when the user:
- Directly changes the address in their browser’s address bar
- Follows a link to an external site
For example, let’s say we have a contact form and want to warn users if they attempt to navigate away when the form has unsubmitted changes:
ContactForm.razor
@page "/NavigationScenarios"
@implements IDisposable
<NavigationLock ConfirmExternalNavigation="@changed"/>
<EditForm EditContext="editContext" OnValidSubmit="Submit" class="container w-50">
<div class="row">
<label class="form-label">
Email:
<InputText @bind-Value="contactFormModel.Email" class="form-control"/>
</label>
</div>
<div class="row">
<label class="form-label">
Message:
<InputTextArea @bind-Value="contactFormModel.Message" class="form-control"/>
</label>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
<!-- a handy link we can use to check the lock is working -->
<div class="row mt-2">
<a href="https://microsoft.com">Check out Microsoft.com</a>
</div>
</EditForm>
This a standard Blazor EditForm
with a little Bootstrap styling to make it look respectable in the browser.
Because we’re using an EditContext
to drive the form, we can wire up a handler to fire when any field is modified:
@code {
private bool changed = false;
private ContactForm contactFormModel = new();
private EditContext? editContext;
protected override void OnInitialized()
{
editContext = new EditContext(contactFormModel);
editContext.OnFieldChanged += EditContextOnOnFieldChanged;
}
private void EditContextOnOnFieldChanged(object sender, FieldChangedEventArgs e)
{
changed = true;
}
public void Dispose()
{
editContext.OnFieldChanged -= EditContextOnOnFieldChanged;
}
...
}
As ever, we need to be careful to detach the handler when our component is disposed.
In this EditContextOnFieldChanged
handler changed
is set to true
whenever the user interacts with any of the form’s fields.
Finally we can reset changed
back to false when the form is successfully submitted.
@code {
...
private void Submit()
{
changed = false;
}
}
With this in place, if the user attempts to navigate directly to https://microsoft.com after entering values into the form, NavigationLock
will kick in and warn them using a standard browser dialog:
The same goes if they attempt to refresh the page:
It’s worth noting we don’t have any control over the appearance or wording of these messages; they’re baked into the browser.
Navigation Lock for Internal Links
Navigation Lock can also be used to prevent internal navigation.
Because external and internal navigation is handled differently (within Blazor and the browser) we have to explicitly wire up a handler for internal navigation.
<NavigationLock OnBeforeInternalNavigation="BeforeInternalNavigation"/>
In the handler we can accept a parameter of type LocationChangingContext
:
@code {
private bool changed = false;
private void BeforeInternalNavigation(LocationChangingContext context)
{
if(changed)
context.PreventNavigation();
}
}
We can then use this context and call PreventNavigation
whenever we want to stop the navigation from taking place.
This will silently abandon the navigation, but chances are we want to let the user know why they’re being blocked from leaving the page.
Custom Confirmation UI
Whether we’re hooking up a handler to Navigation Manager’s LocationChanging
event or assigning a handler to a Navigation Lock component’s OnBeforeInternalNavigation
parameter, the chances are we want a way to display a message when we prevent navigation.
There are a few ways to implement this:
- We can build our own UI to prompt the user for confirmation.
- We can “cheat” and invoke the standard JavaScript confirmation dialog:
private async Task BeforeInternalNavigation(LocationChangingContext context)
{
if (changed)
{
var proceed = await JsRuntime.InvokeAsync<bool>("confirm", "Are you sure you want to continue?");
if(!proceed)
context.PreventNavigation();
}
}
Even better, if we’re using a component library, we can use built-in functionality—such as Telerik’s Dialogs feature:
@code {
[CascadingParameter]
public DialogFactory Dialogs { get; set; }
private async Task BeforeInternalNavigation(LocationChangingContext context)
{
if (changed)
{
var proceed = await Dialogs.ConfirmAsync("You have unsaved changes, are you sure you want to leave this page?");
if(!proceed)
context.PreventNavigation();
}
}
}
In Summary
The new LocationChanging
event and NavigationLock
component make it much easier to intercept both internal and external navigation events in order to run custom business logic.
You can decide whether to cancel navigation and offer users a choice whether to proceed or not, according to your application’s needs.
LocationChanging
is limited to internal navigation and external navigation that originates from NavigationManager
(will not fire for direct links to external sites, nor when the user
directly enters a new address in their browser’s address bar).
NavigationLock
can handle both internal and external links, effectively blocking any attempt by the user to navigate to another URL.
When blocking external navigation using NavigationLock
, you’re limited to the browser’s own “lock” UI. When blocking internal navigation, you can implement
custom UI to warn the user and offer them a choice whether to proceed.
This content originally appeared on Telerik Blogs and was authored by Jon Hilton
Jon Hilton | Sciencx (2023-03-01T08:36:00+00:00) Blazor’s New LocationChanging Events in .NET 7. Retrieved from https://www.scien.cx/2023/03/01/blazors-new-locationchanging-events-in-net-7/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.