This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Note
Access to this page requires authorization. You can trysigning in orchanging directories.
Access to this page requires authorization. You can trychanging directories.
Note
This isn't the latest version of this article. For the current release, see the.NET 10 version of this article.
Warning
This version of ASP.NET Core is no longer supported. For more information, see the.NET and .NET Core Support Policy. For the current release, see the.NET 9 version of this article.
This article explains how to manage Blazor app request routing and how to use theNavLink component to create navigation links.
Important
Code examples throughout this article show methods called onNavigation, which is an injectedNavigationManager in classes and components.
This section applies to Blazor Web Apps.
Ifprerendering is enabled, the Blazor router (Router component,<Router> inRoutes.razor) performs static routing to components during static server-side rendering (static SSR). This type of routing is calledstatic routing.
When an interactive render mode is assigned to theRoutes component, the Blazor router becomes interactive after static SSR with static routing on the server. This type of routing is calledinteractive routing.
Static routers use endpoint routing and the HTTP request path to determine which component to render. When the router becomes interactive, it uses the document's URL (the URL in the browser's address bar) to determine which component to render. This means that the interactive router can dynamically change which component is rendered if the document's URL dynamically changes to another valid internal URL, and it can do so without performing an HTTP request to fetch new page content.
Interactive routing also prevents prerendering because new page content isn't requested from the server with a normal page request. For more information, seeASP.NET Core Blazor prerendered state persistence.
TheRouter component enables routing to Razor components and is located in the app'sRoutes component (Components/Routes.razor).
TheRouter component enables routing to Razor components. TheRouter component is used in theApp component (App.razor).
When a Razor component (.razor) with an@page directive is compiled, the generated component class is provided aRouteAttribute specifying the component's route template.
When the app starts, the assembly specified as the Router'sAppAssembly is scanned to gather route information for the app's components that have aRouteAttribute.
At runtime, theRouteView component:
Optionally specify aDefaultLayout parameter with a layout class for components that don't specify a layout with the@layout directive. The framework'sBlazor project templates specify theMainLayout component (MainLayout.razor) as the app's default layout. For more information on layouts, seeASP.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:
@page "/blazor-route"@page "/different-blazor-route"<PageTitle>Routing</PageTitle><h1>Routing Example</h1><p> This page is reached at either <code>/blazor-route</code> or <code>/different-blazor-route</code>.</p>@page "/blazor-route"@page "/different-blazor-route"<PageTitle>Routing</PageTitle><h1>Routing Example</h1><p> This page is reached at either <code>/blazor-route</code> or <code>/different-blazor-route</code>.</p>@page "/blazor-route"@page "/different-blazor-route"<h1>Blazor routing</h1>@page "/blazor-route"@page "/different-blazor-route"<h1>Blazor routing</h1>@page "/blazor-route"@page "/different-blazor-route"<h1>Blazor routing</h1>@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 thehref attribute. For more information, seeASP.NET Core Blazor app base path.
TheRouter doesn't interact with query string values. To work with query strings, see theQuery strings section.
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 inConstants.CounterRoute, which is set elsewhere in the app to "/counter":
- @page "/counter"+ @attribute [Route(Constants.CounterRoute)]Note
With the release of .NET 5.0.1 and for any additional 5.x releases, theRouter component includes thePreferExactMatches parameter set to@true. For more information, seeMigrate from ASP.NET Core 3.1 to .NET 5.
TheFocusOnNavigate component sets the UI focus to an element based on a CSS selector after navigating from one page to another.
<FocusOnNavigate RouteData="routeData" Selector="h1" />When theRouter component navigates to a new page, theFocusOnNavigate 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.
TheRouter component allows the app to specify custom content if content isn't found for the requested route.
Set custom content for theRouter component'sNotFound parameter:
<Router ...> ... <NotFound> ... </NotFound></Router>Arbitrary items are supported as content of theNotFound parameter, such as other interactive components. To apply a default layout toNotFound content, seeASP.NET Core Blazor layouts.
Blazor Web Apps don't use theNotFound parameter (<NotFound>...</NotFound> markup), but the parameter is supported† for backward compatibility in .NET 8/9 to avoid a breaking change in the framework. The server-side ASP.NET Core middleware pipeline processes requests on the server. Use server-side techniques to handle bad requests.
†Supported in this context means that placing<NotFound>...</NotFound> markup doesn't result in an exception, but using the markup isn't effective either.
For more information, see the following resources:
This section applies to Blazor Web Apps.
Use theRouter component'sAdditionalAssemblies parameter and the endpoint convention builderAddAdditionalAssemblies to discover routable components in additional assemblies. The following subsections explain when and how to use each API.
To discover routable components from additional assemblies for static server-side rendering (static SSR), even if the router later becomes interactive for interactive rendering, the assemblies must be disclosed to the Blazor framework. Call theAddAdditionalAssemblies method with the additional assemblies chained toMapRazorComponents in the server project'sProgram file.
The following example includes the routable components in theBlazorSample.Client project's assembly using the project's_Imports.razor file:
app.MapRazorComponents<App>() .AddAdditionalAssemblies(typeof(BlazorSample.Client._Imports).Assembly);Note
The preceding guidance also applies incomponent class library scenarios. Additional important guidance for class libraries and static SSR is found inASP.NET Core Razor class libraries (RCLs) with static server-side rendering (static SSR).
An interactive render mode can be assigned to theRoutes component (Routes.razor) that makes the Blazor router become interactive after static SSR and static routing on the server. For example,<Routes @rendermode="InteractiveServer" /> assigns interactive server-side rendering (interactive SSR) to theRoutes component. TheRouter component inherits interactive server-side rendering (interactive SSR) from theRoutes component. The router becomes interactive after static routing on the server.
Internal navigation for interactive routing doesn't involve requesting new page content from the server. Therefore, prerendering doesn't occur for internal page requests. For more information, seeASP.NET Core Blazor prerendered state persistence.
If theRoutes component is defined in the server project, theAdditionalAssemblies parameter of theRouter component should include the.Client project's assembly. This allows the router to work correctly when rendered interactively.
In the following example, theRoutes component is in the server project, and the_Imports.razor file of theBlazorSample.Client project indicates the assembly to search for routable components:
<Router AppAssembly="..." AdditionalAssemblies="[ typeof(BlazorSample.Client._Imports).Assembly ]"> ...</Router>Additional assemblies are scanned in addition to the assembly specified toAppAssembly.
Note
The preceding guidance also applies incomponent class library scenarios.
Alternatively, routable components only exist in the.Client project with global Interactive WebAssembly or Auto rendering applied, and theRoutes component is defined in the.Client project, not the server project. In this case, there aren't external assemblies with routable components, so it isn't necessary to specify a value forAdditionalAssemblies.
This section applies to Blazor Server apps.
Use theRouter component'sAdditionalAssemblies parameter and the endpoint convention builderAddAdditionalAssemblies to discover routable components in additional assemblies.
In the following example,Component1 is a routable component defined in a referencedcomponent class library namedComponentLibrary:
<Router AppAssembly="..." AdditionalAssemblies="new[] { typeof(ComponentLibrary.Component1).Assembly }"> ...</Router>Additional assemblies are scanned in addition to the assembly specified toAppAssembly.
The router uses route parameters to populate the correspondingcomponent parameters with the same name. Route parameter names are case insensitive. In the following example, thetext parameter assigns the value of the route segment to the component'sText property. When a request is made for/route-parameter-1/amazing, the content is rendered asBlazor is amazing!.
RouteParameter1.razor:
@page "/route-parameter-1/{text}"<PageTitle>Route Parameter 1</PageTitle><h1>Route Parameter Example 1</h1><p>Blazor is @Text!</p>@code { [Parameter] public string? Text { get; set; }}@page "/route-parameter-1/{text}"<PageTitle>Route Parameter 1</PageTitle><h1>Route Parameter Example 1</h1><p>Blazor is @Text!</p>@code { [Parameter] public string? Text { get; set; }}@page "/route-parameter-1/{text}"<h1>Blazor is @Text!</h1>@code { [Parameter] public string? Text { get; set; }}@page "/route-parameter-1/{text}"<h1>Blazor is @Text!</h1>@code { [Parameter] public string? Text { get; set; }}@page "/route-parameter-1/{text}"<h1>Blazor is @Text!</h1>@code { [Parameter] public string Text { get; set; }}@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, thetext optional parameter assigns the value of the route segment to the component'sText property. If the segment isn't present, the value ofText is set tofantastic.
Optional parameters aren't supported. In the following example, two@page directives are applied. The first directive permits navigation to the component without a parameter. The second directive assigns the{text} route parameter value to the component'sText property.
RouteParameter2.razor:
@page "/route-parameter-2/{text?}"<PageTitle>Route Parameter 2</PageTitle><h1>Route Parameter Example 2</h1><p>Blazor is @Text!</p>@code { [Parameter] public string? Text { get; set; } protected override void OnParametersSet() => Text = Text ?? "fantastic";}@page "/route-parameter-2/{text?}"<PageTitle>Route Parameter 2</PageTitle><h1>Route Parameter Example 2</h1><p>Blazor is @Text!</p>@code { [Parameter] public string? Text { get; set; } protected override void OnParametersSet() => Text = Text ?? "fantastic";}@page "/route-parameter-2/{text?}"<h1>Blazor is @Text!</h1>@code { [Parameter] public string? Text { get; set; } protected override void OnParametersSet() { Text = Text ?? "fantastic"; }}@page "/route-parameter-2/{text?}"<h1>Blazor is @Text!</h1>@code { [Parameter] public string? Text { get; set; } protected override void OnParametersSet() { Text = Text ?? "fantastic"; }}@page "/route-parameter-2/{text?}"<h1>Blazor is @Text!</h1>@code { [Parameter] public string Text { get; set; } protected override void OnParametersSet() { Text = Text ?? "fantastic"; }}@page "/route-parameter-2"@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 theOnInitialized{Async} lifecycle method is used instead of theOnParametersSet{Async} lifecycle method, the default assignment of theText property tofantastic 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, theOnInitialized method isn't invoked again.
Note
Route parameters don't work with query string values. To work with query strings, see theQuery strings section.
A route constraint enforces type matching on a route segment to a component.
In the following example, the route to theUser component only matches if:
Id route segment is present in the request URL.Id segment is an integer (int) type.User.razor:
@page "/user/{Id:int}"<PageTitle>User</PageTitle><h1>User Example</h1><p>User Id: @Id</p>@code { [Parameter] public int Id { get; set; }}Note
Route constraints don't work with query string values. To work with query strings, see theQuery strings section.
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} | NotBlazorSample.styles.css, notfavicon.ico | Yes |
Warning
Route constraints that verify the URL and are converted to a CLR type (such asint orDateTime) always use the invariant culture. These constraints assume that the URL is non-localizable.
Route constraints also work withoptional parameters. In the following example,Id is required, butOption is an optional boolean route parameter.
User.razor:
@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; }}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:
@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:
@page "/{optional:nonfile?}"Aserver-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 namedsome.thing. Without additional configuration, an app returns a404 - Not Found response ifsome.thing was meant to route to a component with an@page directive andsome.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 followingExample component that can receive a route parameter from the last segment of the URL.
Example.razor:
@page "/example/{param?}"<p> Param: @Param</p>@code { [Parameter] public string? Param { get; set; }}@page "/example/{param?}"<p> Param: @Param</p>@code { [Parameter] public string? Param { get; set; }}@page "/example/{param?}"<p> Param: @Param</p>@code { [Parameter] public string Param { get; set; }}@page "/example"@page "/example/{param}"<p> Param: @Param</p>@code { [Parameter] public string Param { get; set; }}To permit theServer app of a hosted Blazor WebAssemblysolution to route the request with a dot in theparam route parameter, add a fallback file route template with the optional parameter in theProgram file:
app.MapFallbackToFile("/example/{param?}", "index.html");To configure a Blazor Server app to route the request with a dot in theparam route parameter, add a fallback page route template with the optional parameter in theProgram file:
app.MapFallbackToPage("/example/{param?}", "/_Host");For more information, seeRouting in ASP.NET Core.
To permit theServer app of a hosted Blazor WebAssemblysolution to route the request with a dot in theparam route parameter, add a fallback file route template with the optional parameter inStartup.Configure.
Startup.cs:
endpoints.MapFallbackToFile("/example/{param?}", "index.html");To configure a Blazor Server app to route the request with a dot in theparam route parameter, add a fallback page route template with the optional parameter inStartup.Configure.
Startup.cs:
endpoints.MapFallbackToPage("/example/{param?}", "/_Host");For more information, seeRouting in ASP.NET Core.
Catch-all route parameters, which capture paths across multiple folder boundaries, are supported in components.
Catch-all route parameters are:
string type. The framework doesn't provide automatic casting.CatchAll.razor:
@page "/catch-all/{*pageRoute}"<PageTitle>Catch All</PageTitle><h1>Catch All Parameters Example</h1><p>Add some URI segments to the route and request the page again.</p><p> PageRoute: @PageRoute</p>@code { [Parameter] public string? PageRoute { get; set; }}@page "/catch-all/{*pageRoute}"<PageTitle>Catch All</PageTitle><h1>Catch All Parameters Example</h1><p>Add some URI segments to the route and request the page again.</p><p> PageRoute: @PageRoute</p>@code { [Parameter] public string? PageRoute { get; set; }}@page "/catch-all/{*pageRoute}"@code { [Parameter] public string? PageRoute { get; set; }}@page "/catch-all/{*pageRoute}"@code { [Parameter] public string? PageRoute { get; set; }}@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 ofPageRoute is set tothis/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 yieldsthis/is/a/test*.
UseNavigationManager 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 thehref attribute on the document's<base> element (location of<head> content). |
| NavigateTo | Navigates to the specified URI. IfforceLoad isfalse:
forceLoad istrue:
For more information, see theEnhanced navigation and form handling section. If |
| LocationChanged | An event that fires when the navigation location has changed. For more information, see theLocation changes section. |
NotFound | Called to handle scenarios where a requested resource isn't found. For more information, see theNot Found responses 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 theProduce a URI relative to the base URI prefix section. |
RegisterLocationChangingHandler | Registers a handler to process incoming navigation events. CallingNavigateTo always invokes the handler. |
| GetUriWithQueryParameter | Returns a URI constructed by updatingNavigationManager.Uri with a single parameter added, updated, or removed. For more information, see theQuery strings section. |
| 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 thehref attribute on the document's<base> element (location of<head> content). |
| NavigateTo | Navigates to the specified URI. IfforceLoad isfalse:
forceLoad istrue:
For more information, see theEnhanced navigation and form handling section. If |
| LocationChanged | An event that fires when the navigation location has changed. For more information, see theLocation 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 theProduce a URI relative to the base URI prefix section. |
RegisterLocationChangingHandler | Registers a handler to process incoming navigation events. CallingNavigateTo always invokes the handler. |
| GetUriWithQueryParameter | Returns a URI constructed by updatingNavigationManager.Uri with a single parameter added, updated, or removed. For more information, see theQuery strings section. |
| 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 thehref attribute on the document's<base> element (location of<head> content). |
| NavigateTo | Navigates to the specified URI. IfforceLoad istrue:
replace istrue, 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 theLocation 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 theProduce a URI relative to the base URI prefix section. |
RegisterLocationChangingHandler | Registers a handler to process incoming navigation events. CallingNavigateTo always invokes the handler. |
| GetUriWithQueryParameter | Returns a URI constructed by updatingNavigationManager.Uri with a single parameter added, updated, or removed. For more information, see theQuery strings section. |
| 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 thehref attribute on the document's<base> element (location of<head> content). |
| NavigateTo | Navigates to the specified URI. IfforceLoad istrue:
replace istrue, 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 theLocation 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 theProduce a URI relative to the base URI prefix section. |
| GetUriWithQueryParameter | Returns a URI constructed by updatingNavigationManager.Uri with a single parameter added, updated, or removed. For more information, see theQuery strings section. |
| 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 thehref attribute on the document's<base> element (location of<head> content). |
| NavigateTo | Navigates to the specified URI. IfforceLoad istrue:
|
| LocationChanged | An event that fires when the navigation location has changed. |
| 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 theProduce a URI relative to the base URI prefix section. |
For theLocationChanged event,LocationChangedEventArgs provides the following information about navigation events:
true, Blazor intercepted the navigation from the browser. Iffalse,NavigationManager.NavigateTo caused the navigation to occur.The following component:
Counter component (Counter.razor) when the button is selected usingNavigateTo.TheHandleLocationChanged method is unhooked whenDispose 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:
@page "/navigate"@implements IDisposable@inject ILogger<Navigate> Logger@inject NavigationManager Navigation<PageTitle>Navigate</PageTitle><h1>Navigate Example</h1><button @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;}@page "/navigate"@implements IDisposable@inject ILogger<Navigate> Logger@inject NavigationManager Navigation<PageTitle>Navigate</PageTitle><h1>Navigate Example</h1><button @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;}@page "/navigate"@using Microsoft.Extensions.Logging @implements IDisposable@inject ILogger<Navigate> Logger@inject NavigationManager Navigation<h1>Navigate in component code example</h1><button @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; }}@page "/navigate"@using Microsoft.Extensions.Logging @implements IDisposable@inject ILogger<Navigate> Logger@inject NavigationManager Navigation<h1>Navigate in component code example</h1><button @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; }}@page "/navigate"@using Microsoft.Extensions.Logging @implements IDisposable@inject ILogger<Navigate> Logger@inject NavigationManager Navigation<h1>Navigate in component code example</h1><button @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; }}@page "/navigate"@using Microsoft.Extensions.Logging @implements IDisposable@inject ILogger<Navigate> Logger@inject NavigationManager Navigation<h1>Navigate in component code example</h1><button @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, seeASP.NET Core Razor component disposal.
For a redirect during static server-side rendering (static SSR),NavigationManager relies on throwing aNavigationException that gets captured by the framework, which converts the error into a redirect. Code that exists after the call toNavigateTo isn't called. When using Visual Studio, the debugger breaks on the exception, requiring you to deselect the checkbox forBreak when this exception type is user-handled in the Visual Studio UI to avoid the debugger stopping for future redirects.
You can use the<BlazorDisableThrowNavigationException> MSBuild property set totrue in the app's project file to opt-in to no longer throwing aNavigationException. Also, code after the call toNavigateTo executes when it wouldn't have run before. This behavior is enabled by default in the .NET 10 or later Blazor Web App project template:
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>Note
In .NET 10 or later, you can opt-in to not throwing aNavigationException by setting the<BlazorDisableThrowNavigationException> MSBuild property totrue in the app's project file. To take advantage of the new MSBuild property and behavior, upgrade the app to .NET 10 or later.
NavigationManager provides aNotFound method to handle scenarios where a requested resource isn't found during static server-side rendering (static SSR) or global interactive rendering:
Static SSR: CallingNotFound sets the HTTP status code to 404.
Interactive rendering: Signals the Blazor router (Router component) to render Not Found content.
Streaming rendering: Ifenhanced navigation is active,streaming rendering renders Not Found content without reloading the page. When enhanced navigation is blocked, the framework redirects to Not Found content with a page refresh.
Note
The following discussion mentions that a Not Found Razor component can be assigned to theRouter component'sNotFoundPage parameter. The parameter works in concert withNavigationManager.NotFound and is described in more detail later in this section.
Streaming rendering can only render components that have a route, such as aNotFoundPage assignment (NotFoundPage="...") or aStatus Code Pages Re-execution Middleware page assignment (UseStatusCodePagesWithReExecute).DefaultNotFound 404 content ("Not found" plain text) doesn't have a route, so it can't be used during streaming rendering.
Note
The Not Found render fragment (<NotFound>...</NotFound>) isn't supported in .NET 10 or later.
NavigationManager.NotFound content rendering uses the following, regardless if the response has started or not (in order):
Router.NotFoundPage is set, render the assigned page.Status Code Pages Re-execution Middleware withUseStatusCodePagesWithReExecute takes precedence for browser-based address routing problems, such as an incorrect URL typed into the browser's address bar or selecting a link that has no endpoint in the app.
When a component is rendered statically (static SSR) andNavigationManager.NotFound is called, the 404 status code is set on the response:
@page "/render-not-found-ssr"@inject NavigationManager Navigation@code { protected override void OnInitialized() { Navigation.NotFound(); }}To provide Not Found content for global interactive rendering, use a Not Found page (Razor component).
Note
The Blazor project template includes aNotFound.razor page by default. This page automatically renders wheneverNavigationManager.NotFound is called, making it easier to handle missing routes with a consistent user experience.
NotFound.razor:
<h1>Not Found</h1><p>Sorry! Nothing to show.</p>Assign theNotFound component to the router'sNotFoundPage parameter.NotFoundPage supports routing that can be used across Status Code Pages Re-execution Middleware, including non-Blazor middleware. If theNotFound render fragment (<NotFound>...</NotFound>) is defined together withNotFoundPage, the page has higher priority.
In the following example, the precedingNotFound component is present in the app'sPages folder and passed to theNotFoundPage parameter:
<Router AppAssembly="@typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)"> <Found Context="routeData"> <RouteView RouteData="@routeData" /> <FocusOnNavigate RouteData="@routeData" Selector="h1" /> </Found></Router>When a component is rendered with a global interactive render mode, callingNotFound signals the Blazor router to render theNotFound component:
@page "/render-not-found-interactive"@inject NavigationManager Navigation@if (RendererInfo.IsInteractive){ <button @onclick="TriggerNotFound">Trigger Not Found</button>}@code { private void TriggerNotFound() { Navigation.NotFound(); }}You can use theOnNotFound event for notifications whenNotFound is invoked. The event is only fired whenNotFound is called, not for any 404 response. For example, settingHttpContextAccessor.HttpContext.Response.StatusCode to404 doesn't triggerNotFound/OnNotFound.
Apps that implement a custom router can also useNavigationManager.NotFound. The custom router can render Not Found content from two sources, depending on the state of the response:
Regardless of the response state, the re-execution path to the page can used by passing it toUseStatusCodePagesWithReExecute:
app.UseStatusCodePagesWithReExecute( "/not-found", createScopeForStatusCodePages: true);When the response has started, theNotFoundEventArgs.Path can be used by subscribing to theOnNotFoundEvent in the router:
@code { [CascadingParameter] private HttpContext? HttpContext { get; set; } private void OnNotFoundEvent(object sender, NotFoundEventArgs e) { // Only execute the logic if HTTP response has started, // because setting NotFoundEventArgs.Path blocks re-execution if (HttpContext?.Response.HasStarted == false) { return; } var type = typeof(CustomNotFoundPage); var routeAttributes = type.GetCustomAttributes<RouteAttribute>(inherit: true); if (routeAttributes.Length == 0) { throw new InvalidOperationException($"The type {type.FullName} " + $"doesn't have a {nameof(RouteAttribute)} applied."); } var routeAttribute = (RouteAttribute)routeAttributes[0]; if (routeAttribute.Template != null) { e.Path = routeAttribute.Template; } }}In the following example components:
NotFoundContext service is injected, along with theNavigationManager.HandleNotFound is an event handler assigned to theOnNotFound event.HandleNotFound callsNotFoundContext.UpdateContext to set a heading and message for Not Found content that's displayed by theRouter component in theRoutes component (Routes.razor).null) to simulate what happens when an entity isn't found.NavigationManager.NotFound is called, which in turn triggers theOnNotFound event and theHandleNotFound event handler. Not Found content is displayed by the router.HandleNotFound method is unhooked on component disposal inIDisposable.Dispose.Movie component (Movie.razor):
@page "/movie/{Id:int}"@implements IDisposable@inject NavigationManager NavigationManager@inject NotFoundContext NotFoundContext<div> No matter what ID is used, no matching movie is returned from the call to GetMovie().</div>@code { [Parameter] public int Id { get; set; } protected override async Task OnInitializedAsync() { NavigationManager.OnNotFound += HandleNotFound; var movie = await GetMovie(Id); if (movie == null) { NavigationManager.NotFound(); } } private void HandleNotFound(object? sender, NotFoundEventArgs e) { NotFoundContext.UpdateContext("Movie Not Found", "Sorry! The requested movie wasn't found."); } private async Task<MovieItem[]?> GetMovie(int id) { // Simulate no movie with matching id found return await Task.FromResult<MovieItem[]?>(null); } void IDisposable.Dispose() { NavigationManager.OnNotFound -= HandleNotFound; } public class MovieItem { public int Id { get; set; } public string? Title { get; set; } }}User component (User.razor):
@page "/user/{Id:int}"@implements IDisposable@inject NavigationManager NavigationManager@inject NotFoundContext NotFoundContext<div> No matter what ID is used, no matching user is returned from the call to GetUser().</div>@code { [Parameter] public int Id { get; set; } protected override async Task OnInitializedAsync() { NavigationManager.OnNotFound += HandleNotFound; var user = await GetUser(Id); if (user == null) { NavigationManager.NotFound(); } } private void HandleNotFound(object? sender, NotFoundEventArgs e) { NotFoundContext.UpdateContext("User Not Found", "Sorry! The requested user wasn't found."); } private async Task<UserItem[]?> GetUser(int id) { // Simulate no user with matching id found return await Task.FromResult<UserItem[]?>(null); } void IDisposable.Dispose() { NavigationManager.OnNotFound -= HandleNotFound; } public class UserItem { public int Id { get; set; } public string? Name { get; set; } }}To reach the preceding components in a local demonstration with a test app, create entries in theNavMenu component (NavMenu.razor) to reach theMovie andUser components. The entity IDs, passed as route parameters, in the following example are mock values that have no effect because they aren't actually used by the components, which simulate not finding a movie or user.
InNavMenu.razor:
<div> <NavLink href="movie/1"> <span aria-hidden="true"></span> Movie </NavLink></div><div> <NavLink href="user/2"> <span aria-hidden="true"></span> User </NavLink></div>This section applies to Blazor Web Apps.
Blazor Web Apps are capable of two types of routing for page navigation and form handling requests:
fetch request instead. Blazor then patches the response content into the page's DOM. Blazor's enhanced navigation and form handling avoid the need for a full-page reload and preserves more of the page state, so pages load faster, usually without losing the user's scroll position on the page.Enhanced navigation is available when:
blazor.web.js) is used, not the Blazor Server script (blazor.server.js) or Blazor WebAssembly script (blazor.webassembly.js).data-enhance-nav attribute set tofalse.If server-side routing and enhanced navigation are enabled,location changing handlers are only invoked for programmatic navigation initiated from an interactive runtime. In future releases, additional types of navigation, such as following a link, may also invoke location changing handlers.
When an enhanced navigation occurs,LocationChanged event handlers registered with Interactive Server and WebAssembly runtimes are typically invoked. There are cases when location changing handlers might not intercept an enhanced navigation. For example, the user might switch to another page before an interactive runtime becomes available. Therefore, it's important that app logic not rely on invoking a location changing handler, as there's no guarantee of the handler executing.
When callingNavigateTo:
forceLoad isfalse, which is the default:forceLoad istrue: Blazor performs a full-page reload for the requested URL, whether enhanced navigation is available or not.You can refresh the current page by callingNavigationManager.Refresh(bool forceLoad = false), which always performs an enhanced navigation, if available. If enhanced navigation isn't available, Blazor performs a full-page reload.
Navigation.Refresh();Passtrue to theforceLoad parameter to ensure a full-page reload is always performed, even if enhanced navigation is available:
Navigation.Refresh(true);Enhanced navigation is enabled by default, but it can be controlled hierarchically and on a per-link basis using thedata-enhance-nav HTML attribute.
The following examples disable enhanced navigation:
<a href="redirect" data-enhance-nav="false"> GET without enhanced navigation</a><ul data-enhance-nav="false"> <li> <a href="redirect">GET without enhanced navigation</a> </li> <li> <a href="redirect-2">GET without enhanced navigation</a> </li></ul>If the destination is a non-Blazor endpoint, enhanced navigation doesn't apply, and the client-side JavaScript retries as a full page load. This ensures no confusion to the framework about external pages that shouldn't be patched into an existing page.
To enable enhanced form handling, add theEnhance parameter toEditForm forms or thedata-enhance attribute to HTML forms (<form>):
<EditForm ... Enhance ...> ...</EditForm><form ... data-enhance ...> ...</form>Enhanced form handling isn't hierarchical and doesn't flow to child forms:
Unsupported: You can't set enhanced navigation on a form's ancestor element to enable enhanced navigation for the form.
<div ... data-enhance ...> <form ...> <!-- NOT enhanced --> </form></div>Enhanced form posts only work with Blazor endpoints. Posting an enhanced form to non-Blazor endpoint results in an error.
To disable enhanced navigation:
false:Enhance="false").<form>, remove thedata-enhance attribute from form element (or set it tofalse:data-enhance="false").Blazor's enhanced navigation and form handing may undo dynamic changes to the DOM if the updated content isn't part of the server rendering. To preserve the content of an element, use thedata-permanent attribute.
In the following example, the content of the<div> element is updated dynamically by a script when the page loads:
<div data-permanent> ...</div>Once Blazor has started on the client, you can use theenhancedload event to listen for enhanced page updates. This allows for re-applying changes to the DOM that may have been undone by an enhanced page update.
Blazor.addEventListener('enhancedload', () => console.log('Enhanced update!'));To disable enhanced navigation and form handling globally, seeASP.NET Core Blazor startup.
Enhanced navigation withstatic server-side rendering (static SSR) requires special attention when loading JavaScript. For more information, seeASP.NET Core Blazor JavaScript with static server-side rendering (static SSR).
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:
try{ baseRelativePath = Navigation.ToBaseRelativePath(inputURI);}catch (ArgumentException ex){ ...}If the base URI of the app ishttps://localhost:8000, the following results are obtained:
https://localhost:8000/segment ininputURI results in abaseRelativePath ofsegment.https://localhost:8000/segment1/segment2 ininputURI results in abaseRelativePath ofsegment1/segment2.If the base URI of the app doesn't match the base URI ofinputURI, anArgumentException is thrown.
Passinghttps://localhost:8001/segment ininputURI results in the following exception:
System.ArgumentException: 'The URI 'https://localhost:8001/segment' is not contained by the base URI 'https://localhost:8000/'.'
TheNavigationManager uses the browser'sHistory 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 whenauthenticating users with external identity providers. For more information, see theNavigation options section.
PassNavigationOptions toNavigateTo to control the following behaviors:
false.false, append the new entry to the history stack. The default value isfalse.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 theHandle/prevent location changes section.
Use the[SupplyParameterFromQuery] attribute to specify that a component parameter comes from the query string.
Use the[SupplyParameterFromQuery] attribute with the[Parameter] attribute to specify that a component parameter of aroutable 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.The correct culture-invariant formatting is applied for the given type (CultureInfo.InvariantCulture).
Specify the[SupplyParameterFromQuery] attribute'sName 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:
Unlike component parameter properties ([Parameter]),[SupplyParameterFromQuery] properties can be markedprivate in addition topublic.
[SupplyParameterFromQuery(Name = "{QUERY PARAMETER NAME}")]private string? {COMPONENT PARAMETER NAME} { get; set; }Just like component parameter properties ([Parameter]),[SupplyParameterFromQuery] properties are alwayspublic properties in .NET 6/7. In .NET 8 or later,[SupplyParameterFromQuery] properties can be markedpublic orprivate.
[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:
Filter property resolves toscifi stars.Page property resolves to3.Stars array is filled from query parameters namedstar (Name = "star") and resolves toLeVar Burton andGary Oldman.Note
The query string parameters in the following routable page component also work in anon-routable component without an@page directive (for example,Search.razor for a sharedSearch component used in other components).
Search.razor:
@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 { [SupplyParameterFromQuery] private string? Filter { get; set; } [SupplyParameterFromQuery] private int? Page { get; set; } [SupplyParameterFromQuery(Name = "star")] private string[]? Stars { get; set; }}Search.razor:
@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; }}UseGetUriWithQueryParameter to add, change, or remove one or more query parameters on the current URL:
@inject NavigationManager Navigation...Navigation.GetUriWithQueryParameter("{NAME}", {VALUE})For the preceding example:
{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.null.CallGetUriWithQueryParameters to create a URI constructed fromUri with multiple parameters added, updated, or removed. For each value, the framework usesvalue?.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.
@inject NavigationManager Navigation...Navigation.GetUriWithQueryParameters({PARAMETERS})The{PARAMETERS} placeholder is anIReadOnlyDictionary<string, object>.
Pass a URI string toGetUriWithQueryParameters to generate a new URI from a provided URI with multiple parameters added, updated, or removed. For each value, the framework usesvalue?.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.
@inject NavigationManager Navigation...Navigation.GetUriWithQueryParameters("{URI}", {PARAMETERS}){URI} placeholder is the URI with or without a query string.{PARAMETERS} placeholder is anIReadOnlyDictionary<string, object>.Supported types are identical to supported types for route constraints:
boolDateOnlyDateTimedecimaldoublefloatGuidintlongstringTimeOnlySupported types include:
Warning
With compression, which is enabled by default, avoid creating secure (authenticated/authorized) interactive server-side components that render data from untrusted sources. Untrusted sources include route parameters, query strings, data from JS interop, and any other source of data that a third-party user can control (databases, external services). For more information, seeASP.NET Core Blazor SignalR guidance andThreat mitigation guidance for ASP.NET Core Blazor interactive server-side rendering.
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 |
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 |
nullNavigation.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/ |
In the following example:
name is removed, if present.age is added with a value of25 (int), if not present. If present,age is updated to a value of25.eye color is added or updated to a value ofgreen.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 |
In the following example:
full name is added or updated toMorena Baccarin, a single value.ping parameters are added or replaced with35,16,87 and240.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 |
To navigate with an added or modified query string, pass a generated URL toNavigateTo.
The following example calls:
name query parameter using a value ofMorena Baccarin.Navigation.NavigateTo( Navigation.GetUriWithQueryParameter("name", "Morena Baccarin"));The query string of a request is obtained from theNavigationManager.Uri property:
@inject NavigationManager Navigation...var query = new Uri(Navigation.Uri).Query;To parse a query string's parameters, one approach is to useURLSearchParams withJavaScript (JS) interop:
export createQueryString = (string queryString) => new URLSearchParams(queryString);For more information on JavaScript isolation with JavaScript modules, seeCall JavaScript functions from .NET methods in ASP.NET Core Blazor.
Navigate to a named element using the following approaches with a hashed (#) reference to the element. Routes to elements within the component and routes to elements in external components use root-relative paths. A leading forward slash (/) is optional.
Examples for each of the following approaches demonstrate navigation to an element with anid oftargetElement in theCounter component:
Anchor element (<a>) with anhref:
<a href="/counter#targetElement">NavLink component with anhref:
<NavLink href="/counter#targetElement">NavigationManager.NavigateTo passing the relative URL:
Navigation.NavigateTo("/counter#targetElement");The following example demonstrates hashed routing to named H2 headings within a component and to external components.
In theHome (Home.razor) andCounter (Counter.razor) components, place the following markup at the bottoms of the existing component markup to serve as navigation targets. The<div> creates artificial vertical space to demonstrate browser scrolling behavior:
<div></div><h2>Target H2 heading</h2><p>Content!</p>Add the followingHashedRouting component to the app.
HashedRouting.razor:
@page "/hashed-routing"@inject NavigationManager Navigation<PageTitle>Hashed routing</PageTitle><h1>Hashed routing to named elements</h1><ul> <li> <a href="/hashed-routing#targetElement"> Anchor in this component </a> </li> <li> <a href="/#targetElement"> Anchor to the <code>Home</code> component </a> </li> <li> <a href="/counter#targetElement"> Anchor to the <code>Counter</code> component </a> </li> <li> <NavLink href="/hashed-routing#targetElement"> Use a `NavLink` component in this component </NavLink> </li> <li> <button @onclick="NavigateToElement"> Navigate with <code>NavigationManager</code> to the <code>Counter</code> component </button> </li></ul><div></div><h2>Target H2 heading</h2><p>Content!</p>@code { private void NavigateToElement() { Navigation.NavigateTo("/counter#targetElement"); }}<Navigating> contentIf there's a significant delay during navigation, such as whilelazy-loading assemblies in a Blazor WebAssembly app or for a slow network connection to a Blazor server-side app, theRouter component can indicate to the user that a page transition is occurring.
At the top of the component that specifies theRouter component, add an@using directive for theMicrosoft.AspNetCore.Components.Routing namespace:
@using Microsoft.AspNetCore.Components.RoutingProvide content to theNavigating parameter for display during page transition events.
In the router element (<Router>...</Router>) content:
<Navigating> <p>Loading the requested page…</p></Navigating>For an example that uses theNavigating property, seeLazy load assemblies in ASP.NET Core Blazor WebAssembly.
OnNavigateAsyncTheRouter component supports anOnNavigateAsync feature. TheOnNavigateAsync handler is invoked when the user:
<Router AppAssembly="typeof(App).Assembly" OnNavigateAsync="OnNavigateAsync"> ...</Router>@code { private async Task OnNavigateAsync(NavigationContext args) { ... }}<Router AppAssembly="typeof(Program).Assembly" OnNavigateAsync="OnNavigateAsync"> ...</Router>@code { private async Task OnNavigateAsync(NavigationContext args) { ... }}For an example that usesOnNavigateAsync, seeLazy load assemblies in ASP.NET Core Blazor WebAssembly.
When prerendering on the server,OnNavigateAsync is executedtwice:
To prevent developer code inOnNavigateAsync from executing twice, theRoutes component can store theNavigationContext for use in theOnAfterRender{Async} lifecycle method, wherefirstRender can be checked. For more information, seePrerendering with JavaScript interop.
To prevent developer code inOnNavigateAsync from executing twice, theApp component can store theNavigationContext for use inOnAfterRender{Async}, wherefirstRender can be checked. For more information, seePrerendering with JavaScript interop.
OnNavigateAsyncTheNavigationContext object passed to theOnNavigateAsync callback contains aCancellationToken that's set when a new navigation event occurs. TheOnNavigateAsync callback must throw when this cancellation token is set to avoid continuing to run theOnNavigateAsync 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 theOnNavigateAsync callback for the first endpoint.
In the following example:
PostAsJsonAsync, which can cancel the POST if the user navigates away from the/about endpoint./store endpoint.@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); } } }}@inject HttpClient Http@inject ProductCatalog Products<Router AppAssembly="typeof(Program).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 inNavigationContext is canceled can result in unintended behavior, such as rendering a component from a previous navigation.
RegisterLocationChangingHandler registers a handler to process incoming navigation events. The handler's context provided byLocationChangingContext includes the following properties:
A component can register multiple location changing handlers in theOnAfterRender{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 toNavigateTo handlers are invoked:
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.
ImplementIDisposable and dispose registered handlers to unregister them. For more information, seeASP.NET Core Razor component disposal.
Important
Don't attempt to execute DOM cleanup tasks via JavaScript (JS) interop when handling location changes. Use theMutationObserver pattern in JS on the client. For more information, seeASP.NET Core Blazor JavaScript interoperability (JS interop).
In the following example, a location changing handler is registered for navigation events.
NavHandler.razor:
@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:
For more information on passingNavigationOptions toNavigateTo to control entries and state of the navigation history stack, see theNavigation options section.
For additional example code, see theNavigationManagerComponent in theBasicTestApp (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 theSwitch branches or tags dropdown list. For more information, seeHow to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).
TheNavigationLock 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. UseNavigationLock when navigation interception can be scoped to the lifetime of a component.
NavigationLock parameters:
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, seeWindow:beforeunload event.In the followingNavLock component:
https://www.microsoft.com succeeds.confirm dialog.NavLock.razor:
@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 theConfigurableNavigationLock component in theBasicTestApp (dotnet/aspnetcore reference source).
NavLink componentUse aNavLink component in place of HTML hyperlink elements (<a>) when creating navigation links. ANavLink component behaves like an<a> element, except it toggles anactive CSS class based on whether itshref matches the current URL. Theactive class helps a user understand which page is the active page among the navigation links displayed. Optionally, assign a CSS class name toNavLink.ActiveClass to apply a custom CSS class to the rendered link when the current route matches thehref.
There are twoNavLinkMatch options that you can assign to theMatch attribute of the<NavLink> element:
Microsoft.AspNetCore.Components.Routing.NavLink.EnableMatchAllForQueryStringAndFragmentAppContext switch set totrue.There are twoNavLinkMatch options that you can assign to theMatch attribute of the<NavLink> element:
In the preceding example, the HomeNavLinkhref="" matches the home URL and only receives theactive CSS class at the app's default base path (/). The secondNavLink receives theactive class when the user visits any URL with acomponent prefix (for example,/component and/component/another-segment).
To adopt custom matching logic, subclassNavLink and override itsShouldMatch method. Returntrue from the method when you want to apply theactive CSS class:
public class CustomNavLink : NavLink{ protected override bool ShouldMatch(string currentUriAbsolute) { // Custom matching logic }}AdditionalNavLink component attributes are passed through to the rendered anchor tag. In the following example, theNavLink component includes thetarget attribute:
<NavLink href="example-page">Example page</NavLink>The following HTML markup is rendered:
<a href="example-page">Example page</a>Warning
Due to the way that Blazor renders child content, renderingNavLink components inside afor loop requires a local index variable if the incrementing loop variable is used in theNavLink (child) component's content:
@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 forany child component that uses a loop variable in itschild content, not just theNavLink component.
Alternatively, use aforeach loop withEnumerable.Range:
@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:
Pages/ProductDetail.razor.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 theNavMenu component (NavMenu.razor) under the defaultHome page,NavLink components are added from a collection:
<div > <nav> <div> <NavLink href="" Match="NavLinkMatch.All"> <span aria-hidden="true"></span> Home </NavLink> </div>+ @foreach (var name in GetRoutableComponents())+ {+ <div>+ <NavLink + href="@Regex.Replace(name, @"(\B[A-Z]|\d+)", "-$1").ToLower()">+ @Regex.Replace(name, @"(\B[A-Z]|\d+)", " $1")+ </NavLink>+ </div>+ } </nav></div>TheGetRoutableComponents method in the@code block:
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 theMatch parameter.Error page: The error page is only navigated to by the framework and shouldn't be listed.For an example of the preceding code in a sample app that you can run locally, obtain theBlazor Web App orBlazor WebAssembly sample app.
This section applies to Blazor Web Apps operating over a circuit.
This section applies to Blazor Server apps.
A Blazor Web App is integrated intoASP.NET Core Endpoint Routing. An ASP.NET Core app is configured to accept incoming connections for interactive components withMapRazorComponents in theProgram file. The default root component (first component loaded) is theApp component (App.razor):
app.MapRazorComponents<App>();Blazor Server is integrated intoASP.NET Core Endpoint Routing. An ASP.NET Core app is configured to accept incoming connections for interactive components withMapBlazorHub in theProgram file:
app.UseRouting();app.MapBlazorHub();app.MapFallbackToPage("/_Host");Blazor Server is integrated intoASP.NET Core Endpoint Routing. An ASP.NET Core app is configured to accept incoming connections for interactive components withMapBlazorHub inStartup.Configure.
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, thehost page is usually named_Host.cshtml in thePages folder of the app.
The route specified in the host file is called afallback 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 configuringMapFallbackToPage for non-root URL server hosting, seeASP.NET Core Blazor app base path.
Was this page helpful?
Need help with this topic?
Want to try using Ask Learn to clarify or guide you through this topic?
Was this page helpful?
Want to try using Ask Learn to clarify or guide you through this topic?