Skip to main content
Microsoft Ignite
Nov 19–22, 2024

Join us this November to explore AI innovations, level up your skillset, and expand your network.

Learn
Sign in
ASP.NET Core
Version
Search
Filter by title
ASP.NET Core documentation
Get started
Choose an ASP.NET Core UI
Overview
Supported platforms
Tooling
WebAssembly tooling and AOT
Hosting models
Project structure
Overview
Routing and navigation
Configuration
Dependency injection
Startup
Environments
Logging
Handle errors
SignalR
Static files
Globalization and localization
File uploads
File downloads
Call a web API
Images and documents
State management
Debug
Lazy load assemblies with WebAssembly
WebAssembly native dependencies
Performance
Test components
Progressive Web Applications
Blazor with EF Core
Advanced scenarios
Session and state management
Layout
Razor syntax
Razor class libraries
Best practices
API reference
Contribute
Learn  .NET  ASP.NET Core 
ASP.NET Core Blazor routing and navigation
Article
10/21/2024
12 contributors
In this article
Route templates
Focus an element on navigation
Provide custom content when content isn't found
Route to components from multiple assemblies
Show 17 more
 Note

This isn't the latest version of this article. For the current release, see the .NET 9 version of this article.

 Warning

This version of ASP.NET Core is no longer supported. For more information, see .NET and .NET Core Support Policy. For the current release, see the .NET 8 version of this article.

This article explains how to manage Blazor app request routing and how to use the NavLink component to create navigation links.

 Important

Code examples throughout this article show methods called on Navigation, which is an injected NavigationManager in classes and components.

Route templates
The Router component enables routing to Razor components. The Router component is used in the App component (App.razor).

When a Razor component (.razor) with an @page directive is compiled, the generated component class is provided a RouteAttribute specifying the component's route template.

When the app starts, the assembly specified as the Router's AppAssembly is scanned to gather route information for the app's components that have a RouteAttribute.

At runtime, the RouteView component:

Receives the RouteData from the Router along with any route parameters.
Renders the specified component with its layout, including any further nested layouts.
Optionally specify a DefaultLayout parameter with a layout class for components that don't specify a layout with the @layout directive. The framework's Blazor project templates specify the MainLayout component (MainLayout.razor) as the app's default layout. For more information on layouts, see ASP.NET Core Blazor layouts.

Components support multiple route templates using multiple @page directives. The following example component loads on requests for /blazor-route and /different-blazor-route.

BlazorRoute.razor:

razor

Copy
@page "/blazor-route"
@page "/different-blazor-route"

<h1>Blazor routing</h1>
 Important

For URLs to resolve correctly, the app must include a <base> tag (location of <head> content) with the app base path specified in the href attribute. For more information, see Host and deploy ASP.NET Core Blazor.

As an alternative to specifying the route template as a string literal with the @page directive, constant-based route templates can be specified with the @attribute directive.

In the following example, the @page directive in a component is replaced with the @attribute directive and the constant-based route template in Constants.CounterRoute, which is set elsewhere in the app to "/counter":

diff

Copy
- @page "/counter"
+ @attribute [Route(Constants.CounterRoute)]
Focus an element on navigation
The FocusOnNavigate component sets the UI focus to an element based on a CSS selector after navigating from one page to another.

razor

Copy
<FocusOnNavigate RouteData="routeData" Selector="h1" />
When the Router component navigates to a new page, the FocusOnNavigate component sets the focus to the page's top-level header (<h1>). This is a common strategy for ensuring that a page navigation is announced when using a screen reader.

Provide custom content when content isn't found
The Router component allows the app to specify custom content if content isn't found for the requested route.

Set custom content for the Router component's NotFound parameter:

razor

Copy
<Router ...>
    ...
    <NotFound>
        ...
    </NotFound>
</Router>
Arbitrary items are supported as content of the NotFound parameter, such as other interactive components. To apply a default layout to NotFound content, see ASP.NET Core Blazor layouts.

Route to components from multiple assemblies
This section applies to Blazor Server apps.

Use the Router component's AdditionalAssemblies parameter and the endpoint convention builder AddAdditionalAssemblies to discover routable components in additional assemblies.

In the following example, Component1 is a routable component defined in a referenced component class library named ComponentLibrary:

razor

Copy
<Router
    AppAssembly="..."
    AdditionalAssemblies="new[] { typeof(ComponentLibrary.Component1).Assembly }">
    ...
