Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Routing features (loc changes, NavigationLock) #26919

Closed
wants to merge 17 commits into from
128 changes: 128 additions & 0 deletions aspnetcore/blazor/fundamentals/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -1543,6 +1543,7 @@ Use <xref:Microsoft.AspNetCore.Components.NavigationManager> to manage URIs and
| <xref:Microsoft.AspNetCore.Components.NavigationManager.LocationChanged> | An event that fires when the navigation location has changed. For more information, see the [Location changes](#location-changes) section. |
| <xref:Microsoft.AspNetCore.Components.NavigationManager.ToAbsoluteUri%2A> | Converts a relative URI into an absolute URI. |
| <xref:Microsoft.AspNetCore.Components.NavigationManager.ToBaseRelativePath%2A> | Given a base URI (for example, a URI previously returned by <xref:Microsoft.AspNetCore.Components.NavigationManager.BaseUri>), converts an absolute URI into a URI relative to the base URI prefix. |
| [`RegisterLocationChangingHandler`](#handle-location-changes-in-a-component) | Registers a handler to process incoming navigation events. Calling <xref:Microsoft.AspNetCore.Components.NavigationManager.NavigateTo%2A> always invokes the handler. |
| <xref:Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithQueryParameter%2A> | Returns a URI constructed by updating <xref:Microsoft.AspNetCore.Components.NavigationManager.Uri?displayProperty=nameWithType> with a single parameter added, updated, or removed. For more information, see the [Query strings](#query-strings) section. |

## Location changes
Expand All @@ -1567,6 +1568,23 @@ The following component:

For more information on component disposal, see <xref:blazor/components/lifecycle#component-disposal-with-idisposable-and-iasyncdisposable>.

## Navigation options

Pass `NavigationOptions` to <xref:Microsoft.AspNetCore.Components.NavigationManager.NavigateTo%2A> to control the following behaviors:

* `ForceLoad`: Bypass client-side routing and force the browser to load the new page from the server, whether or not the URI is handled by the client-side router. The default value is `false`.
* `ReplaceHistoryEntry`: Replace the current entry in the history stack. If `false`, append the new entry to the history stack. The default value is `false`.
* `HistoryEntryState`: Gets or sets the state to append to the history entry.

```csharp
NavigationManager.NavigateTo("/path", new NavigationOptions
{
HistoryEntryState = "Navigation state"
});
```

For more information on obtaining the state associated with the target history entry while handling location changes, see the [Handle location changes in a component](#handle-location-changes-in-a-component) section.

## Query strings

Use the [`[SupplyParameterFromQuery]` attribute](xref:Microsoft.AspNetCore.Components.SupplyParameterFromQueryAttribute) with the [`[Parameter]` attribute](xref:Microsoft.AspNetCore.Components.ParameterAttribute) to specify that a component parameter of a routable component can come from the query string.
Expand Down Expand Up @@ -1902,6 +1920,116 @@ In the following `App` component example:
> [!NOTE]
> Not throwing if the cancellation token in <xref:Microsoft.AspNetCore.Components.Routing.NavigationContext> is canceled can result in unintended behavior, such as rendering a component from a previous navigation.

## Handle location changes in a component
guardrex marked this conversation as resolved.
Show resolved Hide resolved

`RegisterLocationChangingHandler` registers a handler to process incoming navigation events. The handler's context provided by `LocationChangingContext` includes the following properties:
guardrex marked this conversation as resolved.
Show resolved Hide resolved

* `TargetLocation`: Gets the target location.
* `HistoryEntryState`: Gets the state associated with the target history entry.
* `IsNavigationIntercepted`: Gets whether the navigation was intercepted from a link.
* `CancellationToken`: Gets a <xref:System.Threading.CancellationToken> to determine if the navigation was canceled, for example, to determine if the user triggered a different navigation.
* `PreventNavigation`: Called to prevent the navigation from continuing.

A component can register multiple location changing handlers, and calling <xref:Microsoft.AspNetCore.Components.NavigationManager.NavigateTo%2A> invokes all of the registered handlers. [Dispose registered handlers](xref:blazor/components/lifecycle#component-disposal-with-idisposable-and-iasyncdisposable) to unregister them.

In the following example, a location changing handler is registered for navigation events. Note that the component implements `IDisposable` and disposes the handler in its `Dispose` method.

`Pages/NavHandler.razor`:

```razor
@page "/nav-handler"
@inject NavigationManager NavigationManager
@implements IDisposable

<p>
<button @onclick="NavigationManager.NavigateTo("/")">
Home (Allowed)
</button>
<button @onclick="NavigationManager.NavigateTo("/counter")">
Counter (Prevented)
</button>
</p>

@code {
private IDisposable? registration;

protected override void OnInitialized()
{
registration =
NavigationManager.RegisterLocationChangingHandler(OnLocationChanging);
}
guardrex marked this conversation as resolved.
Show resolved Hide resolved

private async ValueTask OnLocationChanging(LocationChangingContext context)
{
if (context.TargetLocation == "counter")
{
context.PreventNavigation();
}
}

public void Dispose() => registration?.Dispose();
}
```

Since internal navigation can be canceled asynchronously, multiple overlapping calls to registered handlers may occur. For example, multiple handler calls may occur when the user rapidly selects the back button on a page or selects multiple links before a navigation is executed. The following is a summary of the asynchronous navigation logic:

* If any location changing handlers are registered, all navigations are initially reverted, then replayed if the navigation isn't canceled.
* If overlapping navigation requests are made, the latest request always cancels earlier requests, which means the following:
* The app may treat multiple back and forward button selections as a single selection.
* If the user selects multiple links before the navigation completes, the last link selected determines the navigation.

For more information on passing `NavigationOptions` to <xref:Microsoft.AspNetCore.Components.NavigationManager.NavigateTo%2A> to control entries and state of the navigation history stack, see the [Navigation options](#navigation-options) section.

For additional example code, see the [`NavigationManagerComponent` in the `BasicTestApp` (`dotnet/aspnetcore` reference source)](https://github.com/dotnet/aspnetcore/blob/main/src/Components/test/testassets/BasicTestApp/RouterTest/NavigationManagerComponent.razor).

[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)]

To facilitate the control of navigation events in a Razor component, use the `NavigationLock` component. As long as the `NavigationLock` component is rendered, it intercepts navigation events. Handlers associated with `NavigationLock` components are disposed automatically when the component is disposed.

In the following `NavLock` component:

* `ConfirmExternalNavigation` sets a browser dialog to prompt the user to either confirm or cancel the external navigation. The default value is `false`. An attempt to follow the link to Microsoft's website must be confirmed by the user before the navigation to `https://www.microsoft.com` succeeds.
* `OnBeforeInternalNavigation` sets a callback to be invoked when an internal navigation event occurs. `PreventNavigation` is called to prevent naviation from occurring if the user declines to confirm the navigation via a [JavaScript (JS) interop call](xref:blazor/js-interop/call-javascript-from-dotnet) that spawns the [JS `confirm` dialog](https://developer.mozilla.org/docs/Web/API/Window/confirm).

`Pages/NavLock.razor`:

```razor
@page "/nav-lock"
@inject IJSRuntime JSRuntime
@inject NavigationManager NavigationManager

<NavigationLock ConfirmExternalNavigation="true"
OnBeforeInternalNavigation="OnBeforeInternalNavigation" />

<p>
<button @onclick="Navigate">Navigate</button>
</p>

<p>
<a href="https://www.microsoft.com">Microsoft homepage</a>
</p>

@code {
private void Navigate()
{
NavigationManager.NavigateTo("/");
}

private async Task OnBeforeInternalNavigation(LocationChangingContext context)
{
var isConfirmed = await JSRuntime.InvokeAsync<bool>("confirm",
"Are you sure you want to navigate to the Index page?");

if (!isConfirmed)
{
context.PreventNavigation();
}
}
}
```

For additional example code, see the [`ConfigurableNavigationLock` component in the `BasicTestApp` (`dotnet/aspnetcore` reference source)](https://github.com/dotnet/aspnetcore/blob/main/src/Components/test/testassets/BasicTestApp/RouterTest/ConfigurableNavigationLock.razor).

## `NavLink` and `NavMenu` components

Use a <xref:Microsoft.AspNetCore.Components.Routing.NavLink> component in place of HTML hyperlink elements (`<a>`) when creating navigation links. A <xref:Microsoft.AspNetCore.Components.Routing.NavLink> component behaves like an `<a>` element, except it toggles an `active` CSS class based on whether its `href` matches the current URL. The `active` class helps a user understand which page is the active page among the navigation links displayed. Optionally, assign a CSS class name to <xref:Microsoft.AspNetCore.Components.Routing.NavLink.ActiveClass?displayProperty=nameWithType> to apply a custom CSS class to the rendered link when the current route matches the `href`.
Expand Down