From 428f8c94ecb8fcaaa725b459dba219824278977a Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 23 Sep 2025 09:55:19 -0400 Subject: [PATCH 1/3] Split the Routing & Navigation article --- aspnetcore/blazor/call-web-api.md | 4 +- .../blazor/components/built-in-components.md | 18 +- aspnetcore/blazor/components/index.md | 50 +- aspnetcore/blazor/components/render-modes.md | 2 +- aspnetcore/blazor/components/rendering.md | 4 +- aspnetcore/blazor/forms/index.md | 8 +- .../fundamentals/dependency-injection.md | 2 +- aspnetcore/blazor/fundamentals/navigation.md | 1394 +++++++++++++ aspnetcore/blazor/fundamentals/routing.md | 1817 +++-------------- aspnetcore/blazor/fundamentals/startup.md | 2 +- .../blazor/fundamentals/static-files.md | 2 +- .../blazor/host-and-deploy/app-base-path.md | 2 +- .../location-of-javascript.md | 2 +- .../static-server-rendering.md | 2 +- aspnetcore/blazor/project-structure.md | 20 +- .../includes/redirecttologin-component.md | 2 +- aspnetcore/blazor/security/index.md | 2 +- .../blazor/security/webassembly/index.md | 6 +- .../prerendered-state-persistence.md | 14 +- aspnetcore/blazor/state-management/server.md | 2 +- .../tutorials/movie-database-app/part-3.md | 2 +- aspnetcore/fundamentals/index.md | 4 +- aspnetcore/fundamentals/routing.md | 2 +- aspnetcore/migration/60-70.md | 2 +- .../aspnetcore-10/includes/blazor.md | 8 +- aspnetcore/release-notes/aspnetcore-6.0.md | 2 +- aspnetcore/release-notes/aspnetcore-8.0.md | 6 +- .../authentication/passkeys/blazor.md | 2 +- aspnetcore/toc.yml | 4 +- 29 files changed, 1711 insertions(+), 1676 deletions(-) create mode 100644 aspnetcore/blazor/fundamentals/navigation.md diff --git a/aspnetcore/blazor/call-web-api.md b/aspnetcore/blazor/call-web-api.md index e003c7b4255b..d86a1cc91d1e 100644 --- a/aspnetcore/blazor/call-web-api.md +++ b/aspnetcore/blazor/call-web-api.md @@ -626,14 +626,14 @@ When prerendering, components render twice: first statically, then interactively You can address this by flowing prerendered state using the Persistent Component State API, which the `BlazorWebAppCallWebApi` and `BlazorWebAppCallWebApi_Weather` [sample apps](#sample-apps) demonstrate. When the component renders interactively, it can render the same way using the same state. For more information, see the following resources: * -* +* :::moniker-end :::moniker range="< aspnetcore-10.0" > [!NOTE] -> The Persistent Component State API only supports enhanced navigation in .NET 10 or later. For apps that target .NET 8 or .NET 9, you can disable enhanced navigation on links to the page with the `data-enhance-nav` attribute set to `false`. For more information, see . +> The Persistent Component State API only supports enhanced navigation in .NET 10 or later. For apps that target .NET 8 or .NET 9, you can disable enhanced navigation on links to the page with the `data-enhance-nav` attribute set to `false`. For more information, see . :::moniker-end diff --git a/aspnetcore/blazor/components/built-in-components.md b/aspnetcore/blazor/components/built-in-components.md index 9869c607b27d..88fa9a07e6f4 100644 --- a/aspnetcore/blazor/components/built-in-components.md +++ b/aspnetcore/blazor/components/built-in-components.md @@ -39,8 +39,8 @@ The following built-in Razor components are provided by the Blazor framework. Fo * [`InputTextArea`](xref:blazor/forms/input-components) * [`LayoutComponentBase`](xref:blazor/components/layouts#create-a-layout-component) * [`LayoutView`](xref:blazor/components/layouts#apply-a-layout-to-arbitrary-content-layoutview-component) -* [`NavigationLock`](xref:blazor/fundamentals/routing#handleprevent-location-changes) -* [`NavLink`](xref:blazor/fundamentals/routing#navlink-component) +* [`NavigationLock`](xref:blazor/fundamentals/navigation#handleprevent-location-changes) +* [`NavLink`](xref:blazor/fundamentals/navigation#navlink-component) * [`PageTitle`](xref:blazor/components/control-head-content) * [`OwningComponentBase`](xref:fundamentals/dependency-injection#utility-base-component-classes-to-manage-a-di-scope) * [`Paginator`](xref:blazor/components/quickgrid#page-items-with-a-paginator-component) @@ -79,8 +79,8 @@ The following built-in Razor components are provided by the Blazor framework. Fo * [`InputTextArea`](xref:blazor/forms/input-components) * [`LayoutComponentBase`](xref:blazor/components/layouts#create-a-layout-component) * [`LayoutView`](xref:blazor/components/layouts#apply-a-layout-to-arbitrary-content-layoutview-component) -* [`NavigationLock`](xref:blazor/fundamentals/routing#handleprevent-location-changes) -* [`NavLink`](xref:blazor/fundamentals/routing#navlink-component) +* [`NavigationLock`](xref:blazor/fundamentals/navigation#handleprevent-location-changes) +* [`NavLink`](xref:blazor/fundamentals/navigation#navlink-component) * [`PageTitle`](xref:blazor/components/control-head-content) * [`OwningComponentBase`](xref:fundamentals/dependency-injection#utility-base-component-classes-to-manage-a-di-scope) * [`Paginator`](xref:blazor/components/quickgrid#page-items-with-a-paginator-component) @@ -117,8 +117,8 @@ The following built-in Razor components are provided by the Blazor framework. Fo * [`InputTextArea`](xref:blazor/forms/input-components) * [`LayoutComponentBase`](xref:blazor/components/layouts#create-a-layout-component) * [`LayoutView`](xref:blazor/components/layouts#apply-a-layout-to-arbitrary-content-layoutview-component) -* [`NavigationLock`](xref:blazor/fundamentals/routing#handleprevent-location-changes) -* [`NavLink`](xref:blazor/fundamentals/routing#navlink-component) +* [`NavigationLock`](xref:blazor/fundamentals/navigation#handleprevent-location-changes) +* [`NavLink`](xref:blazor/fundamentals/navigation#navlink-component) * [`OwningComponentBase`](xref:fundamentals/dependency-injection#utility-base-component-classes-to-manage-a-di-scope) * [`PageTitle`](xref:blazor/components/control-head-content) * [`Router`](xref:blazor/fundamentals/routing#route-templates) @@ -151,7 +151,7 @@ The following built-in Razor components are provided by the Blazor framework. Fo * [`InputTextArea`](xref:blazor/forms/input-components) * [`LayoutComponentBase`](xref:blazor/components/layouts#create-a-layout-component) * [`LayoutView`](xref:blazor/components/layouts#apply-a-layout-to-arbitrary-content-layoutview-component) -* [`NavLink`](xref:blazor/fundamentals/routing#navlink-component) +* [`NavLink`](xref:blazor/fundamentals/navigation#navlink-component) * [`OwningComponentBase`](xref:fundamentals/dependency-injection#utility-base-component-classes-to-manage-a-di-scope) * [`PageTitle`](xref:blazor/components/control-head-content) * [`Router`](xref:blazor/fundamentals/routing#route-templates) @@ -179,7 +179,7 @@ The following built-in Razor components are provided by the Blazor framework. Fo * [`InputTextArea`](xref:blazor/forms/input-components) * [`LayoutComponentBase`](xref:blazor/components/layouts#create-a-layout-component) * [`LayoutView`](xref:blazor/components/layouts#apply-a-layout-to-arbitrary-content-layoutview-component) -* [`NavLink`](xref:blazor/fundamentals/routing#navlink-component) +* [`NavLink`](xref:blazor/fundamentals/navigation#navlink-component) * [`OwningComponentBase`](xref:fundamentals/dependency-injection#utility-base-component-classes-to-manage-a-di-scope) * [`Router`](xref:blazor/fundamentals/routing#route-templates) * [`RouteView`](xref:blazor/fundamentals/routing#route-templates) @@ -205,7 +205,7 @@ The following built-in Razor components are provided by the Blazor framework. Fo * [`InputTextArea`](xref:blazor/forms/input-components) * [`LayoutComponentBase`](xref:blazor/components/layouts#create-a-layout-component) * [`LayoutView`](xref:blazor/components/layouts#apply-a-layout-to-arbitrary-content-layoutview-component) -* [`NavLink`](xref:blazor/fundamentals/routing#navlink-component) +* [`NavLink`](xref:blazor/fundamentals/navigation#navlink-component) * [`OwningComponentBase`](xref:fundamentals/dependency-injection#utility-base-component-classes-to-manage-a-di-scope) * [`Router`](xref:blazor/fundamentals/routing#route-templates) * [`RouteView`](xref:blazor/fundamentals/routing#route-templates) diff --git a/aspnetcore/blazor/components/index.md b/aspnetcore/blazor/components/index.md index 276bc9c94532..e6005de9d44c 100644 --- a/aspnetcore/blazor/components/index.md +++ b/aspnetcore/blazor/components/index.md @@ -5,7 +5,7 @@ description: Learn how to create and use Razor components in Blazor apps, includ monikerRange: '>= aspnetcore-3.1' ms.author: wpickett ms.custom: mvc -ms.date: 11/12/2024 +ms.date: 09/23/2025 uid: blazor/components/index --- # ASP.NET Core Razor components @@ -528,54 +528,6 @@ In the following example, the `BlazorRocksBase1` base class derives from specifying the route template. At runtime, the router searches for component classes with a and renders whichever component has a route template that matches the requested URL. - -The following `HelloWorld` component uses a route template of `/hello-world`, and the rendered webpage for the component is reached at the relative URL `/hello-world`. - -`HelloWorld.razor`: - -:::moniker range=">= aspnetcore-9.0" - -:::code language="razor" source="~/../blazor-samples/9.0/BlazorSample_BlazorWebApp/Components/Pages/HelloWorld.razor"::: - -:::moniker-end - -:::moniker range=">= aspnetcore-8.0 < aspnetcore-9.0" - -:::code language="razor" source="~/../blazor-samples/8.0/BlazorSample_BlazorWebApp/Components/Pages/HelloWorld.razor"::: - -:::moniker-end - -:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0" - -:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/HelloWorld.razor"::: - -:::moniker-end - -:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/HelloWorld.razor"::: - -:::moniker-end - -:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" - -:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/index/HelloWorld.razor"::: - -:::moniker-end - -:::moniker range="< aspnetcore-5.0" - -:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/index/HelloWorld.razor"::: - -:::moniker-end - -The preceding component loads in the browser at `/hello-world` regardless of whether or not you add the component to the app's UI navigation. Optionally, components can be added to the `NavMenu` component so that a link to the component appears in the app's UI-based navigation. - -For the preceding `HelloWorld` component, you can add a `NavLink` component to the `NavMenu` component. For more information, including descriptions of the `NavLink` and `NavMenu` components, see . - ### Markup A component's UI is defined using [Razor syntax](xref:mvc/views/razor), which consists of Razor markup, C#, and HTML. When an app is compiled, the HTML markup and C# rendering logic are converted into a component class. The name of the generated class matches the name of the file. diff --git a/aspnetcore/blazor/components/render-modes.md b/aspnetcore/blazor/components/render-modes.md index 81b7a57565e0..674508a9c425 100644 --- a/aspnetcore/blazor/components/render-modes.md +++ b/aspnetcore/blazor/components/render-modes.md @@ -357,7 +357,7 @@ During static SSR, Razor component page requests are processed by server-side AS If the app exhibits root-level interactivity, server-side ASP.NET Core request processing isn't involved after the initial static SSR, which means that the preceding Blazor features work as expected. -[Enhanced navigation](xref:blazor/fundamentals/routing#enhanced-navigation-and-form-handling) with static SSR requires special attention when loading JavaScript. For more information, see . +[Enhanced navigation](xref:blazor/fundamentals/navigation#enhanced-navigation-and-form-handling) with static SSR requires special attention when loading JavaScript. For more information, see . ## Interactive server-side rendering (interactive SSR) diff --git a/aspnetcore/blazor/components/rendering.md b/aspnetcore/blazor/components/rendering.md index 0a0f331eaf71..f98665cf2d6a 100644 --- a/aspnetcore/blazor/components/rendering.md +++ b/aspnetcore/blazor/components/rendering.md @@ -57,9 +57,9 @@ To stream content updates when using static server-side rendering (static SSR) o :::moniker range=">= aspnetcore-10.0" -If [enhanced navigation](xref:blazor/fundamentals/routing#enhanced-navigation-and-form-handling) is active, streaming rendering renders [Not Found responses](xref:blazor/fundamentals/routing#not-found-responses) without reloading the page. When enhanced navigation is blocked, the framework redirects to Not Found content with a page refresh. +If [enhanced navigation](xref:blazor/fundamentals/navigation#enhanced-navigation-and-form-handling) is active, streaming rendering renders [Not Found responses](xref:blazor/fundamentals/navigation#not-found-responses) without reloading the page. When enhanced navigation is blocked, the framework redirects to Not Found content with a page refresh. -Streaming rendering can only render components that have a route, such as a [`NotFoundPage` assignment](xref:blazor/fundamentals/routing#not-found-responses) (`NotFoundPage="..."`) or a [Status Code Pages Re-execution Middleware page assignment](xref:fundamentals/error-handling#usestatuscodepageswithreexecute) (). The Not Found render fragment (`...`) and the `DefaultNotFound` 404 content ("`Not found`" plain text) don't have routes, so they can't be used during streaming rendering. +Streaming rendering can only render components that have a route, such as a [`NotFoundPage` assignment](xref:blazor/fundamentals/navigation#not-found-responses) (`NotFoundPage="..."`) or a [Status Code Pages Re-execution Middleware page assignment](xref:fundamentals/error-handling#usestatuscodepageswithreexecute) (). The Not Found render fragment (`...`) and the `DefaultNotFound` 404 content ("`Not found`" plain text) don't have routes, so they can't be used during streaming rendering. Streaming `NavigationManager.NotFound` content rendering uses (in order): diff --git a/aspnetcore/blazor/forms/index.md b/aspnetcore/blazor/forms/index.md index c9e1964d6388..902978e5a742 100644 --- a/aspnetcore/blazor/forms/index.md +++ b/aspnetcore/blazor/forms/index.md @@ -76,7 +76,7 @@ In the preceding `StarshipPlainForm` component: > [!IMPORTANT] > Always use the [`@formname`](xref:mvc/views/razor#formname) directive attribute with a unique form name. -Blazor enhances page navigation and form handling by intercepting the request in order to apply the response to the existing DOM, preserving as much of the rendered form as possible. The enhancement avoids the need to fully load the page and provides a much smoother user experience, similar to a single-page app (SPA), although the component is rendered on the server. For more information, see . +Blazor enhances page navigation and form handling by intercepting the request in order to apply the response to the existing DOM, preserving as much of the rendered form as possible. The enhancement avoids the need to fully load the page and provides a much smoother user experience, similar to a single-page app (SPA), although the component is rendered on the server. For more information, see . @@ -124,7 +124,7 @@ In the preceding `Starship1` component: > [!IMPORTANT] > Always use the property with a unique form name. -Blazor enhances page navigation and form handling for components. For more information, see . +Blazor enhances page navigation and form handling for components. For more information, see . @@ -337,7 +337,7 @@ To mitigate overposting, we recommend using a separate view model/data transfer ## Enhanced form handling -[Enhance navigation](xref:blazor/fundamentals/routing#enhanced-navigation-and-form-handling) for form POST requests with the parameter for forms or the `data-enhance` attribute for HTML forms (`
`): +[Enhance navigation](xref:blazor/fundamentals/navigation#enhanced-navigation-and-form-handling) for form POST requests with the parameter for forms or the `data-enhance` attribute for HTML forms (``): ```razor @@ -380,7 +380,7 @@ In the following example, the content of the `
` element is updated dynamica To disable enhanced navigation and form handling globally, see . -For guidance on using the `enhancedload` event to listen for enhanced page updates, see . +For guidance on using the `enhancedload` event to listen for enhanced page updates, see . :::moniker-end diff --git a/aspnetcore/blazor/fundamentals/dependency-injection.md b/aspnetcore/blazor/fundamentals/dependency-injection.md index 20647ee3c325..9901c0513faf 100644 --- a/aspnetcore/blazor/fundamentals/dependency-injection.md +++ b/aspnetcore/blazor/fundamentals/dependency-injection.md @@ -32,7 +32,7 @@ The services shown in the following table are commonly used in Blazor apps. | ------- | -------- | ----------- | | | Scoped |

Provides methods for sending HTTP requests and receiving HTTP responses from a resource identified by a URI.

Client-side, an instance of is registered by the app in the `Program` file and uses the browser for handling the HTTP traffic in the background.

Server-side, an isn't configured as a service by default. In server-side code, provide an .

For more information, see .

An is registered as a scoped service, not singleton. For more information, see the [Service lifetime](#service-lifetime) section.

| | |

**Client-side**: Singleton

**Server-side**: Scoped

The Blazor framework registers in the app's service container.

|

Represents an instance of a JavaScript runtime where JavaScript calls are dispatched. For more information, see .

When seeking to inject the service into a singleton service on the server, take either of the following approaches:

  • Change the service registration to scoped to match 's registration, which is appropriate if the service deals with user-specific state.
  • Pass the into the singleton service's implementation as an argument of its method calls instead of injecting it into the singleton.
| -| |

**Client-side**: Singleton

**Server-side**: Scoped

The Blazor framework registers in the app's service container.

| Contains helpers for working with URIs and navigation state. For more information, see [URI and navigation state helpers](xref:blazor/fundamentals/routing#uri-and-navigation-state-helpers). | +| |

**Client-side**: Singleton

**Server-side**: Scoped

The Blazor framework registers in the app's service container.

| Contains helpers for working with URIs and navigation state. For more information, see [URI and navigation state helpers](xref:blazor/fundamentals/navigation#uri-and-navigation-state-helpers). | Additional services registered by the Blazor framework are described in the documentation where they're used to describe Blazor features, such as configuration and logging. diff --git a/aspnetcore/blazor/fundamentals/navigation.md b/aspnetcore/blazor/fundamentals/navigation.md new file mode 100644 index 000000000000..1dcbb60476f1 --- /dev/null +++ b/aspnetcore/blazor/fundamentals/navigation.md @@ -0,0 +1,1394 @@ +--- +title: ASP.NET Core Blazor navigation +author: guardrex +description: Learn about navigation in Blazor, including how to use the Navigation Manager and NavLink component for navigation. +monikerRange: '>= aspnetcore-3.1' +ms.author: wpickett +ms.custom: mvc +ms.date: 09/23/2025 +uid: blazor/fundamentals/navigation +--- +# ASP.NET Core Blazor navigation + +[!INCLUDE[](~/includes/not-latest-version.md)] + +This article explains how to use the component to create navigation links and how to use the to manage URIs and navigation in C# code. + +> [!IMPORTANT] +> Code examples throughout this article show methods called on `Navigation`, which is an injected in classes and components. + +## `NavLink` component + +Use a component in place of HTML hyperlink elements (``) when creating navigation links. A component behaves like an `` 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 to apply a custom CSS class to the rendered link when the current route matches the `href`. + +:::moniker range=">= aspnetcore-10.0" + +There are two options that you can assign to the `Match` attribute of the `` element: + +* : The is active when it matches the current URL, ignoring the query string and fragment. To include matching on the query string/fragment, use the `Microsoft.AspNetCore.Components.Routing.NavLink.EnableMatchAllForQueryStringAndFragment` [`AppContext` switch](/dotnet/fundamentals/runtime-libraries/system-appcontext) set to `true`. +* (*default*): The is active when it matches any prefix of the current URL. + +:::moniker-end + +:::moniker range="< aspnetcore-10.0" + +There are two options that you can assign to the `Match` attribute of the `` element: + +* : The is active when it matches the entire current URL, including the query string and fragment. +* (*default*): The is active when it matches any prefix of the current URL. + +:::moniker-end + +In the preceding example, the Home `href=""` matches the home URL and only receives the `active` CSS class at the app's default base path (`/`). The second receives the `active` class when the user visits any URL with a `component` prefix (for example, `/component` and `/component/another-segment`). + +:::moniker range=">= aspnetcore-10.0" + + + +To adopt custom matching logic, subclass and override its `ShouldMatch` method. Return `true` from the method when you want to apply the `active` CSS class: + +```csharp +public class CustomNavLink : NavLink +{ + protected override bool ShouldMatch(string currentUriAbsolute) + { + // Custom matching logic + } +} +``` + +:::moniker-end + +Additional component attributes are passed through to the rendered anchor tag. In the following example, the component includes the `target` attribute: + +```razor +Example page +``` + +The following HTML markup is rendered: + +```html +Example page +``` + +> [!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 +> @for (int c = 1; c < 4; c++) +> { +> var ct = c; +>
  • +> +> Product #@ct +> +>
  • +> } +> ``` +> +> Using an index variable in this scenario is a requirement for **any** child component that uses a loop variable in its [child content](xref:blazor/components/index#child-content-render-fragments), not just the `NavLink` component. +> +> Alternatively, use a `foreach` loop with : +> +> ```razor +> @foreach (var c in Enumerable.Range(1, 3)) +> { +>
  • +> +> Product #@c +> +>
  • +> } +> ``` + +## URI and navigation state helpers + +Use to manage URIs and navigation in C# code. provides the event and methods shown in the following table. + +:::moniker range=">= aspnetcore-10.0" + + + +Member | Description +--- | --- + | Gets the current absolute URI. + | Gets the base URI (with a trailing slash) that can be prepended to relative URI paths to produce an absolute URI. Typically, corresponds to the `href` attribute on the document's `` element ([location of `` content](xref:blazor/project-structure#location-of-head-and-body-content)). + | Navigates to the specified URI. If `forceLoad` is `false`:
    • And enhanced navigation is available at the current URL, Blazor's enhanced navigation is activated.
    • Otherwise, Blazor performs a full-page reload for the requested URL.
    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 interactive router.

    For more information, see the [Enhanced navigation and form handling](#enhanced-navigation-and-form-handling) section.

    If `replace` is `true`, the current URI in the browser history is replaced instead of pushing a new URI onto the history stack.

    + | An event that fires when the navigation location has changed. For more information, see the [Location changes](#location-changes) section. +`NotFound` | Called to handle scenarios where a requested resource isn't found. For more information, see the [Not Found responses](#not-found-responses) section. + | Converts a relative URI into an absolute URI. + | 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](#produce-a-uri-relative-to-the-base-uri-prefix) section. +[`RegisterLocationChangingHandler`](#handleprevent-location-changes) | Registers a handler to process incoming navigation events. Calling always invokes the handler. + | Returns a URI constructed by updating with a single parameter added, updated, or removed. For more information, see the [Query strings](#query-strings) section. + +:::moniker-end + +:::moniker range=">= aspnetcore-8.0 < aspnetcore-10.0" + +Member | Description +--- | --- + | Gets the current absolute URI. + | Gets the base URI (with a trailing slash) that can be prepended to relative URI paths to produce an absolute URI. Typically, corresponds to the `href` attribute on the document's `` element ([location of `` content](xref:blazor/project-structure#location-of-head-and-body-content)). + | Navigates to the specified URI. If `forceLoad` is `false`:
    • And enhanced navigation is available at the current URL, Blazor's enhanced navigation is activated.
    • Otherwise, Blazor performs a full-page reload for the requested URL.
    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 interactive router.

    For more information, see the [Enhanced navigation and form handling](#enhanced-navigation-and-form-handling) section.

    If `replace` is `true`, the current URI in the browser history is replaced instead of pushing a new URI onto the history stack.

    + | An event that fires when the navigation location has changed. For more information, see the [Location changes](#location-changes) section. + | Converts a relative URI into an absolute URI. + | 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](#produce-a-uri-relative-to-the-base-uri-prefix) section. +[`RegisterLocationChangingHandler`](#handleprevent-location-changes) | Registers a handler to process incoming navigation events. Calling always invokes the handler. + | Returns a URI constructed by updating with a single parameter added, updated, or removed. For more information, see the [Query strings](#query-strings) section. + +:::moniker-end + +:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0" + +Member | Description +--- | --- + | Gets the current absolute URI. + | Gets the base URI (with a trailing slash) that can be prepended to relative URI paths to produce an absolute URI. Typically, corresponds to the `href` attribute on the document's `` element ([location of `` content](xref:blazor/project-structure#location-of-head-and-body-content)). + | 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. + | An event that fires when the navigation location has changed. For more information, see the [Location changes](#location-changes) section. + | Converts a relative URI into an absolute URI. + | 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](#produce-a-uri-relative-to-the-base-uri-prefix) section. +[`RegisterLocationChangingHandler`](#handleprevent-location-changes) | Registers a handler to process incoming navigation events. Calling always invokes the handler. + | Returns a URI constructed by updating with a single parameter added, updated, or removed. For more information, see the [Query strings](#query-strings) section. + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + +Member | Description +--- | --- + | Gets the current absolute URI. + | Gets the base URI (with a trailing slash) that can be prepended to relative URI paths to produce an absolute URI. Typically, corresponds to the `href` attribute on the document's `` element ([location of `` content](xref:blazor/project-structure#location-of-head-and-body-content)). + | 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. + | An event that fires when the navigation location has changed. For more information, see the [Location changes](#location-changes) section. + | Converts a relative URI into an absolute URI. + | 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](#produce-a-uri-relative-to-the-base-uri-prefix) section. + | Returns a URI constructed by updating with a single parameter added, updated, or removed. For more information, see the [Query strings](#query-strings) section. + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +Member | Description +--- | --- + | Gets the current absolute URI. + | Gets the base URI (with a trailing slash) that can be prepended to relative URI paths to produce an absolute URI. Typically, corresponds to the `href` attribute on the document's `` element ([location of `` content](xref:blazor/project-structure#location-of-head-and-body-content)). + | 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.
    + | An event that fires when the navigation location has changed. + | Converts a relative URI into an absolute URI. + | 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](#produce-a-uri-relative-to-the-base-uri-prefix) section. + +:::moniker-end + +## Location changes + +For the event, provides the following information about navigation events: + +* : The URL of the new location. +* : If `true`, Blazor intercepted the navigation from the browser. If `false`, caused the navigation to occur. + +The following component: + +* Navigates to the app's `Counter` component (`Counter.razor`) when the button is selected using . +* Handles the location changed event by subscribing to . + * 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: + + > :::no-loc text="BlazorSample.Pages.Navigate: Information: URL of new location: https://localhost:{PORT}/counter"::: + +`Navigate.razor`: + +:::moniker range=">= aspnetcore-9.0" + +:::code language="razor" source="~/../blazor-samples/9.0/BlazorSample_BlazorWebApp/Components/Pages/Navigate.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-8.0 < aspnetcore-9.0" + +:::code language="razor" source="~/../blazor-samples/8.0/BlazorSample_BlazorWebApp/Components/Pages/Navigate.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/routing/Navigate.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/routing/Navigate.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/routing/Navigate.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/routing/Navigate.razor"::: + +:::moniker-end + +For more information on component disposal, see . + +:::moniker range=">= aspnetcore-9.0" + +## Navigation Manager redirect behavior during static server-side rendering (static SSR) + +For a redirect during static server-side rendering (static SSR), relies on throwing a that gets captured by the framework, which converts the error into a redirect. Code that exists after the call to isn't called. When using Visual Studio, the debugger breaks on the exception, requiring you to deselect the checkbox for **Break when this exception type is user-handled** in the Visual Studio UI to avoid the debugger stopping for future redirects. + +:::moniker-end + +:::moniker range=">= aspnetcore-10.0" + +You can use the `` MSBuild property set to `true` in the app's project file to opt-in to no longer throwing a . Also, code after the call to 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: + +```xml +true +``` + +:::moniker-end + +:::moniker range=">= aspnetcore-9.0 < aspnetcore-10.0" + +> [!NOTE] +> In .NET 10 or later, you can opt-in to not throwing a by setting the `` MSBuild property to `true` in the app's project file. To take advantage of the new MSBuild property and behavior, upgrade the app to .NET 10 or later. + +:::moniker-end + +:::moniker range=">= aspnetcore-10.0" + +## Not Found responses + + + + provides a `NotFound` method to handle scenarios where a requested resource isn't found during static server-side rendering (static SSR) or global interactive rendering: + +* **Static SSR**: Calling `NavigationManager.NotFound` sets the HTTP status code to 404. + +* **Interactive rendering**: Signals the Blazor router ([`Router` component](xref:blazor/fundamentals/routing#route-templates)) to render Not Found content. + +* **Streaming rendering**: If [enhanced navigation](xref:blazor/fundamentals/routing?view=aspnetcore-10.0#enhanced-navigation-and-form-handling) is active, [streaming rendering](xref:blazor/components/rendering#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 the `Router` component's `NotFoundPage` parameter. The parameter works in concert with `NavigationManager.NotFound` and is described in more detail later in this section. + +Streaming rendering can only render components that have a route, such as a `NotFoundPage` assignment (`NotFoundPage="..."`) or a [Status Code Pages Re-execution Middleware page assignment](xref:fundamentals/error-handling#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 (`...`) 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): + +* If is set, render the contents of the assigned page. +* If `Router.NotFoundPage` is set, render the assigned page. +* A Status Code Pages Re-execution Middleware page, if configured. +* No action if none of the preceding approaches are adopted. + +[Status Code Pages Re-execution Middleware](xref:fundamentals/error-handling#usestatuscodepageswithreexecute) with 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) and `NavigationManager.NotFound` is called, the 404 status code is set on the response: + +```razor +@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 a `NotFound.razor` page. This page automatically renders whenever `NavigationManager.NotFound` is called, making it possible to handle missing routes with a consistent user experience. + +`Pages/NotFound.razor`: + +```razor +@page "/not-found" +@layout MainLayout + +

    Not Found

    +

    Sorry, the content you are looking for does not exist.

    +``` + +The `NotFound` component is assigned to the router's `NotFoundPage` parameter. `NotFoundPage` supports routing that can be used across Status Code Pages Re-execution Middleware, including non-Blazor middleware. + +In the following example, the preceding `NotFound` component is present in the app's `Pages` folder and passed to the `NotFoundPage` parameter: + +```razor + + + + + + +``` + +When a component is rendered with a global interactive render mode, calling `NavigationManager.NotFound` signals the Blazor router to render the `NotFound` component: + +```razor +@page "/render-not-found-interactive" +@inject NavigationManager Navigation + +@if (RendererInfo.IsInteractive) +{ + +} + +@code { + private void TriggerNotFound() + { + Navigation.NotFound(); + } +} +``` + +You can use the `OnNotFound` event for notifications when `NavigationManager.NotFound` is invoked. The event is only fired when `NavigationManager.NotFound` is called, not for any 404 response. For example, setting `HttpContextAccessor.HttpContext.Response.StatusCode` to `404` doesn't trigger `NavigationManager.NotFound`/`OnNotFound`. + +Apps that implement a custom router can also use `NavigationManager.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 to : + + ```csharp + app.UseStatusCodePagesWithReExecute( + "/not-found", createScopeForStatusCodePages: true); + ``` + +* When the response has started, the can be used by subscribing to the `OnNotFoundEvent` in the router: + + ```razor + @code { + [CascadingParameter] + public 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(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 for components that adopt [interactive server-side rendering (interactive SSR)](xref:blazor/fundamentals/index#client-and-server-rendering-concepts), custom content is rendered depending on where `OnNotFound` is called. If the event is triggered by the following `Movie` component when a movie isn't found on component initialization, a custom message states that the requested movie isn't found. If the event is triggered by the `User` component in the following example, a different message states that the user isn't found. + +The following `NotFoundContext` service manages the context and the message for when content isn't found by components. + +`NotFoundContext.cs`: + +```csharp +public class NotFoundContext +{ + public string? Heading { get; private set; } + public string? Message { get; private set; } + + public void UpdateContext(string heading, string message) + { + Heading = heading; + Message = message; + } +} +``` + +The service is registered in the server-side `Program` file: + +```csharp +builder.Services.AddScoped(); +``` + +The `NotFound` page injects the `NotFoundContext` and displays the heading and message. + +`Pages/NotFound.razor`: + +```razor +@page "/not-found" +@layout MainLayout +@inject NotFoundContext NotFoundContext + +

    @NotFoundContext.Heading

    +
    +

    @NotFoundContext.Message

    +
    +``` + +The `Routes` component (`Routes.razor`) sets the `NotFound` component as the Not Found page via the `NotFoundPage` parameter: + +```razor + + ... + +``` + +In the following example components: + +* The `NotFoundContext` service is injected, along with the . +* In , `HandleNotFound` is an event handler assigned to the `OnNotFound` event. `HandleNotFound` calls `NotFoundContext.UpdateContext` to set a heading and message for Not Found content in the `NotFound` component. +* The components would normally use an ID from a route parameter to obtain a movie or user from a data store, such as a database. In the following examples, no entity is returned (`null`) to simulate what happens when an entity isn't found. +* When no entity is returned to , `NavigationManager.NotFound` is called, which in turn triggers the `OnNotFound` event and the `HandleNotFound` event handler. Not Found content is displayed by the router. +* The `HandleNotFound` method is unhooked on component disposal in . + +`Movie` component (`Movie.razor`): + +```razor +@page "/movie/{Id:int}" +@implements IDisposable +@inject NavigationManager NavigationManager +@inject NotFoundContext NotFoundContext + +
    + No matter what ID is used, no matching movie is returned + from the call to GetMovie(). +
    + +@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 GetMovie(int id) + { + // Simulate no movie with matching id found + return await Task.FromResult(null); + } + + void IDisposable.Dispose() + { + NavigationManager.OnNotFound -= HandleNotFound; + } + + public class MovieItem + { + public int Id { get; set; } + public string? Title { get; set; } + } +} +``` + +`User` component (`User.razor`): + +```razor +@page "/user/{Id:int}" +@implements IDisposable +@inject NavigationManager NavigationManager +@inject NotFoundContext NotFoundContext + +
    + No matter what ID is used, no matching user is returned + from the call to GetUser(). +
    + +@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 GetUser(int id) + { + // Simulate no user with matching id found + return await Task.FromResult(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 the `NavMenu` component (`NavMenu.razor`) to reach the `Movie` and `User` 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. + +In `NavMenu.razor`: + +```razor + + + +``` + +:::moniker-end + +:::moniker range=">= aspnetcore-8.0" + +## Enhanced navigation and form handling + +*This section applies to Blazor Web Apps.* + +Blazor Web Apps are capable of two types of routing for page navigation and form handling requests: + +* Normal navigation (cross-document navigation): a full-page reload is triggered for the request URL. +* Enhanced navigation (same-document navigation): Blazor intercepts the request and performs a `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: + +* The Blazor Web App script (`blazor.web.js`) is used, not the Blazor Server script (`blazor.server.js`) or Blazor WebAssembly script (`blazor.webassembly.js`). +* The feature isn't [explicitly disabled](xref:blazor/fundamentals/startup#disable-enhanced-navigation-and-form-handling). +* The destination URL is within the internal base URI space (the app's base path) and the link to the page doesn't have the `data-enhance-nav` attribute set to `false`. + +If server-side routing and enhanced navigation are enabled, [location changing handlers](#location-changes) 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](#location-changes) 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 calling : + +* If `forceLoad` is `false`, which is the default: + * And enhanced navigation is available at the current URL, Blazor's enhanced navigation is activated. + * Otherwise, Blazor performs a full-page reload for the requested URL. +* If `forceLoad` is `true`: Blazor performs a full-page reload for the requested URL, whether enhanced navigation is available or not. + +You can refresh the current page by calling `NavigationManager.Refresh(bool forceLoad = false)`, which always performs an enhanced navigation, if available. If enhanced navigation isn't available, Blazor performs a full-page reload. + +```csharp +Navigation.Refresh(); +``` + +Pass `true` to the `forceLoad` parameter to ensure a full-page reload is always performed, even if enhanced navigation is available: + +```csharp +Navigation.Refresh(true); +``` + +Enhanced navigation is enabled by default, but it can be controlled hierarchically and on a per-link basis using the `data-enhance-nav` HTML attribute. + +The following examples disable enhanced navigation: + +```html + + GET without enhanced navigation + +``` + +```razor + +``` + +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 the parameter to forms or the `data-enhance` attribute to HTML forms (``): + +```razor + + ... + +``` + +```html + + ... + +``` + +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. + +```html +
    +
    + +
    +
    +``` + +Enhanced form posts only work with Blazor endpoints. Posting an enhanced form to non-Blazor endpoint results in an error. + +To disable enhanced navigation: + +* For an , remove the parameter from the form element (or set it to `false`: `Enhance="false"`). +* For an HTML `
    `, remove the `data-enhance` attribute from form element (or set it to `false`: `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 the `data-permanent` attribute. + +In the following example, the content of the `
    ` element is updated dynamically by a script when the page loads: + +```html +
    + ... +
    +``` + +Once Blazor has started on the client, you can use the `enhancedload` 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. + +```javascript +Blazor.addEventListener('enhancedload', () => console.log('Enhanced update!')); +``` + +To disable enhanced navigation and form handling globally, see . + +Enhanced navigation with [static server-side rendering (static SSR)](xref:blazor/components/render-modes#static-server-side-rendering-static-ssr) requires special attention when loading JavaScript. For more information, see . + +:::moniker-end + +## Produce a URI relative to the base URI prefix + +Based on the app's base URI, converts an absolute URI into a URI relative to the base URI prefix. + +Consider the following example: + +```csharp +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 is thrown. + +Passing `https://localhost:8001/segment` in `inputURI` results in the following exception: + +> :::no-loc text="System.ArgumentException: 'The URI 'https://localhost:8001/segment' is not contained by the base URI 'https://localhost:8000/'.'"::: + +:::moniker range=">= aspnetcore-7.0" + +## Navigation history state + +The uses the browser's [History API](https://developer.mozilla.org/docs/Web/API/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](xref:blazor/security/webassembly/index#customize-authorization). For more information, see the [Navigation options](#navigation-options) section. + +## Navigation options + +Pass to to control the following behaviors: + +* : 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`. +* : Replace the current entry in the history stack. If `false`, append the new entry to the history stack. The default value is `false`. +* : Gets or sets the state to append to the history entry. + +```csharp +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](#handleprevent-location-changes) section. + +:::moniker-end + +## Query strings + +:::moniker range=">= aspnetcore-8.0" + +Use the [`[SupplyParameterFromQuery]` attribute](xref:Microsoft.AspNetCore.Components.SupplyParameterFromQueryAttribute) to specify that a component parameter comes from the query string. + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0" + +Use the [`[SupplyParameterFromQuery]` attribute](xref:Microsoft.AspNetCore.Components.SupplyParameterFromQueryAttribute) with the [`[Parameter]` attribute](xref:Microsoft.AspNetCore.Components.ParameterAttribute) to specify that a component parameter of a *routable* component comes from the query string. + +> [!NOTE] +> Component parameters can only receive query parameter values in routable components with an [`@page`](xref:mvc/views/razor#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. + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0" + +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 (). + +Specify the `[SupplyParameterFromQuery]` attribute's 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: + +:::moniker-end + +:::moniker range=">= aspnetcore-8.0" + +Unlike component parameter properties (`[Parameter]`), `[SupplyParameterFromQuery]` properties can be marked `private` in addition to `public`. + +```csharp +[SupplyParameterFromQuery(Name = "{QUERY PARAMETER NAME}")] +private string? {COMPONENT PARAMETER NAME} { get; set; } +``` + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0" + +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`. + +```csharp +[Parameter] +[SupplyParameterFromQuery(Name = "{QUERY PARAMETER NAME}")] +public string? {COMPONENT PARAMETER NAME} { get; set; } +``` + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0" + +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`. + +:::moniker-end + +:::moniker range=">= aspnetcore-8.0" + +> [!NOTE] +> The query string parameters in the following routable page component also work in a *non-routable* component without an `@page` directive (for example, `Search.razor` for a shared `Search` component used in other components). + +`Search.razor`: + +```razor +@page "/search" + +

    Search Example

    + +

    Filter: @Filter

    + +

    Page: @Page

    + +@if (Stars is not null) +{ +

    Stars:

    + +
      + @foreach (var name in Stars) + { +
    • @name
    • + } +
    +} + +@code { + [SupplyParameterFromQuery] + private string? Filter { get; set; } + + [SupplyParameterFromQuery] + private int? Page { get; set; } + + [SupplyParameterFromQuery(Name = "star")] + private string[]? Stars { get; set; } +} +``` + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0" + +`Search.razor`: + +```razor +@page "/search" + +

    Search Example

    + +

    Filter: @Filter

    + +

    Page: @Page

    + +@if (Stars is not null) +{ +

    Stars:

    + +
      + @foreach (var name in Stars) + { +
    • @name
    • + } +
    +} + +@code { + [Parameter] + [SupplyParameterFromQuery] + public string? Filter { get; set; } + + [Parameter] + [SupplyParameterFromQuery] + public int? Page { get; set; } + + [Parameter] + [SupplyParameterFromQuery(Name = "star")] + public string[]? Stars { get; set; } +} +``` + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0" + +Use to add, change, or remove one or more query parameters on the current URL: + +```razor +@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 (). +* 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 to create a URI constructed from 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 +@inject NavigationManager Navigation + +... + +Navigation.GetUriWithQueryParameters({PARAMETERS}) +``` + +The `{PARAMETERS}` placeholder is an `IReadOnlyDictionary`. + +Pass a URI string to 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 +@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`. + +Supported types are identical to supported types for route constraints: + +* `bool` +* `DateOnly` +* `DateTime` +* `decimal` +* `double` +* `float` +* `Guid` +* `int` +* `long` +* `string` +* `TimeOnly` + +Supported types include: + +* Nullable variants of the preceding types. +* Arrays of the preceding types, whether they're nullable or not nullable. + +[!INCLUDE[](~/blazor/includes/compression-with-untrusted-data.md)] + +### Replace a query parameter value when the parameter exists + +```csharp +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 + +```csharp +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` + +```csharp +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`. + +```csharp +Navigation.GetUriWithQueryParameters( + new Dictionary + { + ["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`. + +```csharp +Navigation.GetUriWithQueryParameters( + new Dictionary + { + ["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 . + +The following example calls: + +* to add or replace the `name` query parameter using a value of `Morena Baccarin`. +* Calls to trigger navigation to the new URL. + +```csharp +Navigation.NavigateTo( + Navigation.GetUriWithQueryParameter("name", "Morena Baccarin")); +``` + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +The query string of a request is obtained from the property: + +```razor +@inject NavigationManager Navigation + +... + +var query = new Uri(Navigation.Uri).Query; +``` + +To parse a query string's parameters, one approach is to use [`URLSearchParams`](https://developer.mozilla.org/docs/Web/API/URLSearchParams) with [JavaScript (JS) interop](xref:blazor/js-interop/call-javascript-from-dotnet): + +```javascript +export createQueryString = (string queryString) => new URLSearchParams(queryString); +``` + +For more information on JavaScript isolation with JavaScript modules, see . + +:::moniker-end + +:::moniker range=">= aspnetcore-8.0" + +## Hashed routing to named elements + +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 an `id` of `targetElement` in the `Counter` component: + +* Anchor element (``) with an `href`: + + ```razor + + ``` + +* component with an `href`: + + ```razor + + ``` + +* passing the relative URL: + + ```csharp + Navigation.NavigateTo("/counter#targetElement"); + ``` + +The following example demonstrates hashed routing to named H2 headings within a component and to external components. + +In the `Home` (`Home.razor`) and `Counter` (`Counter.razor`) components, place the following markup at the bottoms of the existing component markup to serve as navigation targets. The `
    ` creates artificial vertical space to demonstrate browser scrolling behavior: + +```razor +
    + +

    Target H2 heading

    +

    Content!

    +``` + +Add the following `HashedRouting` component to the app. + +`HashedRouting.razor`: + +```razor +@page "/hashed-routing" +@inject NavigationManager Navigation + +Hashed routing + +

    Hashed routing to named elements

    + +
    + +
    + +

    Target H2 heading

    +

    Content!

    + +@code { + private void NavigateToElement() + { + Navigation.NavigateTo("/counter#targetElement"); + } +} +``` + +:::moniker-end + +:::moniker range=">= aspnetcore-7.0" + +## Handle/prevent location changes + + registers a handler to process incoming navigation events. The handler's context provided by includes the following properties: + +* : Gets the target location. +* : Gets the state associated with the target history entry. +* : Gets whether the navigation was intercepted from a link. +* : Gets a to determine if the navigation was canceled, for example, to determine if the user triggered a different navigation. +* : Called to prevent the navigation from continuing. + +A component can register multiple location changing handlers in the [`OnAfterRender{Async}` lifecycle method](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync). 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 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 and dispose registered handlers to unregister them. For more information, see . + +> [!IMPORTANT] +> Don't attempt to execute DOM cleanup tasks via JavaScript (JS) interop when handling location changes. Use the [`MutationObserver` pattern](https://developer.mozilla.org/docs/Web/API/MutationObserver) in JS on the client. For more information, see . + +In the following example, a location changing handler is registered for navigation events. + +`NavHandler.razor`: + +```razor +@page "/nav-handler" +@implements IDisposable +@inject NavigationManager Navigation + +

    + + +

    + +@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 to to control entries and state of the navigation history stack, see the [Navigation options](#navigation-options) section. + +For additional example code, see the [`NavigationManagerComponent` in the `BasicTestApp` (`dotnet/aspnetcore` reference source)](https://github.com/dotnet/aspnetcore/blob/main/src/Components/test/testassets/BasicTestApp/RouterTest/NavigationManagerComponent.razor). + +[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] + +The [`NavigationLock` component](xref:Microsoft.AspNetCore.Components.Routing.NavigationLock) 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. + + parameters: + +* 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](https://developer.mozilla.org/docs/Web/API/Window/beforeunload_event). +* 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. +* is called to prevent navigation from occurring if the user declines to confirm the navigation via a [JavaScript (JS) interop call](xref:blazor/js-interop/call-javascript-from-dotnet) that spawns the [JS `confirm` dialog](https://developer.mozilla.org/docs/Web/API/Window/confirm). + +`NavLock.razor`: + +```razor +@page "/nav-lock" +@inject IJSRuntime JSRuntime +@inject NavigationManager Navigation + + + +

    + +

    + +

    + Microsoft homepage +

    + +@code { + private void Navigate() + { + Navigation.NavigateTo("/"); + } + + private async Task OnBeforeInternalNavigation(LocationChangingContext context) + { + var isConfirmed = await JSRuntime.InvokeAsync("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)](https://github.com/dotnet/aspnetcore/blob/main/src/Components/test/testassets/BasicTestApp/RouterTest/ConfigurableNavigationLock.razor). + +:::moniker-end + +## Dynamically-generated `NavLink` components via reflection + + 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, components are added from a collection: + +```diff + +``` + +The `GetRoutableComponents` method in the `@code` block: + +```csharp +public IEnumerable GetRoutableComponents() => + Assembly.GetExecutingAssembly() + .ExportedTypes + .Where(t => t.IsSubclassOf(typeof(ComponentBase))) + .Where(c => c.GetCustomAttributes(inherit: true) + .OfType() + .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. + +:::moniker range=">= aspnetcore-8.0" + +For an demonstration of the preceding code in a sample app, obtain the [**Blazor Web App** or **Blazor WebAssembly** sample app](xref:blazor/fundamentals/index#sample-apps). + +:::moniker-end diff --git a/aspnetcore/blazor/fundamentals/routing.md b/aspnetcore/blazor/fundamentals/routing.md index 8cff5936e39b..cd5805740f64 100644 --- a/aspnetcore/blazor/fundamentals/routing.md +++ b/aspnetcore/blazor/fundamentals/routing.md @@ -1,21 +1,62 @@ --- -title: ASP.NET Core Blazor routing and navigation +title: ASP.NET Core Blazor routing author: guardrex -description: Learn how to manage Blazor app request routing and how to use the Navigation Manager and NavLink component for navigation. +description: Learn about Blazor app request routing. monikerRange: '>= aspnetcore-3.1' ms.author: wpickett ms.custom: mvc -ms.date: 09/08/2025 +ms.date: 09/23/2025 uid: blazor/fundamentals/routing --- -# ASP.NET Core Blazor routing and navigation +# ASP.NET Core Blazor routing [!INCLUDE[](~/includes/not-latest-version.md)] -This article explains how to manage Blazor app request routing and how to use the component to create navigation links. +This article explains Blazor app request routing, including Static versus interactive routing, ASP.NET Core endpoint routing integration, -> [!IMPORTANT] -> Code examples throughout this article show methods called on `Navigation`, which is an injected in classes and components. +Routing in Blazor is achieved by providing a route template to each accessible component in the app with an [`@page`](xref:mvc/views/razor#page) directive. When a Razor file with an `@page` directive is compiled, the generated class is given a specifying the route template. At runtime, the router searches for component classes with a and renders whichever component has a route template that matches the requested URL. + +The following `HelloWorld` component uses a route template of `/hello-world`, and the rendered webpage for the component is reached at the relative URL `/hello-world`. + +`HelloWorld.razor`: + +:::moniker range=">= aspnetcore-9.0" + +:::code language="razor" source="~/../blazor-samples/9.0/BlazorSample_BlazorWebApp/Components/Pages/HelloWorld.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-8.0 < aspnetcore-9.0" + +:::code language="razor" source="~/../blazor-samples/8.0/BlazorSample_BlazorWebApp/Components/Pages/HelloWorld.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/HelloWorld.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/HelloWorld.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/index/HelloWorld.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/index/HelloWorld.razor"::: + +:::moniker-end + +The preceding component loads in the browser at `/hello-world` regardless of whether or not you add the component to the app's UI navigation. :::moniker range=">= aspnetcore-8.0" @@ -33,6 +74,49 @@ Interactive routing also prevents prerendering because new page content isn't re :::moniker-end +## ASP.NET Core endpoint routing integration + +*This section applies to Blazor Web Apps and Blazor Server apps operating over a circuit.* + +:::moniker range=">= aspnetcore-8.0" + +A Blazor Web App is integrated into [ASP.NET Core Endpoint Routing](xref:fundamentals/routing). An ASP.NET Core app is configured to accept incoming connections for interactive components with in the `Program` file. The default root component (first component loaded) is the `App` component (`App.razor`): + +```csharp +app.MapRazorComponents(); +``` + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0" + +Blazor Server is integrated into [ASP.NET Core Endpoint Routing](xref:fundamentals/routing). An ASP.NET Core app is configured to accept incoming connections for interactive components with in the `Program` file: + +```csharp +app.UseRouting(); + +app.MapBlazorHub(); +app.MapFallbackToPage("/_Host"); +``` + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +Blazor Server is integrated into [ASP.NET Core Endpoint Routing](xref:fundamentals/routing). An ASP.NET Core app is configured to accept incoming connections for interactive components with in `Startup.Configure`. + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + +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 for non-root URL server hosting, see . + +:::moniker-end + ## Route templates :::moniker range=">= aspnetcore-8.0" @@ -136,10 +220,45 @@ When the component navigat :::moniker-end -:::moniker range="< aspnetcore-10.0" - ## Provide custom content when content isn't found +:::moniker range=">= aspnetcore-10.0" + + + +For requests where content isn't found, a Razor component can be assigned to the `Router` component's `NotFoundPage` parameter. The parameter works in concert with `NavigationManager.NotFound`, a method called in developer code that triggers a Not Found response. `NavigationManager.NotFound` is described in the next article, . + +The Blazor project template includes a `NotFound.razor` page. This page automatically renders whenever `NavigationManager.NotFound` is called, making it possible to handle missing routes with a consistent user experience. + +`NotFound.razor`: + +```razor +@page "/not-found" +@layout MainLayout + +

    Not Found

    +

    Sorry, the content you are looking for does not exist.

    +``` + +The `NotFound` component is assigned to the router's `NotFoundPage` parameter. `NotFoundPage` supports routing that can be used across Status Code Pages Re-execution Middleware, including non-Blazor middleware. + +In the following example, the preceding `NotFound` component is present in the app's `Pages` folder and passed to the `NotFoundPage` parameter: + +```razor + + + + + + +``` + +For more information, see the next article on . + +:::moniker-end + +:::moniker range="< aspnetcore-10.0" + The component allows the app to specify custom content if content isn't found for the requested route. Set custom content for the component's parameter: @@ -162,7 +281,7 @@ Blazor Web Apps don't use the -* [Not Found responses](#not-found-responses) section +* [Blazor navigation: Not Found responses](xref:blazor/fundamentals/navigation#not-found-responses) :::moniker-end @@ -569,1624 +688,192 @@ Slashes and segments of the captured path are decoded. For a route template of ` :::moniker-end -## URI and navigation state helpers - -Use to manage URIs and navigation in C# code. provides the event and methods shown in the following table. +:::moniker range=">= aspnetcore-5.0" -:::moniker range=">= aspnetcore-10.0" +## Handle asynchronous navigation events with `OnNavigateAsync` - +The component supports an feature. The handler is invoked when the user: -Member | Description ---- | --- - | Gets the current absolute URI. - | Gets the base URI (with a trailing slash) that can be prepended to relative URI paths to produce an absolute URI. Typically, corresponds to the `href` attribute on the document's `` element ([location of `` content](xref:blazor/project-structure#location-of-head-and-body-content)). - | Navigates to the specified URI. If `forceLoad` is `false`:
    • And enhanced navigation is available at the current URL, Blazor's enhanced navigation is activated.
    • Otherwise, Blazor performs a full-page reload for the requested URL.
    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 interactive router.

    For more information, see the [Enhanced navigation and form handling](#enhanced-navigation-and-form-handling) section.

    If `replace` is `true`, the current URI in the browser history is replaced instead of pushing a new URI onto the history stack.

    - | An event that fires when the navigation location has changed. For more information, see the [Location changes](#location-changes) section. -`NotFound` | Called to handle scenarios where a requested resource isn't found. For more information, see the [Not Found responses](#not-found-responses) section. - | Converts a relative URI into an absolute URI. - | 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](#produce-a-uri-relative-to-the-base-uri-prefix) section. -[`RegisterLocationChangingHandler`](#handleprevent-location-changes) | Registers a handler to process incoming navigation events. Calling always invokes the handler. - | Returns a URI constructed by updating with a single parameter added, updated, or removed. For more information, see the [Query strings](#query-strings) section. +* 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 invocation, which is called in developer code to navigates to a URI. [ API is described in the next article, .] :::moniker-end -:::moniker range=">= aspnetcore-8.0 < aspnetcore-10.0" - -Member | Description ---- | --- - | Gets the current absolute URI. - | Gets the base URI (with a trailing slash) that can be prepended to relative URI paths to produce an absolute URI. Typically, corresponds to the `href` attribute on the document's `` element ([location of `` content](xref:blazor/project-structure#location-of-head-and-body-content)). - | Navigates to the specified URI. If `forceLoad` is `false`:
    • And enhanced navigation is available at the current URL, Blazor's enhanced navigation is activated.
    • Otherwise, Blazor performs a full-page reload for the requested URL.
    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 interactive router.

    For more information, see the [Enhanced navigation and form handling](#enhanced-navigation-and-form-handling) section.

    If `replace` is `true`, the current URI in the browser history is replaced instead of pushing a new URI onto the history stack.

    - | An event that fires when the navigation location has changed. For more information, see the [Location changes](#location-changes) section. - | Converts a relative URI into an absolute URI. - | 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](#produce-a-uri-relative-to-the-base-uri-prefix) section. -[`RegisterLocationChangingHandler`](#handleprevent-location-changes) | Registers a handler to process incoming navigation events. Calling always invokes the handler. - | Returns a URI constructed by updating with a single parameter added, updated, or removed. For more information, see the [Query strings](#query-strings) section. - -:::moniker-end +:::moniker range=">= aspnetcore-6.0" -:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0" +```razor + + ... + -Member | Description ---- | --- - | Gets the current absolute URI. - | Gets the base URI (with a trailing slash) that can be prepended to relative URI paths to produce an absolute URI. Typically, corresponds to the `href` attribute on the document's `` element ([location of `` content](xref:blazor/project-structure#location-of-head-and-body-content)). - | 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. - | An event that fires when the navigation location has changed. For more information, see the [Location changes](#location-changes) section. - | Converts a relative URI into an absolute URI. - | 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](#produce-a-uri-relative-to-the-base-uri-prefix) section. -[`RegisterLocationChangingHandler`](#handleprevent-location-changes) | Registers a handler to process incoming navigation events. Calling always invokes the handler. - | Returns a URI constructed by updating with a single parameter added, updated, or removed. For more information, see the [Query strings](#query-strings) section. +@code { + private async Task OnNavigateAsync(NavigationContext args) + { + ... + } +} +``` :::moniker-end -:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" - -Member | Description ---- | --- - | Gets the current absolute URI. - | Gets the base URI (with a trailing slash) that can be prepended to relative URI paths to produce an absolute URI. Typically, corresponds to the `href` attribute on the document's `` element ([location of `` content](xref:blazor/project-structure#location-of-head-and-body-content)). - | 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. - | An event that fires when the navigation location has changed. For more information, see the [Location changes](#location-changes) section. - | Converts a relative URI into an absolute URI. - | 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](#produce-a-uri-relative-to-the-base-uri-prefix) section. - | Returns a URI constructed by updating with a single parameter added, updated, or removed. For more information, see the [Query strings](#query-strings) section. - -:::moniker-end +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -:::moniker range="< aspnetcore-6.0" +```razor + + ... + -Member | Description ---- | --- - | Gets the current absolute URI. - | Gets the base URI (with a trailing slash) that can be prepended to relative URI paths to produce an absolute URI. Typically, corresponds to the `href` attribute on the document's `` element ([location of `` content](xref:blazor/project-structure#location-of-head-and-body-content)). - | 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.
    - | An event that fires when the navigation location has changed. - | Converts a relative URI into an absolute URI. - | 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](#produce-a-uri-relative-to-the-base-uri-prefix) section. +@code { + private async Task OnNavigateAsync(NavigationContext args) + { + ... + } +} +``` :::moniker-end -## Location changes - -For the event, provides the following information about navigation events: - -* : The URL of the new location. -* : If `true`, Blazor intercepted the navigation from the browser. If `false`, caused the navigation to occur. - -The following component: - -* Navigates to the app's `Counter` component (`Counter.razor`) when the button is selected using . -* Handles the location changed event by subscribing to . - * 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: - - > :::no-loc text="BlazorSample.Pages.Navigate: Information: URL of new location: https://localhost:{PORT}/counter"::: +:::moniker range=">= aspnetcore-5.0" -`Navigate.razor`: +For an example that uses , see . -:::moniker range=">= aspnetcore-9.0" +When prerendering on the server, is executed *twice*: -:::code language="razor" source="~/../blazor-samples/9.0/BlazorSample_BlazorWebApp/Components/Pages/Navigate.razor"::: +* Once when the requested endpoint component is initially rendered statically. +* A second time when the browser renders the endpoint component. :::moniker-end -:::moniker range=">= aspnetcore-8.0 < aspnetcore-9.0" +:::moniker range=">= aspnetcore-8.0" -:::code language="razor" source="~/../blazor-samples/8.0/BlazorSample_BlazorWebApp/Components/Pages/Navigate.razor"::: +To prevent developer code in from executing twice, the `Routes` component can store the for use in the [`OnAfterRender{Async}` lifecycle method](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync), where `firstRender` can be checked. For more information, see [Prerendering with JavaScript interop](xref:blazor/components/lifecycle#prerendering-with-javascript-interop). :::moniker-end -:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0" +:::moniker range=">= aspnetcore-5.0 < aspnetcore-8.0" -:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/routing/Navigate.razor"::: +To prevent developer code in from executing twice, the `App` component can store the for use in [`OnAfterRender{Async}`](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync), where `firstRender` can be checked. For more information, see [Prerendering with JavaScript interop](xref:blazor/components/lifecycle#prerendering-with-javascript-interop). :::moniker-end -:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" +:::moniker range=">= aspnetcore-5.0" -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/routing/Navigate.razor"::: +## Handle cancellations in `OnNavigateAsync` -:::moniker-end +The object passed to the callback contains a that's set when a new navigation event occurs. The callback must throw when this cancellation token is set to avoid continuing to run the callback on an outdated navigation. -:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" +If a user navigates to an endpoint but then immediately navigates to a new endpoint, the app shouldn't continue running the callback for the first endpoint. + +In the following example: -:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/routing/Navigate.razor"::: +* 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. :::moniker-end -:::moniker range="< aspnetcore-5.0" +:::moniker range=">= aspnetcore-6.0" -:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/routing/Navigate.razor"::: +```razor +@inject HttpClient Http +@inject ProductCatalog Products -:::moniker-end + + ... + -For more information on component disposal, see . +@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 }; -:::moniker range=">= aspnetcore-9.0" + foreach (var productId in productIds) + { + context.CancellationToken.ThrowIfCancellationRequested(); + Products.Prefetch(productId); + } + } + } +} +``` -## Navigation Manager redirect behavior during static server-side rendering (static SSR) +:::moniker-end -For a redirect during static server-side rendering (static SSR), relies on throwing a that gets captured by the framework, which converts the error into a redirect. Code that exists after the call to isn't called. When using Visual Studio, the debugger breaks on the exception, requiring you to deselect the checkbox for **Break when this exception type is user-handled** in the Visual Studio UI to avoid the debugger stopping for future redirects. +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -:::moniker-end +```razor +@inject HttpClient Http +@inject ProductCatalog Products -:::moniker range=">= aspnetcore-10.0" + + ... + -You can use the `` MSBuild property set to `true` in the app's project file to opt-in to no longer throwing a . Also, code after the call to 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: +@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 }; -```xml -true + foreach (var productId in productIds) + { + context.CancellationToken.ThrowIfCancellationRequested(); + Products.Prefetch(productId); + } + } + } +} ``` :::moniker-end -:::moniker range=">= aspnetcore-9.0 < aspnetcore-10.0" +:::moniker range=">= aspnetcore-5.0" > [!NOTE] -> In .NET 10 or later, you can opt-in to not throwing a by setting the `` MSBuild property to `true` in the app's project file. To take advantage of the new MSBuild property and behavior, upgrade the app to .NET 10 or later. +> Not throwing if the cancellation token in is canceled can result in unintended behavior, such as rendering a component from a previous navigation. :::moniker-end -:::moniker range=">= aspnetcore-10.0" +:::moniker range=">= aspnetcore-5.0" -## Not Found responses +## User interaction with `` content - +If there's a significant delay during navigation, such as while [lazy-loading assemblies in a Blazor WebAssembly app](xref:blazor/webassembly-lazy-load-assemblies) or for a slow network connection to a Blazor server-side app, the component can indicate to the user that a page transition is occurring. - provides a `NotFound` method to handle scenarios where a requested resource isn't found during static server-side rendering (static SSR) or global interactive rendering: +At the top of the component that specifies the component, add an [`@using`](xref:mvc/views/razor#using) directive for the namespace: -* **Static SSR**: Calling `NotFound` sets the HTTP status code to 404. +```razor +@using Microsoft.AspNetCore.Components.Routing +``` -* **Interactive rendering**: Signals the Blazor router ([`Router` component](xref:blazor/fundamentals/routing#route-templates)) to render Not Found content. +Provide content to the parameter for display during page transition events. -* **Streaming rendering**: If [enhanced navigation](xref:blazor/fundamentals/routing?view=aspnetcore-10.0#enhanced-navigation-and-form-handling) is active, [streaming rendering](xref:blazor/components/rendering#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. +In the router element (`...`) content: -> [!NOTE] -> The following discussion mentions that a Not Found Razor component can be assigned to the `Router` component's `NotFoundPage` parameter. The parameter works in concert with `NavigationManager.NotFound` and is described in more detail later in this section. +```razor + +

    Loading the requested page…

    +
    +``` -Streaming rendering can only render components that have a route, such as a `NotFoundPage` assignment (`NotFoundPage="..."`) or a [Status Code Pages Re-execution Middleware page assignment](xref:fundamentals/error-handling#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 (`...`) 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): - -* If is set, render the contents of the assigned page. -* If `Router.NotFoundPage` is set, render the assigned page. -* A Status Code Pages Re-execution Middleware page, if configured. -* No action if none of the preceding approaches are adopted. - -[Status Code Pages Re-execution Middleware](xref:fundamentals/error-handling#usestatuscodepageswithreexecute) with 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) and `NavigationManager.NotFound` is called, the 404 status code is set on the response: - -```razor -@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 a `NotFound.razor` page by default. This page automatically renders whenever `NavigationManager.NotFound` is called, making it easier to handle missing routes with a consistent user experience. - -`NotFound.razor`: - -```razor -

    Not Found

    - -

    Sorry! Nothing to show.

    -``` - -Assign the `NotFound` component to the router's `NotFoundPage` parameter. `NotFoundPage` supports routing that can be used across Status Code Pages Re-execution Middleware, including non-Blazor middleware. If the `NotFound` render fragment (`...`) is defined together with `NotFoundPage`, the page has higher priority. - -In the following example, the preceding `NotFound` component is present in the app's `Pages` folder and passed to the `NotFoundPage` parameter: - -```razor - - - - - - -``` - -When a component is rendered with a global interactive render mode, calling `NotFound` signals the Blazor router to render the `NotFound` component: - -```razor -@page "/render-not-found-interactive" -@inject NavigationManager Navigation - -@if (RendererInfo.IsInteractive) -{ - -} - -@code { - private void TriggerNotFound() - { - Navigation.NotFound(); - } -} -``` - -You can use the `OnNotFound` event for notifications when `NotFound` is invoked. The event is only fired when `NotFound` is called, not for any 404 response. For example, setting `HttpContextAccessor.HttpContext.Response.StatusCode` to `404` doesn't trigger `NotFound`/`OnNotFound`. - -Apps that implement a custom router can also use `NavigationManager.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 to : - - ```csharp - app.UseStatusCodePagesWithReExecute( - "/not-found", createScopeForStatusCodePages: true); - ``` - -* When the response has started, the can be used by subscribing to the `OnNotFoundEvent` in the router: - - ```razor - @code { - [CascadingParameter] - public 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(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: - -* The `NotFoundContext` service is injected, along with the . -* In , `HandleNotFound` is an event handler assigned to the `OnNotFound` event. `HandleNotFound` calls `NotFoundContext.UpdateContext` to set a heading and message for Not Found content that's displayed by the `Router` component in the `Routes` component (`Routes.razor`). -* The components would normally use an ID from a route parameter to obtain a movie or user from a data store, such as a database. In the following examples, no entity is returned (`null`) to simulate what happens when an entity isn't found. -* When no entity is returned to , `NavigationManager.NotFound` is called, which in turn triggers the `OnNotFound` event and the `HandleNotFound` event handler. Not Found content is displayed by the router. -* The `HandleNotFound` method is unhooked on component disposal in . - -`Movie` component (`Movie.razor`): - -```razor -@page "/movie/{Id:int}" -@implements IDisposable -@inject NavigationManager NavigationManager -@inject NotFoundContext NotFoundContext - -
    - No matter what ID is used, no matching movie is returned - from the call to GetMovie(). -
    - -@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 GetMovie(int id) - { - // Simulate no movie with matching id found - return await Task.FromResult(null); - } - - void IDisposable.Dispose() - { - NavigationManager.OnNotFound -= HandleNotFound; - } - - public class MovieItem - { - public int Id { get; set; } - public string? Title { get; set; } - } -} -``` - -`User` component (`User.razor`): - -```razor -@page "/user/{Id:int}" -@implements IDisposable -@inject NavigationManager NavigationManager -@inject NotFoundContext NotFoundContext - -
    - No matter what ID is used, no matching user is returned - from the call to GetUser(). -
    - -@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 GetUser(int id) - { - // Simulate no user with matching id found - return await Task.FromResult(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 the `NavMenu` component (`NavMenu.razor`) to reach the `Movie` and `User` 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. - -In `NavMenu.razor`: - -```razor - - - -``` - -:::moniker-end - -:::moniker range=">= aspnetcore-8.0" - -## Enhanced navigation and form handling - -*This section applies to Blazor Web Apps.* - -Blazor Web Apps are capable of two types of routing for page navigation and form handling requests: - -* Normal navigation (cross-document navigation): a full-page reload is triggered for the request URL. -* Enhanced navigation (same-document navigation): Blazor intercepts the request and performs a `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: - -* The Blazor Web App script (`blazor.web.js`) is used, not the Blazor Server script (`blazor.server.js`) or Blazor WebAssembly script (`blazor.webassembly.js`). -* The feature isn't [explicitly disabled](xref:blazor/fundamentals/startup#disable-enhanced-navigation-and-form-handling). -* The destination URL is within the internal base URI space (the app's base path) and the link to the page doesn't have the `data-enhance-nav` attribute set to `false`. - -If server-side routing and enhanced navigation are enabled, [location changing handlers](#location-changes) 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](#location-changes) 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 calling : - -* If `forceLoad` is `false`, which is the default: - * And enhanced navigation is available at the current URL, Blazor's enhanced navigation is activated. - * Otherwise, Blazor performs a full-page reload for the requested URL. -* If `forceLoad` is `true`: Blazor performs a full-page reload for the requested URL, whether enhanced navigation is available or not. - -You can refresh the current page by calling `NavigationManager.Refresh(bool forceLoad = false)`, which always performs an enhanced navigation, if available. If enhanced navigation isn't available, Blazor performs a full-page reload. - -```csharp -Navigation.Refresh(); -``` - -Pass `true` to the `forceLoad` parameter to ensure a full-page reload is always performed, even if enhanced navigation is available: - -```csharp -Navigation.Refresh(true); -``` - -Enhanced navigation is enabled by default, but it can be controlled hierarchically and on a per-link basis using the `data-enhance-nav` HTML attribute. - -The following examples disable enhanced navigation: - -```html - - GET without enhanced navigation - -``` - -```razor - -``` - -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 the parameter to forms or the `data-enhance` attribute to HTML forms (``): - -```razor - - ... - -``` - -```html - - ... - -``` - -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. - -```html -
    -
    - -
    -
    -``` - -Enhanced form posts only work with Blazor endpoints. Posting an enhanced form to non-Blazor endpoint results in an error. - -To disable enhanced navigation: - -* For an , remove the parameter from the form element (or set it to `false`: `Enhance="false"`). -* For an HTML `
    `, remove the `data-enhance` attribute from form element (or set it to `false`: `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 the `data-permanent` attribute. - -In the following example, the content of the `
    ` element is updated dynamically by a script when the page loads: - -```html -
    - ... -
    -``` - -Once Blazor has started on the client, you can use the `enhancedload` 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. - -```javascript -Blazor.addEventListener('enhancedload', () => console.log('Enhanced update!')); -``` - -To disable enhanced navigation and form handling globally, see . - -Enhanced navigation with [static server-side rendering (static SSR)](xref:blazor/components/render-modes#static-server-side-rendering-static-ssr) requires special attention when loading JavaScript. For more information, see . - -:::moniker-end - -## Produce a URI relative to the base URI prefix - -Based on the app's base URI, converts an absolute URI into a URI relative to the base URI prefix. - -Consider the following example: - -```csharp -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 is thrown. - -Passing `https://localhost:8001/segment` in `inputURI` results in the following exception: - -> :::no-loc text="System.ArgumentException: 'The URI 'https://localhost:8001/segment' is not contained by the base URI 'https://localhost:8000/'.'"::: - -:::moniker range=">= aspnetcore-7.0" - -## Navigation history state - -The uses the browser's [History API](https://developer.mozilla.org/docs/Web/API/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](xref:blazor/security/webassembly/index#customize-authorization). For more information, see the [Navigation options](#navigation-options) section. - -## Navigation options - -Pass to to control the following behaviors: - -* : 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`. -* : Replace the current entry in the history stack. If `false`, append the new entry to the history stack. The default value is `false`. -* : Gets or sets the state to append to the history entry. - -```csharp -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](#handleprevent-location-changes) section. - -:::moniker-end - -## Query strings - -:::moniker range=">= aspnetcore-8.0" - -Use the [`[SupplyParameterFromQuery]` attribute](xref:Microsoft.AspNetCore.Components.SupplyParameterFromQueryAttribute) to specify that a component parameter comes from the query string. - -:::moniker-end - -:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0" - -Use the [`[SupplyParameterFromQuery]` attribute](xref:Microsoft.AspNetCore.Components.SupplyParameterFromQueryAttribute) with the [`[Parameter]` attribute](xref:Microsoft.AspNetCore.Components.ParameterAttribute) to specify that a component parameter of a *routable* component comes from the query string. - -> [!NOTE] -> Component parameters can only receive query parameter values in routable components with an [`@page`](xref:mvc/views/razor#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. - -:::moniker-end - -:::moniker range=">= aspnetcore-6.0" - -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 (). - -Specify the `[SupplyParameterFromQuery]` attribute's 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: - -:::moniker-end - -:::moniker range=">= aspnetcore-8.0" - -Unlike component parameter properties (`[Parameter]`), `[SupplyParameterFromQuery]` properties can be marked `private` in addition to `public`. - -```csharp -[SupplyParameterFromQuery(Name = "{QUERY PARAMETER NAME}")] -private string? {COMPONENT PARAMETER NAME} { get; set; } -``` - -:::moniker-end - -:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0" - -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`. - -```csharp -[Parameter] -[SupplyParameterFromQuery(Name = "{QUERY PARAMETER NAME}")] -public string? {COMPONENT PARAMETER NAME} { get; set; } -``` - -:::moniker-end - -:::moniker range=">= aspnetcore-6.0" - -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`. - -:::moniker-end - -:::moniker range=">= aspnetcore-8.0" - -> [!NOTE] -> The query string parameters in the following routable page component also work in a *non-routable* component without an `@page` directive (for example, `Search.razor` for a shared `Search` component used in other components). - -`Search.razor`: - -```razor -@page "/search" - -

    Search Example

    - -

    Filter: @Filter

    - -

    Page: @Page

    - -@if (Stars is not null) -{ -

    Stars:

    - -
      - @foreach (var name in Stars) - { -
    • @name
    • - } -
    -} - -@code { - [SupplyParameterFromQuery] - private string? Filter { get; set; } - - [SupplyParameterFromQuery] - private int? Page { get; set; } - - [SupplyParameterFromQuery(Name = "star")] - private string[]? Stars { get; set; } -} -``` - -:::moniker-end - -:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0" - -`Search.razor`: - -```razor -@page "/search" - -

    Search Example

    - -

    Filter: @Filter

    - -

    Page: @Page

    - -@if (Stars is not null) -{ -

    Stars:

    - -
      - @foreach (var name in Stars) - { -
    • @name
    • - } -
    -} - -@code { - [Parameter] - [SupplyParameterFromQuery] - public string? Filter { get; set; } - - [Parameter] - [SupplyParameterFromQuery] - public int? Page { get; set; } - - [Parameter] - [SupplyParameterFromQuery(Name = "star")] - public string[]? Stars { get; set; } -} -``` - -:::moniker-end - -:::moniker range=">= aspnetcore-6.0" - -Use to add, change, or remove one or more query parameters on the current URL: - -```razor -@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 (). -* 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 to create a URI constructed from 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 -@inject NavigationManager Navigation - -... - -Navigation.GetUriWithQueryParameters({PARAMETERS}) -``` - -The `{PARAMETERS}` placeholder is an `IReadOnlyDictionary`. - -Pass a URI string to 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 -@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`. - -Supported types are identical to supported types for route constraints: - -* `bool` -* `DateOnly` -* `DateTime` -* `decimal` -* `double` -* `float` -* `Guid` -* `int` -* `long` -* `string` -* `TimeOnly` - -Supported types include: - -* Nullable variants of the preceding types. -* Arrays of the preceding types, whether they're nullable or not nullable. - -[!INCLUDE[](~/blazor/includes/compression-with-untrusted-data.md)] - -### Replace a query parameter value when the parameter exists - -```csharp -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 - -```csharp -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` - -```csharp -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`. - -```csharp -Navigation.GetUriWithQueryParameters( - new Dictionary - { - ["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`. - -```csharp -Navigation.GetUriWithQueryParameters( - new Dictionary - { - ["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 . - -The following example calls: - -* to add or replace the `name` query parameter using a value of `Morena Baccarin`. -* Calls to trigger navigation to the new URL. - -```csharp -Navigation.NavigateTo( - Navigation.GetUriWithQueryParameter("name", "Morena Baccarin")); -``` - -:::moniker-end - -:::moniker range="< aspnetcore-6.0" - -The query string of a request is obtained from the property: - -```razor -@inject NavigationManager Navigation - -... - -var query = new Uri(Navigation.Uri).Query; -``` - -To parse a query string's parameters, one approach is to use [`URLSearchParams`](https://developer.mozilla.org/docs/Web/API/URLSearchParams) with [JavaScript (JS) interop](xref:blazor/js-interop/call-javascript-from-dotnet): - -```javascript -export createQueryString = (string queryString) => new URLSearchParams(queryString); -``` - -For more information on JavaScript isolation with JavaScript modules, see . - -:::moniker-end - -:::moniker range=">= aspnetcore-8.0" - -## Hashed routing to named elements - -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 an `id` of `targetElement` in the `Counter` component: - -* Anchor element (``) with an `href`: - - ```razor - - ``` - -* component with an `href`: - - ```razor - - ``` - -* passing the relative URL: - - ```csharp - Navigation.NavigateTo("/counter#targetElement"); - ``` - -The following example demonstrates hashed routing to named H2 headings within a component and to external components. - -In the `Home` (`Home.razor`) and `Counter` (`Counter.razor`) components, place the following markup at the bottoms of the existing component markup to serve as navigation targets. The `
    ` creates artificial vertical space to demonstrate browser scrolling behavior: - -```razor -
    - -

    Target H2 heading

    -

    Content!

    -``` - -Add the following `HashedRouting` component to the app. - -`HashedRouting.razor`: - -```razor -@page "/hashed-routing" -@inject NavigationManager Navigation - -Hashed routing - -

    Hashed routing to named elements

    - -
    - -
    - -

    Target H2 heading

    -

    Content!

    - -@code { - private void NavigateToElement() - { - Navigation.NavigateTo("/counter#targetElement"); - } -} -``` - -:::moniker-end - -:::moniker range=">= aspnetcore-5.0" - -## User interaction with `` content - -If there's a significant delay during navigation, such as while [lazy-loading assemblies in a Blazor WebAssembly app](xref:blazor/webassembly-lazy-load-assemblies) or for a slow network connection to a Blazor server-side app, the component can indicate to the user that a page transition is occurring. - -At the top of the component that specifies the component, add an [`@using`](xref:mvc/views/razor#using) directive for the namespace: - -```razor -@using Microsoft.AspNetCore.Components.Routing -``` - -Provide content to the parameter for display during page transition events. - -In the router element (`...`) content: - -```razor - -

    Loading the requested page…

    -
    -``` - -For an example that uses the property, see . - -:::moniker-end - -:::moniker range=">= aspnetcore-5.0" - -## Handle asynchronous navigation events with `OnNavigateAsync` - -The component supports an feature. The 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 invocation. - -:::moniker-end - -:::moniker range=">= aspnetcore-6.0" - -```razor - - ... - - -@code { - private async Task OnNavigateAsync(NavigationContext args) - { - ... - } -} -``` - -:::moniker-end - -:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" - -```razor - - ... - - -@code { - private async Task OnNavigateAsync(NavigationContext args) - { - ... - } -} -``` - -:::moniker-end - -:::moniker range=">= aspnetcore-5.0" - -For an example that uses , see . - -When prerendering on the server, is executed *twice*: - -* Once when the requested endpoint component is initially rendered statically. -* A second time when the browser renders the endpoint component. - -:::moniker-end - -:::moniker range=">= aspnetcore-8.0" - -To prevent developer code in from executing twice, the `Routes` component can store the for use in the [`OnAfterRender{Async}` lifecycle method](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync), where `firstRender` can be checked. For more information, see [Prerendering with JavaScript interop](xref:blazor/components/lifecycle#prerendering-with-javascript-interop). - -:::moniker-end - -:::moniker range=">= aspnetcore-5.0 < aspnetcore-8.0" - -To prevent developer code in from executing twice, the `App` component can store the for use in [`OnAfterRender{Async}`](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync), where `firstRender` can be checked. For more information, see [Prerendering with JavaScript interop](xref:blazor/components/lifecycle#prerendering-with-javascript-interop). - -:::moniker-end - -:::moniker range=">= aspnetcore-5.0" - -## Handle cancellations in `OnNavigateAsync` - -The object passed to the callback contains a that's set when a new navigation event occurs. The callback must throw when this cancellation token is set to avoid continuing to run the 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 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. - -:::moniker-end - -:::moniker range=">= aspnetcore-6.0" - -```razor -@inject HttpClient Http -@inject ProductCatalog Products - - - ... - - -@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); - } - } - } -} -``` - -:::moniker-end - -:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" - -```razor -@inject HttpClient Http -@inject ProductCatalog Products - - - ... - - -@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); - } - } - } -} -``` - -:::moniker-end - -:::moniker range=">= aspnetcore-5.0" - -> [!NOTE] -> Not throwing if the cancellation token in is canceled can result in unintended behavior, such as rendering a component from a previous navigation. - -:::moniker-end - -:::moniker range=">= aspnetcore-7.0" - -## Handle/prevent location changes - - registers a handler to process incoming navigation events. The handler's context provided by includes the following properties: - -* : Gets the target location. -* : Gets the state associated with the target history entry. -* : Gets whether the navigation was intercepted from a link. -* : Gets a to determine if the navigation was canceled, for example, to determine if the user triggered a different navigation. -* : Called to prevent the navigation from continuing. - -A component can register multiple location changing handlers in the [`OnAfterRender{Async}` lifecycle method](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync). 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 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 and dispose registered handlers to unregister them. For more information, see . - -> [!IMPORTANT] -> Don't attempt to execute DOM cleanup tasks via JavaScript (JS) interop when handling location changes. Use the [`MutationObserver` pattern](https://developer.mozilla.org/docs/Web/API/MutationObserver) in JS on the client. For more information, see . - -In the following example, a location changing handler is registered for navigation events. - -`NavHandler.razor`: - -```razor -@page "/nav-handler" -@implements IDisposable -@inject NavigationManager Navigation - -

    - - -

    - -@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 to to control entries and state of the navigation history stack, see the [Navigation options](#navigation-options) section. - -For additional example code, see the [`NavigationManagerComponent` in the `BasicTestApp` (`dotnet/aspnetcore` reference source)](https://github.com/dotnet/aspnetcore/blob/main/src/Components/test/testassets/BasicTestApp/RouterTest/NavigationManagerComponent.razor). - -[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] - -The [`NavigationLock` component](xref:Microsoft.AspNetCore.Components.Routing.NavigationLock) 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. - - parameters: - -* 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](https://developer.mozilla.org/docs/Web/API/Window/beforeunload_event). -* 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. -* is called to prevent navigation from occurring if the user declines to confirm the navigation via a [JavaScript (JS) interop call](xref:blazor/js-interop/call-javascript-from-dotnet) that spawns the [JS `confirm` dialog](https://developer.mozilla.org/docs/Web/API/Window/confirm). - -`NavLock.razor`: - -```razor -@page "/nav-lock" -@inject IJSRuntime JSRuntime -@inject NavigationManager Navigation - - - -

    - -

    - -

    - Microsoft homepage -

    - -@code { - private void Navigate() - { - Navigation.NavigateTo("/"); - } - - private async Task OnBeforeInternalNavigation(LocationChangingContext context) - { - var isConfirmed = await JSRuntime.InvokeAsync("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)](https://github.com/dotnet/aspnetcore/blob/main/src/Components/test/testassets/BasicTestApp/RouterTest/ConfigurableNavigationLock.razor). - -:::moniker-end - -## `NavLink` component - -Use a component in place of HTML hyperlink elements (``) when creating navigation links. A component behaves like an `` 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 to apply a custom CSS class to the rendered link when the current route matches the `href`. - -:::moniker range=">= aspnetcore-10.0" - -There are two options that you can assign to the `Match` attribute of the `` element: - -* : The is active when it matches the current URL, ignoring the query string and fragment. To include matching on the query string/fragment, use the `Microsoft.AspNetCore.Components.Routing.NavLink.EnableMatchAllForQueryStringAndFragment` [`AppContext` switch](/dotnet/fundamentals/runtime-libraries/system-appcontext) set to `true`. -* (*default*): The is active when it matches any prefix of the current URL. - -:::moniker-end - -:::moniker range="< aspnetcore-10.0" - -There are two options that you can assign to the `Match` attribute of the `` element: - -* : The is active when it matches the entire current URL, including the query string and fragment. -* (*default*): The is active when it matches any prefix of the current URL. - -:::moniker-end - -In the preceding example, the Home `href=""` matches the home URL and only receives the `active` CSS class at the app's default base path (`/`). The second receives the `active` class when the user visits any URL with a `component` prefix (for example, `/component` and `/component/another-segment`). - -:::moniker range=">= aspnetcore-10.0" - - - -To adopt custom matching logic, subclass and override its `ShouldMatch` method. Return `true` from the method when you want to apply the `active` CSS class: - -```csharp -public class CustomNavLink : NavLink -{ - protected override bool ShouldMatch(string currentUriAbsolute) - { - // Custom matching logic - } -} -``` - -:::moniker-end - -Additional component attributes are passed through to the rendered anchor tag. In the following example, the component includes the `target` attribute: - -```razor -Example page -``` - -The following HTML markup is rendered: - -```html -Example page -``` - -> [!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 -> @for (int c = 1; c < 4; c++) -> { -> var ct = c; ->
  • -> -> Product #@ct -> ->
  • -> } -> ``` -> -> Using an index variable in this scenario is a requirement for **any** child component that uses a loop variable in its [child content](xref:blazor/components/index#child-content-render-fragments), not just the `NavLink` component. -> -> Alternatively, use a `foreach` loop with : -> -> ```razor -> @foreach (var c in Enumerable.Range(1, 3)) -> { ->
  • -> -> Product #@c -> ->
  • -> } -> ``` - - 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, components are added from a collection: - -```diff - -``` - -The `GetRoutableComponents` method in the `@code` block: - -```csharp -public IEnumerable GetRoutableComponents() => - Assembly.GetExecutingAssembly() - .ExportedTypes - .Where(t => t.IsSubclassOf(typeof(ComponentBase))) - .Where(c => c.GetCustomAttributes(inherit: true) - .OfType() - .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. - -:::moniker range=">= aspnetcore-8.0" - -For an example of the preceding code in a sample app that you can run locally, obtain the [**Blazor Web App** or **Blazor WebAssembly** sample app](xref:blazor/fundamentals/index#sample-apps). - -:::moniker-end - -## ASP.NET Core endpoint routing integration - -:::moniker range=">= aspnetcore-8.0" - -*This section applies to Blazor Web Apps operating over a circuit.* - -:::moniker-end - -:::moniker range="< aspnetcore-8.0" - -*This section applies to Blazor Server apps.* - -:::moniker-end - -:::moniker range=">= aspnetcore-8.0" - -A Blazor Web App is integrated into [ASP.NET Core Endpoint Routing](xref:fundamentals/routing). An ASP.NET Core app is configured to accept incoming connections for interactive components with in the `Program` file. The default root component (first component loaded) is the `App` component (`App.razor`): - -```csharp -app.MapRazorComponents(); -``` - -:::moniker-end - -:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0" - -Blazor Server is integrated into [ASP.NET Core Endpoint Routing](xref:fundamentals/routing). An ASP.NET Core app is configured to accept incoming connections for interactive components with in the `Program` file: - -```csharp -app.UseRouting(); - -app.MapBlazorHub(); -app.MapFallbackToPage("/_Host"); -``` - -:::moniker-end - -:::moniker range="< aspnetcore-6.0" - -Blazor Server is integrated into [ASP.NET Core Endpoint Routing](xref:fundamentals/routing). An ASP.NET Core app is configured to accept incoming connections for interactive components with in `Startup.Configure`. - -:::moniker-end - -:::moniker range="< aspnetcore-8.0" - -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 for non-root URL server hosting, see . +For an example that uses the property, see . :::moniker-end diff --git a/aspnetcore/blazor/fundamentals/startup.md b/aspnetcore/blazor/fundamentals/startup.md index 64e34c887b00..3f79e17f7c2f 100644 --- a/aspnetcore/blazor/fundamentals/startup.md +++ b/aspnetcore/blazor/fundamentals/startup.md @@ -1005,7 +1005,7 @@ Standalone Blazor WebAssembly: *This section applies to Blazor Web Apps.* -To disable [enhanced navigation and form handling](xref:blazor/fundamentals/routing#enhanced-navigation-and-form-handling), set `disableDomPreservation` to `true` for `Blazor.start`: +To disable [enhanced navigation and form handling](xref:blazor/fundamentals/navigation#enhanced-navigation-and-form-handling), set `disableDomPreservation` to `true` for `Blazor.start`: ```html diff --git a/aspnetcore/blazor/fundamentals/static-files.md b/aspnetcore/blazor/fundamentals/static-files.md index 074c04ec24fe..4f63a3b8c002 100644 --- a/aspnetcore/blazor/fundamentals/static-files.md +++ b/aspnetcore/blazor/fundamentals/static-files.md @@ -64,7 +64,7 @@ When [Interactive WebAssembly or Interactive Auto render modes](xref:blazor/fund * Blazor creates an endpoint to expose the resource collection as a JS module. * The URL is emitted to the body of the request as persisted component state when a WebAssembly component is rendered into the page. * During WebAssembly boot, Blazor retrieves the URL, imports the module, and calls a function to retrieve the asset collection and reconstruct it in memory. The URL is specific to the content and cached forever, so this overhead cost is only paid once per user until the app is updated. -* The resource collection is also exposed at a human-readable URL (`_framework/resource-collection.js`), so JS has access to the resource collection for [enhanced navigation](xref:blazor/fundamentals/routing#enhanced-navigation-and-form-handling) or to implement features of other frameworks and third-party components. +* The resource collection is also exposed at a human-readable URL (`_framework/resource-collection.js`), so JS has access to the resource collection for [enhanced navigation](xref:blazor/fundamentals/navigation#enhanced-navigation-and-form-handling) or to implement features of other frameworks and third-party components. Static File Middleware () is useful in the following situations that Map Static Assets () can't handle: diff --git a/aspnetcore/blazor/host-and-deploy/app-base-path.md b/aspnetcore/blazor/host-and-deploy/app-base-path.md index 4416fef8e02d..c1d60e4179f0 100644 --- a/aspnetcore/blazor/host-and-deploy/app-base-path.md +++ b/aspnetcore/blazor/host-and-deploy/app-base-path.md @@ -222,7 +222,7 @@ In [Blazor WebAssembly web API requests with the `HttpClient` service](xref:blaz * Incorrect: `var rsp = await client.GetFromJsonAsync("/api/Account");` * Correct: `var rsp = await client.GetFromJsonAsync("api/Account");` -Don't prefix [Navigation Manager](xref:blazor/fundamentals/routing#uri-and-navigation-state-helpers) relative links with a forward slash. Either avoid the use of a path segment separator or use dot-slash (`./`) relative path notation (`Navigation` is an injected ): +Don't prefix [Navigation Manager](xref:blazor/fundamentals/navigation#uri-and-navigation-state-helpers) relative links with a forward slash. Either avoid the use of a path segment separator or use dot-slash (`./`) relative path notation (`Navigation` is an injected ): * Incorrect: `Navigation.NavigateTo("/other");` * Correct: `Navigation.NavigateTo("other");` diff --git a/aspnetcore/blazor/javascript-interoperability/location-of-javascript.md b/aspnetcore/blazor/javascript-interoperability/location-of-javascript.md index 1be47ad2f137..49fe676eea75 100644 --- a/aspnetcore/blazor/javascript-interoperability/location-of-javascript.md +++ b/aspnetcore/blazor/javascript-interoperability/location-of-javascript.md @@ -39,7 +39,7 @@ Inline JavaScript isn't recommended for Blazor apps. We recommend using [JS coll :::moniker range=">= aspnetcore-8.0" -Only place a `