</Router>
Additional assemblies are scanned in addition to the assembly specified to AppAssembly.

Route parameters
The router uses route parameters to populate the corresponding component parameters with the same name. Route parameter names are case insensitive. In the following example, the text parameter assigns the value of the route segment to the component's Text property. When a request is made for /route-parameter-1/amazing, the content is rendered as Blazor is amazing!.

RouteParameter1.razor:

razor

Copy
@page "/route-parameter-1/{text}"

<h1>Blazor is @Text!</h1>

@code {
    [Parameter]
    public string? Text { get; set; }
}
Optional parameters are supported. In the following example, the text optional parameter assigns the value of the route segment to the component's Text property. If the segment isn't present, the value of Text is set to fantastic.

RouteParameter2.razor:

razor

Copy
@page "/route-parameter-2/{text?}"

<h1>Blazor is @Text!</h1>

@code {
    [Parameter]
    public string? Text { get; set; }

    protected override void OnParametersSet()
    {
        Text = Text ?? "fantastic";
    }
}
When the OnInitialized{Async} lifecycle method is used instead of the OnParametersSet{Async} lifecycle method, the default assignment of the Text property to fantastic doesn't occur if the user navigates within the same component. For example, this situation arises when the user navigates from /route-parameter-2/amazing to /route-parameter-2. As the component instance persists and accepts new parameters, the OnInitialized method isn't invoked again.

Route constraints
A route constraint enforces type matching on a route segment to a component.

In the following example, the route to the User component only matches if:

An Id route segment is present in the request URL.
The Id segment is an integer (int) type.
User.razor:

razor

Copy
@page "/user/{Id:int}"

<PageTitle>User</PageTitle>

<h1>User Example</h1>

<p>User Id: @Id</p>

@code {
    [Parameter]
    public int Id { get; set; }
}
The route constraints shown in the following table are available. For the route constraints that match the invariant culture, see the warning below the table for more information.

Constraint	Example	Example Matches	Invariant
culture
matching
bool	{active:bool}	true, FALSE	No
datetime	{dob:datetime}	2016-12-31, 2016-12-31 7:32pm	Yes
decimal	{price:decimal}	49.99, -1,000.01	Yes
double	{weight:double}	1.234, -1,001.01e8	Yes
float	{weight:float}	1.234, -1,001.01e8	Yes
guid	{id:guid}	00001111-aaaa-2222-bbbb-3333cccc4444, {00001111-aaaa-2222-bbbb-3333cccc4444}	No
int	{id:int}	123456789, -123456789	Yes
long	{ticks:long}	123456789, -123456789	Yes
nonfile	{parameter:nonfile}	Not BlazorSample.styles.css, not favicon.ico	Yes
 Warning

Route constraints that verify the URL and are converted to a CLR type (such as int or DateTime) always use the invariant culture. These constraints assume that the URL is non-localizable.

Route constraints also work with optional parameters. In the following example, Id is required, but Option is an optional boolean route parameter.

User.razor:

razor

Copy
@page "/user/{id:int}/{option:bool?}"

<p>
    Id: @Id
</p>

<p>
    Option: @Option
</p>

@code {
    [Parameter]
    public int Id { get; set; }

    [Parameter]
    public bool Option { get; set; }
}
Avoid file capture in a route parameter
The following route template inadvertently captures static asset paths in its optional route parameter (Optional). For example, the app's stylesheet (.styles.css) is captured, which breaks the app's styles:

razor

Copy
@page "/{optional?}"

...

@code {
    [Parameter]
    public string? Optional { get; set; }
}
To restrict a route parameter to capturing non-file paths, use the :nonfile constraint in the route template:

razor

Copy
@page "/{optional:nonfile?}"
Routing with URLs that contain dots
A server-side default route template assumes that if the last segment of a request URL contains a dot (.) that a file is requested. For example, the relative URL /example/some.thing is interpreted by the router as a request for a file named some.thing. Without additional configuration, an app returns a 404 - Not Found response if some.thing was meant to route to a component with an @page directive and some.thing is a route parameter value. To use a route with one or more parameters that contain a dot, the app must configure the route with a custom template.

Consider the following Example component that can receive a route parameter from the last segment of the URL.

Example.razor:

razor

Copy
@page "/example/{param?}"

<p>
    Param: @Param
</p>

@code {
    [Parameter]
    public string? Param { get; set; }
}
To permit the Server app of a hosted Blazor WebAssembly solution to route the request with a dot in the param route parameter, add a fallback file route template with the optional parameter in the Program file:

C#

Copy
app.MapFallbackToFile("/example/{param?}", "index.html");
To configure a Blazor Server app to route the request with a dot in the param route parameter, add a fallback page route template with the optional parameter in the Program file:

C#

Copy
app.MapFallbackToPage("/example/{param?}", "/_Host");
For more information, see Routing in ASP.NET Core.

Catch-all route parameters
Catch-all route parameters, which capture paths across multiple folder boundaries, are supported in components.

Catch-all route parameters are:

Named to match the route segment name. Naming isn't case-sensitive.
A string type. The framework doesn't provide automatic casting.
At the end of the URL.
CatchAll.razor:

razor

Copy
@page "/catch-all/{*pageRoute}"

@code {
    [Parameter]
    public string? PageRoute { get; set; }
}
For the URL /catch-all/this/is/a/test with a route template of /catch-all/{*pageRoute}, the value of PageRoute is set to this/is/a/test.

Slashes and segments of the captured path are decoded. For a route template of /catch-all/{*pageRoute}, the URL /catch-all/this/is/a%2Ftest%2A yields this/is/a/test*.

URI and navigation state helpers
Use NavigationManager to manage URIs and navigation in C# code. NavigationManager provides the event and methods shown in the following table.

Member	Description
Uri	Gets the current absolute URI.
BaseUri	Gets the base URI (with a trailing slash) that can be prepended to relative URI paths to produce an absolute URI. Typically, BaseUri corresponds to the href attribute on the document's <base> element (location of <head> content).
NavigateTo	Navigates to the specified URI. If forceLoad is true:
Client-side routing is bypassed.
The browser is forced to load the new page from the server, whether or not the URI is normally handled by the client-side router.
If replace is true, the current URI in the browser history is replaced instead of pushing a new URI onto the history stack.
LocationChanged	An event that fires when the navigation location has changed. For more information, see the Location changes section.
ToAbsoluteUri	Converts a relative URI into an absolute URI.
ToBaseRelativePath	Based on the app's base URI, converts an absolute URI into a URI relative to the base URI prefix. For an example, see the Produce a URI relative to the base URI prefix section.
RegisterLocationChangingHandler	Registers a handler to process incoming navigation events. Calling NavigateTo always invokes the handler.
GetUriWithQueryParameter	Returns a URI constructed by updating NavigationManager.Uri with a single parameter added, updated, or removed. For more information, see the Query strings section.
Location changes
For the LocationChanged event, LocationChangedEventArgs provides the following information about navigation events:

Location: The URL of the new location.
IsNavigationIntercepted: If true, Blazor intercepted the navigation from the browser. If false, NavigationManager.NavigateTo caused the navigation to occur.
The following component:

Navigates to the app's Counter component (Counter.razor) when the button is selected using NavigateTo.
Handles the location changed event by subscribing to NavigationManager.LocationChanged.
The HandleLocationChanged method is unhooked when Dispose is called by the framework. Unhooking the method permits garbage collection of the component.

The logger implementation logs the following information when the button is selected:

BlazorSample.Pages.Navigate: Information: URL of new location: https://localhost:{PORT}/counter

Navigate.razor:

razor

Copy
@page "/navigate"
@using Microsoft.Extensions.Logging 
@implements IDisposable
@inject ILogger<Navigate> Logger
@inject NavigationManager Navigation

<h1>Navigate in component code example</h1>

<button class="btn btn-primary" @onclick="NavigateToCounterComponent">
    Navigate to the Counter component
</button>

@code {
    private void NavigateToCounterComponent()
    {
        Navigation.NavigateTo("counter");
    }

    protected override void OnInitialized()
    {
        Navigation.LocationChanged += HandleLocationChanged;
    }

    private void HandleLocationChanged(object? sender, LocationChangedEventArgs e)
    {
        Logger.LogInformation("URL of new location: {Location}", e.Location);
    }

    public void Dispose()
    {
        Navigation.LocationChanged -= HandleLocationChanged;
    }
}
For more information on component disposal, see ASP.NET Core Razor component lifecycle.

Produce a URI relative to the base URI prefix
Based on the app's base URI, ToBaseRelativePath converts an absolute URI into a URI relative to the base URI prefix.

Consider the following example:

C#

Copy
try
{
    baseRelativePath = Navigation.ToBaseRelativePath(inputURI);
}
catch (ArgumentException ex)
{
    ...
}
If the base URI of the app is https://localhost:8000, the following results are obtained:

Passing https://localhost:8000/segment in inputURI results in a baseRelativePath of segment.
Passing https://localhost:8000/segment1/segment2 in inputURI results in a baseRelativePath of segment1/segment2.
If the base URI of the app doesn't match the base URI of inputURI, an ArgumentException is thrown.

Passing https://localhost:8001/segment in inputURI results in the following exception:

System.ArgumentException: 'The URI 'https://localhost:8001/segment' is not contained by the base URI 'https://localhost:8000/'.'

Navigation history state
The NavigationManager uses the browser's History API to maintain navigation history state associated with each location change made by the app. Maintaining history state is particularly useful in external redirect scenarios, such as when authenticating users with external identity providers. For more information, see the Navigation options section.

Navigation options
Pass NavigationOptions to NavigateTo 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.
C#

Copy
Navigation.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/prevent location changes section.

Query strings
Use the [SupplyParameterFromQuery] attribute with the [Parameter] attribute to specify that a component parameter of a routable component comes from the query string.

 Note

Component parameters can only receive query parameter values in routable components with an @page directive.

Only routable components directly receive query parameters in order to avoid subverting top-down information flow and to make parameter processing order clear, both by the framework and by the app. This design avoids subtle bugs in app code that was written assuming a specific parameter processing order. You're free to define custom cascading parameters or directly assign to regular component parameters in order to pass query parameter values to non-routable components.

Component parameters supplied from the query string support the following types:

bool, DateTime, decimal, double, float, Guid, int, long, string.
Nullable variants of the preceding types.
Arrays of the preceding types, whether they're nullable or not nullable.
The correct culture-invariant formatting is applied for the given type (CultureInfo.InvariantCulture).

Specify the [SupplyParameterFromQuery] attribute's Name property to use a query parameter name different from the component parameter name. In the following example, the C# name of the component parameter is {COMPONENT PARAMETER NAME}. A different query parameter name is specified for the {QUERY PARAMETER NAME} placeholder:

Just like component parameter properties ([Parameter]), [SupplyParameterFromQuery] properties are always public properties in .NET 6/7. In .NET 8 or later, [SupplyParameterFromQuery] properties can be marked public or private.

C#

Copy
[Parameter]
[SupplyParameterFromQuery(Name = "{QUERY PARAMETER NAME}")]
public string? {COMPONENT PARAMETER NAME} { get; set; }
In the following example with a URL of /search?filter=scifi%20stars&page=3&star=LeVar%20Burton&star=Gary%20Oldman:

The Filter property resolves to scifi stars.
The Page property resolves to 3.
The Stars array is filled from query parameters named star (Name = "star") and resolves to LeVar Burton and Gary Oldman.
Search.razor:

razor

Copy
@page "/search"

<h1>Search Example</h1>

<p>Filter: @Filter</p>

<p>Page: @Page</p>

@if (Stars is not null)
{
    <p>Stars:</p>

    <ul>
        @foreach (var name in Stars)
        {
            <li>@name</li>
        }
    </ul>
}

@code {
    [Parameter]
    [SupplyParameterFromQuery]
    public string? Filter { get; set; }

    [Parameter]
    [SupplyParameterFromQuery]
    public int? Page { get; set; }

    [Parameter]
    [SupplyParameterFromQuery(Name = "star")]
    public string[]? Stars { get; set; }
}
Use GetUriWithQueryParameter to add, change, or remove one or more query parameters on the current URL:

razor

Copy
@inject NavigationManager Navigation

...

Navigation.GetUriWithQueryParameter("{NAME}", {VALUE})
For the preceding example:

The {NAME} placeholder specifies the query parameter name. The {VALUE} placeholder specifies the value as a supported type. Supported types are listed later in this section.
A string is returned equal to the current URL with a single parameter:
Added if the query parameter name doesn't exist in the current URL.
Updated to the value provided if the query parameter exists in the current URL.
Removed if the type of the provided value is nullable and the value is null.
The correct culture-invariant formatting is applied for the given type (CultureInfo.InvariantCulture).
The query parameter name and value are URL-encoded.
All of the values with the matching query parameter name are replaced if there are multiple instances of the type.
Call GetUriWithQueryParameters to create a URI constructed from Uri with multiple parameters added, updated, or removed. For each value, the framework uses value?.GetType() to determine the runtime type for each query parameter and selects the correct culture-invariant formatting. The framework throws an error for unsupported types.

razor

Copy
@inject NavigationManager Navigation

...

Navigation.GetUriWithQueryParameters({PARAMETERS})
The {PARAMETERS} placeholder is an IReadOnlyDictionary<string, object>.

Pass a URI string to GetUriWithQueryParameters to generate a new URI from a provided URI with multiple parameters added, updated, or removed. For each value, the framework uses value?.GetType() to determine the runtime type for each query parameter and selects the correct culture-invariant formatting. The framework throws an error for unsupported types. Supported types are listed later in this section.

razor

Copy
@inject NavigationManager Navigation

...

Navigation.GetUriWithQueryParameters("{URI}", {PARAMETERS})
The {URI} placeholder is the URI with or without a query string.
The {PARAMETERS} placeholder is an IReadOnlyDictionary<string, object>.
Supported types are identical to supported types for route constraints:

bool
DateTime
decimal
double
float
Guid
int
long
string
Supported types include:

Nullable variants of the preceding types.
Arrays of the preceding types, whether they're nullable or not nullable.
Replace a query parameter value when the parameter exists
C#

Copy
Navigation.GetUriWithQueryParameter("full name", "Morena Baccarin")
Current URL	Generated URL
scheme://host/?full%20name=David%20Krumholtz&age=42	scheme://host/?full%20name=Morena%20Baccarin&age=42
scheme://host/?fUlL%20nAmE=David%20Krumholtz&AgE=42	scheme://host/?full%20name=Morena%20Baccarin&AgE=42
scheme://host/?full%20name=Jewel%20Staite&age=42&full%20name=Summer%20Glau	scheme://host/?full%20name=Morena%20Baccarin&age=42&full%20name=Morena%20Baccarin
scheme://host/?full%20name=&age=42	scheme://host/?full%20name=Morena%20Baccarin&age=42
scheme://host/?full%20name=	scheme://host/?full%20name=Morena%20Baccarin
Append a query parameter and value when the parameter doesn't exist
C#

Copy
Navigation.GetUriWithQueryParameter("name", "Morena Baccarin")
Current URL	Generated URL
scheme://host/?age=42	scheme://host/?age=42&name=Morena%20Baccarin
scheme://host/	scheme://host/?name=Morena%20Baccarin
scheme://host/?	scheme://host/?name=Morena%20Baccarin
Remove a query parameter when the parameter value is null
C#

Copy
Navigation.GetUriWithQueryParameter("full name", (string)null)
Current URL	Generated URL
scheme://host/?full%20name=David%20Krumholtz&age=42	scheme://host/?age=42
scheme://host/?full%20name=Sally%20Smith&age=42&full%20name=Summer%20Glau	scheme://host/?age=42
scheme://host/?full%20name=Sally%20Smith&age=42&FuLl%20NaMe=Summer%20Glau	scheme://host/?age=42
scheme://host/?full%20name=&age=42	scheme://host/?age=42
scheme://host/?full%20name=	scheme://host/
Add, update, and remove query parameters
In the following example:

name is removed, if present.
age is added with a value of 25 (int), if not present. If present, age is updated to a value of 25.
eye color is added or updated to a value of green.
C#

Copy
Navigation.GetUriWithQueryParameters(
    new Dictionary<string, object?>
    {
        ["name"] = null,
        ["age"] = (int?)25,
        ["eye color"] = "green"
    })
Current URL	Generated URL
scheme://host/?name=David%20Krumholtz&age=42	scheme://host/?age=25&eye%20color=green
scheme://host/?NaMe=David%20Krumholtz&AgE=42	scheme://host/?age=25&eye%20color=green
scheme://host/?name=David%20Krumholtz&age=42&keepme=true	scheme://host/?age=25&keepme=true&eye%20color=green
scheme://host/?age=42&eye%20color=87	scheme://host/?age=25&eye%20color=green
scheme://host/?	scheme://host/?age=25&eye%20color=green
scheme://host/	scheme://host/?age=25&eye%20color=green
Support for enumerable values
In the following example:

full name is added or updated to Morena Baccarin, a single value.
ping parameters are added or replaced with 35, 16, 87 and 240.
C#

Copy
Navigation.GetUriWithQueryParameters(
    new Dictionary<string, object?>
    {
        ["full name"] = "Morena Baccarin",
        ["ping"] = new int?[] { 35, 16, null, 87, 240 }
    })
Current URL	Generated URL
scheme://host/?full%20name=David%20Krumholtz&ping=8&ping=300	scheme://host/?full%20name=Morena%20Baccarin&ping=35&ping=16&ping=87&ping=240
scheme://host/?ping=8&full%20name=David%20Krumholtz&ping=300	scheme://host/?ping=35&full%20name=Morena%20Baccarin&ping=16&ping=87&ping=240
scheme://host/?ping=8&ping=300&ping=50&ping=68&ping=42	scheme://host/?ping=35&ping=16&ping=87&ping=240&full%20name=Morena%20Baccarin
Navigate with an added or modified query string
To navigate with an added or modified query string, pass a generated URL to NavigateTo.

The following example calls:

GetUriWithQueryParameter to add or replace the name query parameter using a value of Morena Baccarin.
Calls NavigateTo to trigger navigation to the new URL.
C#

Copy
Navigation.NavigateTo(
    Navigation.GetUriWithQueryParameter("name", "Morena Baccarin"));
User interaction with <Navigating> content
If there's a significant delay during navigation, such as while lazy-loading assemblies in a Blazor WebAssembly app or for a slow network connection to a Blazor server-side app, the Router component can indicate to the user that a page transition is occurring.

At the top of the component that specifies the Router component, add an @using directive for the Microsoft.AspNetCore.Components.Routing namespace:

razor

Copy
@using Microsoft.AspNetCore.Components.Routing
Provide content to the Navigating parameter for display during page transition events.

In the router element (<Router>...</Router>) content:

razor

Copy
<Navigating>
    <p>Loading the requested page&hellip;</p>
</Navigating>
For an example that uses the Navigating property, see Lazy load assemblies in ASP.NET Core Blazor WebAssembly.

Handle asynchronous navigation events with OnNavigateAsync
The Router component supports an OnNavigateAsync feature. The OnNavigateAsync handler is invoked when the user:

Visits a route for the first time by navigating to it directly in their browser.
Navigates to a new route using a link or a NavigationManager.NavigateTo invocation.
razor

Copy
<Router AppAssembly="typeof(App).Assembly" 
    OnNavigateAsync="OnNavigateAsync">
    ...
</Router>

@code {
    private async Task OnNavigateAsync(NavigationContext args)
    {
        ...
    }
}
For an example that uses OnNavigateAsync, see Lazy load assemblies in ASP.NET Core Blazor WebAssembly.

When prerendering on the server, OnNavigateAsync is executed twice:

Once when the requested endpoint component is initially rendered statically.
A second time when the browser renders the endpoint component.
To prevent developer code in OnNavigateAsync from executing twice, the App component can store the NavigationContext for use in OnAfterRender{Async}, where firstRender can be checked. For more information, see Prerendering with JavaScript interop.

Handle cancellations in OnNavigateAsync
The NavigationContext object passed to the OnNavigateAsync callback contains a CancellationToken that's set when a new navigation event occurs. The OnNavigateAsync callback must throw when this cancellation token is set to avoid continuing to run the OnNavigateAsync callback on an outdated navigation.

If a user navigates to an endpoint but then immediately navigates to a new endpoint, the app shouldn't continue running the OnNavigateAsync callback for the first endpoint.

In the following example:

The cancellation token is passed in the call to PostAsJsonAsync, which can cancel the POST if the user navigates away from the /about endpoint.
The cancellation token is set during a product prefetch operation if the user navigates away from the /store endpoint.
razor

Copy
@inject HttpClient Http
@inject ProductCatalog Products

<Router AppAssembly="typeof(App).Assembly" 
    OnNavigateAsync="OnNavigateAsync">
    ...
</Router>

@code {
    private async Task OnNavigateAsync(NavigationContext context)
    {
        if (context.Path == "/about") 
        {
            var stats = new Stats { Page = "/about" };
            await Http.PostAsJsonAsync("api/visited", stats, 
                context.CancellationToken);
        }
        else if (context.Path == "/store")
        {
            var productIds = new[] { 345, 789, 135, 689 };

            foreach (var productId in productIds) 
            {
                context.CancellationToken.ThrowIfCancellationRequested();
                Products.Prefetch(productId);
            }
        }
    }
}
 Note

Not throwing if the cancellation token in NavigationContext is canceled can result in unintended behavior, such as rendering a component from a previous navigation.

Handle/prevent location changes
RegisterLocationChangingHandler registers a handler to process incoming navigation events. The handler's context provided by LocationChangingContext includes the following properties:

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 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 in the OnAfterRender{Async} lifecycle method. Navigation invokes all of the location changing handlers registered across the entire app (across multiple components), and any internal navigation executes them all in parallel. In addition to NavigateTo handlers are invoked:

When selecting internal links, which are links that point to URLs under the app's base path.
When navigating using the forward and back buttons in a browser.
Handlers are only executed for internal navigation within the app. If the user selects a link that navigates to a different site or changes the address bar to a different site manually, location changing handlers aren't executed.

Implement IDisposable and dispose registered handlers to unregister them. For more information, see ASP.NET Core Razor component lifecycle.

 Important

Don't attempt to execute DOM cleanup tasks via JavaScript (JS) interop when handling location changes. Use the MutationObserver pattern in JS on the client. For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

In the following example, a location changing handler is registered for navigation events.

NavHandler.razor:

razor

Copy
@page "/nav-handler"
@implements IDisposable
@inject NavigationManager Navigation

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

@code {
    private IDisposable? registration;

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            registration = 
                Navigation.RegisterLocationChangingHandler(OnLocationChanging);
        }
    }

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

        return ValueTask.CompletedTask;
    }

    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 navigation is 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 NavigateTo to control entries and state of the navigation history stack, see the Navigation options section.

For additional example code, see the NavigationManagerComponent in the BasicTestApp (dotnet/aspnetcore reference source).

 Note

Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

The NavigationLock component intercepts navigation events as long as it is rendered, effectively "locking" any given navigation until a decision is made to either proceed or cancel. Use NavigationLock when navigation interception can be scoped to the lifetime of a component.

NavigationLock parameters:

ConfirmExternalNavigation sets a browser dialog to prompt the user to either confirm or cancel external navigation. The default value is false. Displaying the confirmation dialog requires initial user interaction with the page before triggering external navigation with the URL in the browser's address bar. For more information on the interaction requirement, see Window: beforeunload event (MDN documentation).
OnBeforeInternalNavigation sets a callback for internal navigation events.
In the following NavLock component:

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.
PreventNavigation is called to prevent navigation from occurring if the user declines to confirm the navigation via a JavaScript (JS) interop call that spawns the JS confirm dialog.
NavLock.razor:

razor

Copy
@page "/nav-lock"
@inject IJSRuntime JSRuntime
@inject NavigationManager Navigation

<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()
    {
        Navigation.NavigateTo("/");
    }

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

        if (!isConfirmed)
        {
            context.PreventNavigation();
        }
    }
}
For additional example code, see the ConfigurableNavigationLock component in the BasicTestApp (dotnet/aspnetcore reference source).

NavLink component
Use a NavLink component in place of HTML hyperlink elements (<a>) when creating navigation links. A 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 NavLink.ActiveClass to apply a custom CSS class to the rendered link when the current route matches the href.

There are two NavLinkMatch options that you can assign to the Match attribute of the <NavLink> element:

NavLinkMatch.All: The NavLink is active when it matches the entire current URL.
NavLinkMatch.Prefix (default): The NavLink is active when it matches any prefix of the current URL.
In the preceding example, the Home NavLink href="" matches the home URL and only receives the active CSS class at the app's default base path (/). The second NavLink receives the active class when the user visits any URL with a component prefix (for example, /component and /component/another-segment).

Additional NavLink component attributes are passed through to the rendered anchor tag. In the following example, the NavLink component includes the target attribute:

razor

Copy
<NavLink href="example-page" target="_blank">Example page</NavLink>
The following HTML markup is rendered:

HTML

Copy
<a href="example-page" target="_blank">Example page</a>
 Warning

Due to the way that Blazor renders child content, rendering NavLink components inside a for loop requires a local index variable if the incrementing loop variable is used in the NavLink (child) component's content:

razor

Copy
@for (int c = 1; c < 4; c++)
{
    var ct = c;
    <li ...>
        <NavLink ...>
            <span ...></span> Product #@ct
        </NavLink>
    </li>
}
Using an index variable in this scenario is a requirement for any child component that uses a loop variable in its child content, not just the NavLink component.

Alternatively, use a foreach loop with Enumerable.Range:

razor

Copy
@foreach (var c in Enumerable.Range(1, 3))
{
    <li ...>
        <NavLink ...>
            <span ...></span> Product #@c
        </NavLink>
    </li>
}
NavLink component entries can be dynamically created from the app's components via reflection. The following example demonstrates the general approach for further customization.

For the following demonstration, a consistent, standard naming convention is used for the app's components:

Routable component file names use Pascal case†, for example Pages/ProductDetail.razor.
Routable component file paths match their URLs in kebab case‡ with hyphens appearing between words in a component's route template. For example, a ProductDetail component with a route template of /product-detail (@page "/product-detail") is requested in a browser at the relative URL /product-detail.
†Pascal case (upper camel case) is a naming convention without spaces and punctuation and with the first letter of each word capitalized, including the first word.
‡Kebab case is a naming convention without spaces and punctuation that uses lowercase letters and dashes between words.

In the Razor markup of the NavMenu component (NavMenu.razor) under the default Home page, NavLink components are added from a collection:

diff

Copy
<div class="nav-scrollable" 
    onclick="document.querySelector('.navbar-toggler').click()">
    <nav class="flex-column">
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="bi bi-house-door-fill-nav-menu" 
                    aria-hidden="true"></span> Home
            </NavLink>
        </div>

+       @foreach (var name in GetRoutableComponents())
+       {
+           <div class="nav-item px-3">
+               <NavLink class="nav-link" 
+                       href="@Regex.Replace(name, @"(\B[A-Z]|\d+)", "-$1").ToLower()">
+                   @Regex.Replace(name, @"(\B[A-Z]|\d+)", " $1")
+               </NavLink>
+           </div>
+       }

    </nav>
</div>
The GetRoutableComponents method in the @code block:

C#

Copy
public IEnumerable<string> GetRoutableComponents() => 
    Assembly.GetExecutingAssembly()
        .ExportedTypes
        .Where(t => t.IsSubclassOf(typeof(ComponentBase)))
        .Where(c => c.GetCustomAttributes(inherit: true)
                     .OfType<RouteAttribute>()
                     .Any())
        .Where(c => c.Name != "Home" && c.Name != "Error")
        .OrderBy(o => o.Name)
        .Select(c => c.Name);
The preceding example doesn't include the following pages in the rendered list of components:

Home page: The page is listed separately from the automatically generated links because it should appear at the top of the list and set the Match parameter.
Error page: The error page is only navigated to by the framework and shouldn't be listed.
ASP.NET Core endpoint routing integration
This section applies to Blazor Server apps.

Blazor Server is integrated into ASP.NET Core Endpoint Routing. An ASP.NET Core app is configured to accept incoming connections for interactive components with MapBlazorHub in the Program file:

C#

Copy
app.UseRouting();

app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
The typical configuration is to route all requests to a Razor page, which acts as the host for the server-side part of the Blazor Server app. By convention, the host page is usually named _Host.cshtml in the Pages folder of the app.

The route specified in the host file is called a fallback route because it operates with a low priority in route matching. The fallback route is used when other routes don't match. This allows the app to use other controllers and pages without interfering with component routing in the Blazor Server app.

For information on configuring MapFallbackToPage for non-root URL server hosting, see Host and deploy ASP.NET Core Blazor.

 Collaborate with us on GitHub
The source for this content can be found on GitHub, where you can also create and review issues and pull requests. For more information, see our contributor guide.

ASP.NET Core feedback

ASP.NET Core is an open source project. Select a link to provide feedback:

 Open a documentation issue
 Provide product feedback
Additional resources
Events


Nov 20, 2 AM - Nov 22, 2 AM

Join online sessions at Microsoft Ignite created to expand your skills and help you tackle today's complex issues.

Register now
Training

Module

Use pages, routing, and layouts to improve Blazor navigation - Training

Learn how to optimize your app's navigation, use parameters from the URL, and create reusable layouts in a Blazor web app.

Documentation

ASP.NET Core Blazor render modes

Learn about Blazor render modes and how to apply them in Blazor Web Apps.

ASP.NET Core Razor components

Learn how to create and use Razor components in Blazor apps, including guidance on Razor syntax, component naming, namespaces, and component parameters.

ASP.NET Core Blazor CSS isolation

Learn how CSS isolation scopes CSS to Razor components, which can simplify CSS and avoid collisions with other components or libraries.

Show 5 more
Previous Versions
Blog
Contribute
Privacy
Terms of Use
Trademarks
© Microsoft 2024
Tell us what you think! Your feedback is essential to us.

Overall, how satisfied or dissatisfied are you with Microsoft Learn?


why instead of returning my 404 page it just returns 404 error without even rendering layout