Поделиться через


навигация ASP.NET Core Blazor

Замечание

Это не последняя версия этой статьи. В текущей версии см. версию .NET 10 этой статьи.

Предупреждение

Эта версия ASP.NET Core больше не поддерживается. Для получения дополнительной информации см. Политику поддержки .NET и .NET Core. В текущей версии см. версию .NET 10 этой статьи.

В этой статье объясняется, как активировать и обрабатывать навигацию Blazorпо страницам. Хотя пользователи могут перемещаться между разными страницами с помощью обычных HTML-ссылок, Blazor улучшает навигацию в приложении, чтобы избежать полной перезагрузки страниц и обеспечить более плавное взаимодействие. NavLink Используйте компонент для создания ссылок навигации, которые автоматически применяют стили, когда ссылка соответствует текущей странице. Для программной навигации и управления URI с помощью службы в коде C# используйте NavigationManager.

В этой статье объясняется, как активировать и обрабатывать навигацию Blazorпо страницам. NavLink Используйте компонент для создания ссылок навигации, которые автоматически применяют стили, когда ссылка соответствует текущей странице. Для программной навигации и управления URI в коде NavigationManager C# используйте службу.

Это важно

Примеры кода в этой статье показывают вызовы методов на Navigation, который является инъецированным NavigationManager в классах и компонентах.

Используйте при создании ссылок навигации компонент NavLink вместо HTML-элементов гиперссылок (<a>). Компонент NavLink ведет себя как элемент <a>, за исключением того, что он переключает класс CSS active в зависимости от того, соответствует ли его href текущему URL-адресу. Класс active помогает пользователю понять, какая страница является активной страницей среди отображаемых ссылок навигации. При необходимости назначьте имя класса CSS свойству NavLink.ActiveClass, чтобы применить пользовательский класс CSS к отображаемой ссылке, если текущий маршрут совпадает с href.

В компоненте NavMenu (NavMenu.razor) приложения Blazor, созданного на основе шаблона проекта Blazor:

<div class="nav-item px-3">
    <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
        <span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
    </NavLink>
</div>
<div class="nav-item px-3">
    <NavLink class="nav-link" href="counter">
        <span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
    </NavLink>
</div>

В предыдущем примере HomeNavLinkhref="" совпадает с домашним URL-адресом и получает только класс CSS active по базовому пути приложения по умолчанию (/). NavLink Второй получает active класс, когда пользователь посещает Counter компонент /counter.

Существует два параметра NavLinkMatch, которые можно назначить атрибуту Match элемента <NavLink>:

  • NavLinkMatch.All: NavLink активна при совпадении с текущим URL-адресом, игнорируя строку запроса и фрагмент. Чтобы включить сопоставление по строке запроса/фрагменту, используйте переключатель Microsoft.AspNetCore.Components.Routing.NavLink.EnableMatchAllForQueryStringAndFragmentAppContext, установленный на true.
  • NavLinkMatch.Prefix (по умолчанию) NavLink активен, если он соответствует любому префиксу текущего URL-адреса.

Для применения пользовательской логики сопоставления создайте подкласс NavLink и переопределите его метод ShouldMatch. Верните true из метода, если вы хотите применить класс CSS active:

public class CustomNavLink : NavLink
{
    protected override bool ShouldMatch(string currentUriAbsolute)
    {
        // Custom matching logic
    }
}

Существует два параметра NavLinkMatch, которые можно назначить атрибуту Match элемента <NavLink>:

  • NavLinkMatch.All: NavLink активна при совпадении всего текущего URL-адреса, включая строку запроса и фрагмент.
  • NavLinkMatch.Prefix (по умолчанию) NavLink активен, если он соответствует любому префиксу текущего URL-адреса.

Дополнительные атрибуты компонента NavLink передаются в отображаемый тег привязки. В следующем примере компонент NavLink включает атрибут target.

<NavLink href="example-page" target="_blank">Example page</NavLink>

Отобразится следующая разметка HTML.

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

Предупреждение

В связи с тем, как Blazor выполняет рендеринг дочернего содержимого, для рендеринга компонентов NavLink в цикле for требуется задать локальную переменную индекса, если в содержимом дочернего компонента NavLink используется переменная цикла приращения:

@for (int c = 1; c < 4; c++)
{
    var ct = c;
    <li ...>
        <NavLink ...>
            <span ...></span> Product #@ct
        </NavLink>
    </li>
}

Использование переменной индекса в этом сценарии обязательно для любого дочернего компонента, который использует переменную цикла в своем дочернем содержимом, а не только для компонента NavLink.

Вместо этого можно использовать цикл foreach с Enumerable.Range:

@foreach (var c in Enumerable.Range(1, 3))
{
    <li ...>
        <NavLink ...>
            <span ...></span> Product #@c
        </NavLink>
    </li>
}

URI и вспомогательные средства для навигации

Используйте NavigationManager для управления кодами URI и навигацией в коде C#. NavigationManager предоставляет события и методы, приведенные в следующей таблице.

Член Description
Uri Возвращает текущий абсолютный URI.
BaseUri Получает базовый URI (с завершающим слэшем), который можно присоединить к относительным путям URI для получения абсолютного URI. Как правило, BaseUri соответствует атрибуту href элемента <base> документа (расположение содержимого <head>).
NavigateTo Переходит по указанному URI. Если значение forceLoad равно false:
  • Также доступна расширенная навигация по текущему URL-адресу, и расширенная навигация Blazor активирована.
  • Blazor В противном случае выполняется полная перезагрузка страницы для запрошенного URL-адреса.
Если значение forceLoad равно true:
  • Маршрутизация на стороне клиента пропускается.
  • Браузер вынужден загрузить новую страницу с сервера, независимо от того, обрабатывается ли URI на стороне клиента интерактивным маршрутизатором.

Дополнительные сведения см. в разделе "Расширенная навигация и обработка форм".

Если replace имеет значение true, текущий URI в журнале браузера заменяется вместо отправки нового URI в стек журнала.

LocationChanged Событие, которое запускается при изменении точки навигации. Дополнительные сведения см. в разделе Изменения расположения.
NotFound Вызывается для обработки сценариев, когда запрошенный ресурс не найден. Дополнительные сведения см. в разделе "Не найдено ответов ".
ToAbsoluteUri Преобразует относительный URI в абсолютный.
ToBaseRelativePath На основе базового URI приложения преобразует абсолютный URI в URI относительно базового URI. Например, см. раздел "Создать URI, относящийся к базовому префиксу URI".
RegisterLocationChangingHandler Регистрирует обработчик для обработки входящих событий навигации. Вызов NavigateTo всегда активирует обработчик.
GetUriWithQueryParameter Возвращает URI, созданный путем обновления NavigationManager.Uri с добавлением, обновлением или удалением одного параметра. Дополнительные сведения см. в разделе Строки запросов.
Член Description
Uri Возвращает текущий абсолютный URI.
BaseUri Получает базовый URI (с завершающим слэшем), который можно присоединить к относительным путям URI для получения абсолютного URI. Как правило, BaseUri соответствует атрибуту href элемента <base> документа (расположение содержимого <head>).
NavigateTo Переходит по указанному URI. Если значение forceLoad равно false:
  • Также доступна расширенная навигация по текущему URL-адресу, и расширенная навигация Blazor активирована.
  • Blazor В противном случае выполняется полная перезагрузка страницы для запрошенного URL-адреса.
Если значение forceLoad равно true:
  • Маршрутизация на стороне клиента пропускается.
  • Браузер вынужден загрузить новую страницу с сервера, независимо от того, обрабатывается ли URI на стороне клиента интерактивным маршрутизатором.

Дополнительные сведения см. в разделе "Расширенная навигация и обработка форм".

Если replace имеет значение true, текущий URI в журнале браузера заменяется вместо отправки нового URI в стек журнала.

LocationChanged Событие, которое запускается при изменении точки навигации. Дополнительные сведения см. в разделе Изменения расположения.
ToAbsoluteUri Преобразует относительный URI в абсолютный.
ToBaseRelativePath На основе базового URI приложения преобразует абсолютный URI в URI относительно базового URI. Например, см. раздел "Создать URI, относящийся к базовому префиксу URI".
RegisterLocationChangingHandler Регистрирует обработчик для обработки входящих событий навигации. Вызов NavigateTo всегда активирует обработчик.
GetUriWithQueryParameter Возвращает URI, созданный путем обновления NavigationManager.Uri с добавлением, обновлением или удалением одного параметра. Дополнительные сведения см. в разделе Строки запросов.
Член Description
Uri Возвращает текущий абсолютный URI.
BaseUri Получает базовый URI (с завершающим слэшем), который можно присоединить к относительным путям URI для получения абсолютного URI. Как правило, BaseUri соответствует атрибуту href элемента <base> документа (расположение содержимого <head>).
NavigateTo Переходит по указанному URI. Если значение forceLoad равно true:
  • Маршрутизация на стороне клиента пропускается.
  • Браузер вынужден загрузить новую страницу с сервера, независимо от того, обрабатывается ли URI клиентским маршрутизатором.
Если replace имеет значение true, текущий URI в журнале браузера заменяется вместо отправки нового URI в стек журнала.
LocationChanged Событие, которое запускается при изменении точки навигации. Дополнительные сведения см. в разделе Изменения расположения.
ToAbsoluteUri Преобразует относительный URI в абсолютный.
ToBaseRelativePath На основе базового URI приложения преобразует абсолютный URI в URI относительно базового URI. Например, см. раздел "Создать URI, относящийся к базовому префиксу URI".
RegisterLocationChangingHandler Регистрирует обработчик для обработки входящих событий навигации. Вызов NavigateTo всегда активирует обработчик.
GetUriWithQueryParameter Возвращает URI, созданный путем обновления NavigationManager.Uri с добавлением, обновлением или удалением одного параметра. Дополнительные сведения см. в разделе Строки запросов.
Член Description
Uri Возвращает текущий абсолютный URI.
BaseUri Получает базовый URI (с завершающим слэшем), который можно присоединить к относительным путям URI для получения абсолютного URI. Как правило, BaseUri соответствует атрибуту href элемента <base> документа (расположение содержимого <head>).
NavigateTo Переходит по указанному URI. Если значение forceLoad равно true:
  • Маршрутизация на стороне клиента пропускается.
  • Браузер вынужден загрузить новую страницу с сервера, независимо от того, обрабатывается ли URI клиентским маршрутизатором.
Если replace имеет значение true, текущий URI в журнале браузера заменяется вместо отправки нового URI в стек журнала.
LocationChanged Событие, которое запускается при изменении точки навигации. Дополнительные сведения см. в разделе Изменения расположения.
ToAbsoluteUri Преобразует относительный URI в абсолютный.
ToBaseRelativePath На основе базового URI приложения преобразует абсолютный URI в URI относительно базового URI. Например, см. раздел "Создать URI, относящийся к базовому префиксу URI".
GetUriWithQueryParameter Возвращает URI, созданный путем обновления NavigationManager.Uri с добавлением, обновлением или удалением одного параметра. Дополнительные сведения см. в разделе Строки запросов.
Член Description
Uri Возвращает текущий абсолютный URI.
BaseUri Получает базовый URI (с завершающим слэшем), который можно присоединить к относительным путям URI для получения абсолютного URI. Как правило, BaseUri соответствует атрибуту href элемента <base> документа (расположение содержимого <head>).
NavigateTo Переходит по указанному URI. Если значение forceLoad равно true:
  • Маршрутизация на стороне клиента пропускается.
  • Браузер вынужден загрузить новую страницу с сервера, независимо от того, обрабатывается ли URI клиентским маршрутизатором.
LocationChanged Событие, которое запускается при изменении точки навигации.
ToAbsoluteUri Преобразует относительный URI в абсолютный.
ToBaseRelativePath На основе базового URI приложения преобразует абсолютный URI в URI относительно базового URI. Например, см. раздел "Создать URI, относящийся к базовому префиксу URI".

Изменения расположения

Для события LocationChangedLocationChangedEventArgs предоставляет следующие сведения о событиях навигации.

Следующий компонент:

  • переходит к компоненту Counter приложения (Counter.razor) при нажатии кнопки с помощью NavigateTo;
  • Обрабатывает событие изменения местоположения путем оформления подписки на NavigationManager.LocationChanged.
    • Метод HandleLocationChanged отсоединяется, когда платформа вызывает Dispose. Отсоединение метода позволяет выполнить сборку мусора для компонента.

    • При нажатии кнопки реализация средства ведения журнала записывает следующие сведения.

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

Navigate.razor:

@page "/navigate"
@implements IDisposable
@inject ILogger<Navigate> Logger
@inject NavigationManager Navigation

<h1>Navigate Example</h1>

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

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

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

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

    public void Dispose() => Navigation.LocationChanged -= HandleLocationChanged;
}

Дополнительные сведения об удалении компонентов см. в разделе Удаление компонентов в ASP.NET Core Razor.

Для перенаправления во время отрисовки на стороне статического сервера (статический SSR) NavigationManager используется исключение NavigationException , которое захватывается платформой, которая преобразует ошибку в перенаправление. Код, который существует после вызова NavigateTo , не вызывается. При использовании Visual Studio отладчик прерывает исключение, требуя отмены флажка для прерывания, если этот тип исключения обрабатывается пользователем в пользовательском интерфейсе Visual Studio, чтобы избежать остановки отладчика для будущих перенаправлений.

Свойство MSBuild, заданное <BlazorDisableThrowNavigationException> в файле проекта приложения, можно использовать true для того, чтобы больше не вызывать NavigationExceptionисключение. Кроме того, код после вызова NavigateTo выполняется, когда он не будет выполняться раньше. Это поведение включено по умолчанию в шаблоне проекта .NET 10 или более поздней версии Blazor Web App :

<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>

Замечание

В .NET 10 или более поздней версии вы можете отказаться от создания объекта, задав NavigationException<BlazorDisableThrowNavigationException> для свойства MSBuild значение true в файле проекта приложения. Чтобы воспользоваться новым свойством и поведением MSBuild, обновите приложение до .NET 10 или более поздней версии.

Не найдены ответы

NavigationManager NotFound предоставляет метод для обработки сценариев, когда запрошенный ресурс не найден во время статического рендеринга на стороне сервера (SSR) или глобального интерактивного рендеринга:

  • Статический SSR: вызов NavigationManager.NotFound устанавливает код состояния HTTP на 404.

  • Интерактивная отрисовка: сигнализирует Blazor маршрутизатору (Router компоненту) для отрисовки не найденного содержимого.

  • Потоковая отрисовка: если расширенная навигация активна, потоковая отрисовка отрисовывает не найденное содержимое без перезагрузки страницы. Если расширенная навигация заблокирована, платформа перенаправляется на содержимое Not Found с обновлением страницы.

Замечание

В следующем обсуждении упоминается, что компонент Not Found Razor можно назначить в параметр Router компонента NotFoundPage. Этот параметр работает в сочетании с NavigationManager.NotFound и подробно описан далее в этом разделе.

Потоковая отрисовка может отображать только компоненты, имеющие маршрут, например NotFoundPage назначение (NotFoundPage="...") или назначение страницы кода состояния повторного выполнения по промежуточного слоя (UseStatusCodePagesWithReExecute). DefaultNotFound 404 содержимое ("Not foundобычный текст") не имеет маршрута, поэтому его нельзя использовать во время потоковой отрисовки.

Замечание

Фрагмент отрисовки не найден (<NotFound>...</NotFound>) не поддерживается в .NET 10 или более поздней версии.

NavigationManager.NotFound Рендеринг содержимого использует следующее, независимо от того, начался ли ответ (в порядке):

  • Если NotFoundEventArgs.Path задан, отобразите содержимое назначенной страницы.
  • Если Router.NotFoundPage задан, отобразите назначенную страницу.
  • При правильной настройке страниц кодов состояния через промежуточное программное обеспечение повторного выполнения.
  • Никаких действий, если ни один из предыдущих подходов не принят.

ПО переобработки страниц с кодами состояния имеет UseStatusCodePagesWithReExecute приоритет в решении проблем маршрутизации адресов, связанных с браузером, таких как ввод неправильного URL-адреса в адресную строку браузера или выбор ссылки, не ведущей к конечной точке в приложении.

Когда компонент отображается статически (статический SSR) и NavigationManager.NotFound вызывается, код состояния 404 устанавливается в ответе:

@page "/render-not-found-ssr"
@inject NavigationManager Navigation

@code {
    protected override void OnInitialized()
    {
        Navigation.NotFound();
    }
}

Чтобы предоставить содержимое страницы «Не найдено» для глобального интерактивного рендеринга, используйте компонент Razor.

Замечание

Шаблон Blazor проекта содержит страницу NotFound.razor . Эта страница автоматически отрисовывается всякий раз, когда вызывается NavigationManager.NotFound, что позволяет обрабатывать отсутствующие маршруты с единым пользовательским опытом.

Pages/NotFound.razor:

@page "/not-found"
@layout MainLayout

<h3>Not Found</h3>
<p>Sorry, the content you are looking for does not exist.</p>

Компонент NotFound назначается параметру маршрутизатора NotFoundPage . NotFoundPage поддерживает маршрутизацию, которую можно использовать в промежуточном слое повторного выполнения страниц кодов состояния, включая промежуточные слои, отличные от Blazor.

В следующем примере предыдущий NotFound компонент присутствует в папке приложения Pages и передается параметру NotFoundPage :

<Router AppAssembly="@typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
</Router>

Когда компонент отрисовывается в глобальном интерактивном режиме, вызов NavigationManager.NotFound сигнализирует маршрутизатору Blazor отрисовать компонент NotFound.

@page "/render-not-found-interactive"
@inject NavigationManager Navigation

@if (RendererInfo.IsInteractive)
{
    <button @onclick="TriggerNotFound">Trigger Not Found</button>
}

@code {
    private void TriggerNotFound()
    {
        Navigation.NotFound();
    }
}

Событие OnNotFound можно использовать для уведомлений при вызове NavigationManager.NotFound. Событие запускается только когда вызывается NavigationManager.NotFound, а не для любого ответа с кодом 404. Например, установка HttpContextAccessor.HttpContext.Response.StatusCode в 404 не вызывает NavigationManager.NotFound/OnNotFound.

Приложения, реализующие пользовательский маршрутизатор, также могут использовать NavigationManager.NotFound. Пользовательский маршрутизатор может выводить содержимое «Не найдено» из двух источников в зависимости от состояния ответа.

  • Независимо от состояния ответа, путь повторного перехода к странице может быть использован, передавая его в UseStatusCodePagesWithReExecute.

    app.UseStatusCodePagesWithReExecute(
        "/not-found", createScopeForStatusCodePages: true);
    
  • Когда ответ начался, NotFoundEventArgs.Path можно воспользоваться, подписавшись на OnNotFoundEvent в маршрутизаторе.

    @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<RouteAttribute>(inherit: true);
    
            if (routeAttributes.Length == 0)
            {
                throw new InvalidOperationException($"The type {type.FullName} " +
                    $"doesn't have a {nameof(RouteAttribute)} applied.");
            }
    
            var routeAttribute = (RouteAttribute)routeAttributes[0];
    
            if (routeAttribute.Template != null)
            {
                e.Path = routeAttribute.Template;
            }
        }
    }
    

В следующем примере для компонентов, использующих интерактивную отрисовку на стороне сервера (интерактивная служба SSR), настраиваемое содержимое отображается в зависимости от того, где OnNotFound вызывается вызов. Если событие активируется следующим Movie компонентом, если фильм не найден при инициализации компонента, настраиваемое сообщение указывает, что запрошенный фильм не найден. Если событие активируется компонентом User в следующем примере, то другое сообщение указывает, что пользователь не найден.

Следующая NotFoundContext служба управляет контекстом и сообщением, если содержимое не найдено компонентами.

NotFoundContext.cs:

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;
    }
}

Служба регистрируется в серверном Program файле:

builder.Services.AddScoped<NotFoundContext>();

Страница NotFound внедряет NotFoundContext и отображает заголовок и сообщение.

Pages/NotFound.razor:

@page "/not-found"
@layout MainLayout
@inject NotFoundContext NotFoundContext

<h3>@NotFoundContext.Heading</h3>
<div>
    <p>@NotFoundContext.Message</p>
</div>

Компонент Routes с помощью параметра NotFoundPage задает компонент NotFound как страницу Not Found.

<Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
    ...
</Router>

В следующем примере показаны компоненты:

  • Служба NotFoundContext внедряется вместе с NavigationManager.
  • В OnInitializedAsync, HandleNotFound является обработчиком, назначенным для события OnNotFound. HandleNotFound вызывает NotFoundContext.UpdateContext для задания заголовка и сообщения для содержимого "Не найдено" в компоненте NotFound.
  • Компоненты обычно используют идентификатор из параметра маршрута для получения фильма или пользователя из хранилища данных, например базы данных. В следующих примерах объект не возвращается (null) для имитации того, что происходит, когда сущность не найдена.
  • Если сущность не возвращается в OnInitializedAsync, NavigationManager.NotFound вызывается, который, в свою очередь, активирует событие OnNotFound и обработчик событий HandleNotFound. Не найденное содержимое отображается маршрутизатором.
  • Метод HandleNotFound отсоединяется при освобождении компонентов в IDisposable.Dispose.

Компонент Movie (Movie.razor):

@page "/movie/{Id:int}"
@implements IDisposable
@inject NavigationManager NavigationManager
@inject NotFoundContext NotFoundContext

<div>
    No matter what ID is used, no matching movie is returned
    from the call to GetMovie().
</div>

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

    protected override async Task OnInitializedAsync()
    {
        NavigationManager.OnNotFound += HandleNotFound;

        var movie = await GetMovie(Id);

        if (movie == null)
        {
            NavigationManager.NotFound();
        }
    }

    private void HandleNotFound(object? sender, NotFoundEventArgs e)
    {
        NotFoundContext.UpdateContext("Movie Not Found",
            "Sorry! The requested movie wasn't found.");
    }

    private async Task<MovieItem[]?> GetMovie(int id)
    {
        // Simulate no movie with matching id found
        return await Task.FromResult<MovieItem[]?>(null);
    }

    void IDisposable.Dispose()
    {
        NavigationManager.OnNotFound -= HandleNotFound;
    }

    public class MovieItem
    {
        public int Id { get; set; }
        public string? Title { get; set; }
    }
}

Компонент User (User.razor):

@page "/user/{Id:int}"
@implements IDisposable
@inject NavigationManager NavigationManager
@inject NotFoundContext NotFoundContext

<div>
    No matter what ID is used, no matching user is returned
    from the call to GetUser().
</div>

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

    protected override async Task OnInitializedAsync()
    {
        NavigationManager.OnNotFound += HandleNotFound;

        var user = await GetUser(Id);

        if (user == null)
        {
            NavigationManager.NotFound();
        }
    }

    private void HandleNotFound(object? sender, NotFoundEventArgs e)
    {
        NotFoundContext.UpdateContext("User Not Found",
            "Sorry! The requested user wasn't found.");
    }

    private async Task<UserItem[]?> GetUser(int id)
    {
        // Simulate no user with matching id found
        return await Task.FromResult<UserItem[]?>(null);
    }

    void IDisposable.Dispose()
    {
        NavigationManager.OnNotFound -= HandleNotFound;
    }

    public class UserItem
    {
        public int Id { get; set; }
        public string? Name { get; set; }
    }
}

Чтобы обратиться к предыдущим компонентам в локальной демонстрации с помощью тестового приложения, создайте записи в компоненте NavMenu (NavMenu.razor), чтобы получить доступ к компонентам Movie и User. Идентификаторы сущностей, передаваемые в качестве параметров маршрута, в следующем примере являются фиктивными значениями, которые не оказывают эффекта, так как они фактически не используются компонентами, имитирующими отсутствие фильма или пользователя.

В NavMenu.razor:

<div class="nav-item px-3">
    <NavLink class="nav-link" href="movie/1">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Movie
    </NavLink>
</div>

<div class="nav-item px-3">
    <NavLink class="nav-link" href="user/2">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> User
    </NavLink>
</div>

Улучшенная навигация и обработка форм

Этот раздел относится к Blazor Web Apps.

Blazor Web Apps поддерживает два типа маршрутизации для запросов навигации по страницам и обработки форм:

  • Обычная навигация (перекрестная навигация по документу): для URL-адреса запроса активируется полная перезагрузка.
  • Улучшенная навигация (навигирование по тому же документу): Blazor перехватывает и выполняет fetch запрос вместо этого. Blazor затем вставляет содержимое ответа в DOM страницы. BlazorУлучшенная навигация и обработка форм избегают необходимости полностраничной перезагрузки и, при этом, сохраняют больше состояния страницы, что помогает страницам загружаться быстрее, обычно без потери позиции прокрутки пользователя на странице.

Улучшенная навигация доступна при следующих случаях:

  • Скрипт Blazor Web App (blazor.web.js) используется, а не Blazor Server скрипт (blazor.server.js) или Blazor WebAssembly скрипт (blazor.webassembly.js).
  • Эта функция не отключена явным образом.
  • URL-адрес назначения находится в пределах внутреннего базового пространства URI (базовый путь приложения), а ссылка на страницу не имеет атрибута data-enhance-navfalse.

Если включена маршрутизация на стороне сервера и расширенная навигация, обработчики изменения расположения вызываются только для программной навигации, инициированной из интерактивной среды выполнения. В будущих выпусках дополнительные типы навигации, такие как переход по ссылке, также могут вызывать обработчики изменения местоположения.

Когда происходит расширенная навигация, обработчики событий, LocationChanged зарегистрированные в интерактивном сервере и среде выполнения WebAssembly, обычно вызываются. В некоторых случаях обработчики изменения местоположения не могут перехватывать расширенную навигацию. Например, пользователь может переключиться на другую страницу перед тем, как интерактивная среда выполнения станет доступной. Поэтому важно, чтобы логика приложения не зависела от вызова обработчика изменения расположения, так как не гарантируется выполнение обработчика.

При вызове NavigateTo:

  • Если forceLoad — это false, как и установлено по умолчанию:
    • Также доступна расширенная навигация по текущему URL-адресу, и расширенная навигация Blazor активирована.
    • Blazor В противном случае выполняется полная перезагрузка страницы для запрошенного URL-адреса.
  • Если forceLoad это true: Blazor выполняет полную перезагрузку страницы для запрошенного URL-адреса, независимо от того, доступна ли расширенная навигация.

Вы можете обновить текущую страницу, вызвав NavigationManager.Refresh(bool forceLoad = false)функцию, которая всегда выполняет расширенную навигацию, если она доступна. Если расширенная навигация недоступна, Blazor выполняет полную перезагрузку страницы.

Navigation.Refresh();

Передайте true параметру forceLoad, чтобы убедиться, что полная перезагрузка страницы всегда выполняется, даже если имеется расширенная навигация.

Navigation.Refresh(true);

Расширенная навигация включена по умолчанию, но она может управляться иерархически и на основе каждой ссылки с помощью атрибута data-enhance-nav HTML.

В следующих примерах отключена расширенная навигация:

<a href="redirect" data-enhance-nav="false">
    GET without enhanced navigation
</a>
<ul data-enhance-nav="false">
    <li>
        <a href="redirect">GET without enhanced navigation</a>
    </li>
    <li>
        <a href="redirect-2">GET without enhanced navigation</a>
    </li>
</ul>

Если назначение не является конечнойBlazor точкой, расширенная навигация не применяется, и JavaScript на стороне клиента повторно загружает страницу целиком. Это гарантирует отсутствие путаницы для фреймворка относительно внешних страниц, которые не должны быть внедрены в существующую страницу.

Чтобы включить расширенную обработку форм, добавьте Enhance параметр в EditForm формы или data-enhance атрибут в HTML-формы (<form>):

<EditForm ... Enhance ...>
    ...
</EditForm>
<form ... data-enhance ...>
    ...
</form>

Улучшенная обработка форм не является иерархической и не будет передаваться в дочерние формы:

Неподдерживается: нельзя задать расширенную навигацию на элементе-предке формы, чтобы включить ее для формы.

<div ... data-enhance ...>
    <form ...>
        <!-- NOT enhanced -->
    </form>
</div>

Расширенные записи форм работают только с Blazor конечными точками. Публикация расширенной формы в непредназначенную Blazor точку приводит к ошибке.

Чтобы отключить расширенную навигацию, выполните приведенные действия.

  • Удалите параметр EditForm из элемента формы (или задайте для него значение Enhance: false).
  • Для HTML <form>удалите data-enhance атрибут из элемента формы (или задайте для него falseзначение : data-enhance="false").

BlazorУлучшенная навигация и обработка форм могут отменить динамические изменения в DOM, если обновленное содержимое не является частью рендеринга сервера. Чтобы сохранить содержимое элемента, используйте data-permanent атрибут.

В следующем примере содержимое <div> элемента динамически обновляется скриптом при загрузке страницы:

<div data-permanent>
    ...
</div>

После запуска Blazor на клиенте можно использовать событие enhancedload для отслеживания расширенных обновлений страниц. Это позволяет повторно применять изменения к DOM, которые, возможно, были отменены расширенным обновлением страницы.

Blazor.addEventListener('enhancedload', () => console.log('Enhanced update!'));

Сведения об отключении расширенной навигации и глобальной обработки форм см. в разделе ASP.NET Core Blazor startup.

Улучшенная навигация с стати́ческой серверной отрисо́вкой (SSR) требует особого внимания при загрузке JavaScript. Дополнительные сведения см. в разделе ASP.NET Core Blazor JavaScript со статическим отображением на стороне сервера (статический SSR).

Создайте URI относительно префикса базового URI

Исходя из базового URI приложения, ToBaseRelativePath преобразует абсолютный URI в URI, относительно префикса базового URI.

Рассмотрим следующий пример:

try
{
    baseRelativePath = Navigation.ToBaseRelativePath(inputURI);
}
catch (ArgumentException ex)
{
    ...
}

Если базовый универсальный код ресурса (URI) приложения равен https://localhost:8000, то получаются следующие результаты:

  • https://localhost:8000/segment в inputURI приводит к baseRelativePath в segment.
  • https://localhost:8000/segment1/segment2 в inputURI приводит к baseRelativePath в segment1/segment2.

Если базовый URI приложения не соответствует базовому URI inputURI, будет выброшено исключение ArgumentException.

Передача https://localhost:8001/segment в inputURI приводит к следующему исключению:

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

NavigationManager использует History API браузера, чтобы поддерживать состояние истории навигации, связанное с каждым изменением местоположения, внесенным приложением. Поддержание состояния истории особенно полезно в сценариях внешнего перенаправления, например, при аутентификации пользователей с внешними поставщиками идентификационных данных. Дополнительные сведения см. в разделе Параметры навигации.

Передайте NavigationOptions в NavigateTo для управления следующими функциями:

  • ForceLoad: Обойти маршрутизацию на стороне клиента и принудительно загрузить новую страницу с сервера, независимо от того, обрабатывается ли URI маршрутизатором на стороне клиента. Значение по умолчанию — false.
  • ReplaceHistoryEntry: замените текущую запись в стеке истории. Если false, добавьте новую запись в стек истории. Значение по умолчанию — false.
  • HistoryEntryState: возвращает или задает состояние, которое будет добавлено к записи журнала.
Navigation.NavigateTo("/path", new NavigationOptions
{
    HistoryEntryState = "Navigation state"
});

Дополнительные сведения о получении состояния, связанного с записью в целевой истории при управлении изменениями местоположения, см. раздел Управление и предотвращение изменений местоположения.

Строки запросов

[SupplyParameterFromQuery] Используйте атрибут, чтобы указать, что параметр компонента поступает из строки запроса.

Используйте атрибут [SupplyParameterFromQuery] с атрибутом [Parameter], чтобы указать, что параметр компонента маршрутизируемого поступает из строки запроса.

Замечание

Параметры компонента могут принимать значения параметров запроса только в маршрутизируемых компонентах с директивой @page.

Только маршрутизируемые компоненты напрямую получают параметры запроса, чтобы избежать переключения потока сведений сверху вниз и очистить порядок обработки параметров, как платформы, так и приложения. Эта конструкция позволяет избежать тонких ошибок в коде приложения, написанном при условии определенного порядка обработки параметров. Вы можете задать настраиваемые каскадные параметры или напрямую назначить их обычным параметрам компонента, чтобы передать значения параметров запроса в нераутируемые компоненты.

Параметры компонента, предоставляемые из строки запроса, поддерживают следующие типы:

  • bool, DateTime, decimaldoublefloatGuidintlongstring.
  • Варианты предыдущих типов, допускающие значение NULL.
  • Массивы предыдущих типов, допускающие или не допускающие значение NULL.

Корректное инвариантное форматирование относительно культуры применяется для данного типа (CultureInfo.InvariantCulture).

Укажите свойство [SupplyParameterFromQuery] атрибута Name, чтобы использовать имя параметра запроса, отличное от имени параметра компонента. В следующем примере в C# именем параметра компонента является {COMPONENT PARAMETER NAME}. Для заполнителя {QUERY PARAMETER NAME} указано другое имя параметра запроса:

В отличие от свойств параметров компонента ([Parameter]), свойства [SupplyParameterFromQuery] можно пометить private, так же, как и public.

[SupplyParameterFromQuery(Name = "{QUERY PARAMETER NAME}")]
private string? {COMPONENT PARAMETER NAME} { get; set; }

Как и свойства параметра компонента ([Parameter]), [SupplyParameterFromQuery] свойства в .NET 6/7 всегда являются public свойствами. В .NET 8 и более поздних версиях свойства [SupplyParameterFromQuery] можно пометить как public или private.

[Parameter]
[SupplyParameterFromQuery(Name = "{QUERY PARAMETER NAME}")]
public string? {COMPONENT PARAMETER NAME} { get; set; }

В следующем примере с URL-адресом /search?filter=scifi%20stars&page=3&star=LeVar%20Burton&star=Gary%20Oldman:

  • Свойство Filter разрешается в значение scifi stars.
  • Свойство Page разрешается в значение 3.
  • Массив Stars заполняется из параметров запроса с именем star (Name = "star") и преобразуется в LeVar Burton и Gary Oldman.

Замечание

Параметры строки запроса в следующем маршрутизируемом компоненте страницы также работают в немаршрутизируемом компоненте без @page директивы (например, для общего Search.razor компонента, Search используемого в других компонентах).

Search.razor:

@page "/search"

<h1>Search Example</h1>

<p>Filter: @Filter</p>

<p>Page: @Page</p>

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

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

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

    [SupplyParameterFromQuery]
    private int? Page { get; set; }

    [SupplyParameterFromQuery(Name = "star")]
    private string[]? Stars { get; set; }
}

Search.razor:

@page "/search"

<h1>Search Example</h1>

<p>Filter: @Filter</p>

<p>Page: @Page</p>

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

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

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

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

    [Parameter]
    [SupplyParameterFromQuery(Name = "star")]
    public string[]? Stars { get; set; }
}

Используйте GetUriWithQueryParameter для добавления, изменения или удаления одного или нескольких параметров запроса для текущего URL-адреса:

@inject NavigationManager Navigation

...

Navigation.GetUriWithQueryParameter("{NAME}", {VALUE})

В предыдущем примере:

  • Заполнитель {NAME} указывает имя параметра запроса. Заполнитель {VALUE} указывает значение в качестве поддерживаемого типа. Поддерживаемые типы перечислены далее в этом разделе.
  • Строка возвращается, равная текущему URL-адресу с одним параметром:
    • Добавляется, если имя параметра запроса не существует в текущем URL-адресе.
    • Изменено на значение, указываемое в том случае, если параметр запроса существует в текущем URL-адресе.
    • Удаляется, если тип предоставленного значения является нулевой, а значение равно c0.
  • Корректное инвариантное форматирование относительно культуры применяется для данного типа (CultureInfo.InvariantCulture).
  • Имя и значение параметра запроса кодируются в виде URL-адреса.
  • Все значения с соответствующим именем параметра запроса заменяются при наличии нескольких экземпляров типа.

Вызовите GetUriWithQueryParameters для создания URI, созданного из Uri, с добавлением, обновлением или удалением нескольких параметров. Для каждого значения платформа использует value?.GetType() для определения времени выполнения запроса и выбирает правильный инвариантный формат для культуры. Платформа выдает ошибку для неподдерживаемых типов.

@inject NavigationManager Navigation

...

Navigation.GetUriWithQueryParameters({PARAMETERS})

Заполнитель {PARAMETERS} — это IReadOnlyDictionary<string, object>.

Передайте строку URI в GetUriWithQueryParameters, чтобы создать новый URI из предоставленного URI с несколькими добавленными, обновленными или удаленными параметрами. Для каждого значения платформа использует value?.GetType() для определения времени выполнения запроса и выбирает правильный инвариантный формат для культуры. Платформа выдает ошибку для неподдерживаемых типов. Поддерживаемые типы перечислены далее в этом разделе.

@inject NavigationManager Navigation

...

Navigation.GetUriWithQueryParameters("{URI}", {PARAMETERS})
  • Заполнитель {URI} — это URI с параметрами запроса или без них.
  • Заполнитель {PARAMETERS} — это IReadOnlyDictionary<string, object>.

Поддерживаемые типы идентичны поддерживаемым типам для ограничений маршрута:

  • bool
  • DateOnly
  • DateTime
  • decimal
  • double
  • float
  • Guid
  • int
  • long
  • string
  • TimeOnly

К поддерживаемым типам относятся:

  • Варианты предыдущих типов, допускающие значение NULL.
  • Массивы предыдущих типов, допускающие или не допускающие значение NULL.

Предупреждение

С помощью сжатия, который включен по умолчанию, избегайте создания безопасных (прошедших проверку подлинности или авторизованных) интерактивных компонентов на стороне сервера, отрисовывающих данные из ненадежных источников. Ненадежные источники включают параметры маршрута, строки запроса, данные из обмена JS и любой другой источник данных, которые сторонний пользователь может контролировать (базы данных, внешние сервисы). Для получения дополнительной информации смотрите руководство по ASP.NET CoreBlazorSignalR и руководство по уменьшению угроз при интерактивной серверной обработке на стороне ASP.NET CoreBlazor.

Замена значения параметра запроса, если параметр существует

Navigation.GetUriWithQueryParameter("full name", "Morena Baccarin")
Текущий URL-адрес Созданный URL-адрес
scheme://host/?full%20name=David%20Krumholtz&age=42 scheme://host/?full%20name=Morena%20Baccarin&age=42
scheme://host/?fUlL%20nAmE=David%20Krumholtz&AgE=42 scheme://host/?full%20name=Morena%20Baccarin&AgE=42
scheme://host/?full%20name=Jewel%20Staite&age=42&full%20name=Summer%20Glau scheme://host/?full%20name=Morena%20Baccarin&age=42&full%20name=Morena%20Baccarin
scheme://host/?full%20name=&age=42 scheme://host/?full%20name=Morena%20Baccarin&age=42
scheme://host/?full%20name= scheme://host/?full%20name=Morena%20Baccarin

Добавление параметра запроса и значения, если параметр не существует

Navigation.GetUriWithQueryParameter("name", "Morena Baccarin")
Текущий URL-адрес Созданный 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

Удаление параметра запроса, если значение параметра — null

Navigation.GetUriWithQueryParameter("full name", (string)null)
Текущий URL-адрес Созданный 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/

Добавление, обновление и удаление параметров запроса

В следующем примере :

  • name удаляется при наличии.
  • age добавляется со значением 25 (int), если отсутствует. При наличии age обновляется и получает значение 25.
  • eye color добавляется или обновляется до значения green.
Navigation.GetUriWithQueryParameters(
    new Dictionary<string, object?>
    {
        ["name"] = null,
        ["age"] = (int?)25,
        ["eye color"] = "green"
    })
Текущий URL-адрес Созданный 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

Поддержка перечислимых значений

В следующем примере :

  • full name добавляется или обновляется до Morena Baccarin, одного значения.
  • Параметры ping добавляются или заменяются параметрами 35, 16, 87 и 240.
Navigation.GetUriWithQueryParameters(
    new Dictionary<string, object?>
    {
        ["full name"] = "Morena Baccarin",
        ["ping"] = new int?[] { 35, 16, null, 87, 240 }
    })
Текущий URL-адрес Созданный 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

Для перехода с помощью добавленной или измененной строки запроса передайте созданный URL-адрес в NavigateTo.

В следующем примере производится вызов:

  • GetUriWithQueryParameter для добавления или замены параметра запроса name с помощью значения Morena Baccarin.
  • Вызывает NavigateTo, чтобы активировать навигацию по новому URL-адресу.
Navigation.NavigateTo(
    Navigation.GetUriWithQueryParameter("name", "Morena Baccarin"));

Строка запроса получается из свойства NavigationManager.Uri.

@inject NavigationManager Navigation

...

var query = new Uri(Navigation.Uri).Query;

Для анализа параметров строки запроса можно воспользоваться URLSearchParams в сочетании со взаимодействием JavaScript (JS):

export createQueryString = (string queryString) => new URLSearchParams(queryString);

Дополнительные сведения об изоляции JavaScript с модулями JavaScript см. в статье Вызов функций JavaScript из методов .NET в ASP.NET Core Blazor.

Перейдите к именованному элементу с использованием следующих подходов с хэш-ссылкой на элемент #. Маршруты к элементам в компоненте и маршруты к элементам во внешних компонентах используют корневой относительный путь. Начальная косая черта (/) является необязательной.

Примеры для каждого подхода демонстрируют навигацию к элементу с id и targetElement в компоненте Counter.

  • Якорный элемент (<a>) с href:

    <a href="/counter#targetElement">
    
  • NavLink компонент с параметром href:

    <NavLink href="/counter#targetElement">
    
  • NavigationManager.NavigateTo передача относительного URL-адреса:

    Navigation.NavigateTo("/counter#targetElement");
    

В следующем примере показан переход к именованным заголовкам H2 внутри компонента и к внешним компонентам.

В компонентах Home (Home.razor) и Counter (Counter.razor) поместите следующую разметку в нижней части существующей разметки компонента, чтобы использовать их в качестве целей навигации. <div> создает искусственное вертикальное пространство для демонстрации поведения прокрутки браузера.

<div class="border border-info rounded bg-info" style="height:500px"></div>

<h2 id="targetElement">Target H2 heading</h2>
<p>Content!</p>

Добавьте следующий FragmentRouting компонент в приложение.

FragmentRouting.razor:

@page "/fragment-routing"
@inject NavigationManager Navigation

<PageTitle>Fragment routing</PageTitle>

<h1>Fragment routing to named elements</h1>

<ul>
    <li>
        <a href="/fragment-routing#targetElement">
            Anchor in this component
        </a>
    </li>
    <li>
        <a href="/#targetElement">
            Anchor to the <code>Home</code> component
        </a>
    </li>
    <li>
        <a href="/counter#targetElement">
            Anchor to the <code>Counter</code> component
        </a>
    </li>
    <li>
        <NavLink href="/fragment-routing#targetElement">
            Use a `NavLink` component in this component
        </NavLink>
    </li>
    <li>
        <button @onclick="NavigateToElement">
            Navigate with <code>NavigationManager</code> to the 
            <code>Counter</code> component
        </button>
    </li>
</ul>

<div class="border border-info rounded bg-info" style="height:500px"></div>

<h2 id="targetElement">Target H2 heading</h2>
<p>Content!</p>

@code {
    private void NavigateToElement()
    {
        Navigation.NavigateTo("/counter#targetElement");
    }
}

Управление и предотвращение изменений местоположения

RegisterLocationChangingHandler: регистрирует обработчик для обработки входящих событий навигации. Контекст обработчика, предоставляемый LocationChangingContext, включает следующие свойства:

  • TargetLocation: возвращает целевое местоположение.
  • HistoryEntryState: возвращает состояние, связанное с записью в целевой истории.
  • IsNavigationIntercepted: возвращает значение, указывающее, была ли навигация перехвачена из ссылки.
  • CancellationToken: возвращает значение CancellationToken, определяющее, была ли отменена навигация, например, чтобы определить, активировал ли пользователь другую навигацию.
  • PreventNavigation: вызывается, чтобы предотвратить продолжение навигации.

Компонент может зарегистрировать несколько обработчиков изменения местоположения в методе жизненного цикла. Навигация вызывает все обработчики изменения расположения, зарегистрированные во всем приложении (между несколькими компонентами), и любая внутренняя навигация выполняет их одновременно. В дополнение к NavigateTo обработчики вызываются в следующих случаях:

  • При нажатии на внутренние ссылки, которые указывают на URL-адреса в базовом пути приложения.
  • При навигации с помощью кнопок "Вперед" и "Назад" в браузере.

Обработчики исполняются исключительно для навигации внутри приложения. Если пользователь нажимает на ссылку, которая ведет на другой сайт, или вручную вводит в адресную строку адрес другого сайта, обработчики изменения расположения не выполняются.

Реализуйте IDisposable и удалите зарегистрированные обработчики, чтобы отменить их регистрацию. Дополнительные сведения см. в разделе ASP.NET Core Razor утилизации компонентов.

Это важно

Не пытайтесь выполнять задачи очистки DOM с помощью взаимодействия JavaScript (JS) при обработке изменений местоположения. MutationObserver Используйте шаблон в JS клиенте. Дополнительные сведения см. в разделе Интероперабельность JavaScript в ASP.NET Core Blazor (JS interop).

В следующем примере обработчик изменения расположения регистрируется для событий навигации.

NavHandler.razor:

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

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

@code {
    private IDisposable? registration;

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

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

        return ValueTask.CompletedTask;
    }

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

Поскольку внутренняя навигация может быть отменена асинхронно, могут произойти несколько перекрывающихся вызовов зарегистрированных обработчиков. Например, несколько вызовов обработчика могут возникать, когда пользователь быстро нажимает кнопку "Назад" на странице или когда пользователь выбирает несколько ссылок перед выполнением навигации. Ниже приведена сводка по логике асинхронной навигации:

  • Если регистрируются какие-либо обработчики изменения местоположения, вся навигация изначально отменяется, а затем воспроизводится, если навигация не отменена.
  • Если выполняются перекрывающиеся запросы навигации, последний запрос всегда отменяет предыдущие запросы, что означает следующее:
    • Приложение может рассматривать несколько нажатий кнопки "Назад" и "Вперед" как одно.
    • Если пользователь выбирает несколько ссылок до завершения процесса навигации, последняя выбранная ссылка определяет навигацию.

Дополнительные сведения о передаче NavigationOptions в NavigateTo для управления записями и состоянием стека журнала навигации см. в разделе Варианты навигации.

Для получения дополнительных примеров кода см. NavigationManagerComponent в BasicTestApp (справочный источник dotnet/aspnetcore).

Замечание

Ссылки в документации на исходный код .NET обычно загружают ветку репозитория по умолчанию, которая представляет текущую разработку для следующего выпуска .NET. Чтобы выбрать тег для конкретного релиза, используйте раскрывающийся список Переключение ветвей или тегов. Дополнительные сведения см. в статье Выбор тега версии исходного кода ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Компонент NavigationLock перехватывает события навигации до тех пор, пока он отображается, фактически "блокирует" любую заданную навигацию до тех пор, пока не будет принято решение о продолжении или отмене. Используйте NavigationLock при перехвате навигации в течение времени существования компонента.

Параметры NavigationLock:

  • ConfirmExternalNavigation задает диалоговое окно браузера, чтобы предложить пользователю подтвердить или отменить внешнюю навигацию. Значение по умолчанию — false. Для отображения диалогового окна подтверждения требуется начальное взаимодействие пользователя со страницей перед запуском внешней навигации по URL-адресу в адресной строке браузера. Дополнительные сведения о требовании взаимодействия см. в разделе "Окно: beforeunload событие".
  • OnBeforeInternalNavigation задает обратный вызов для событий внутренней навигации.

В следующем компоненте NavLock:

NavLock.razor:

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

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

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

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

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

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

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

Для дополнительных примеров кода см. компонент ConfigurableNavigationLock в BasicTestApp (справочный источник dotnet/aspnetcore).

NavLink Записи компонентов можно динамически создавать из компонентов приложения с помощью отражения. В следующем примере показан общий подход к дальнейшей настройке.

Для следующей демонстрации для компонентов приложения используется согласованное стандартное соглашение об именовании:

  • Имена файлов маршрутизируемых компонентов используют регистр Pascal†, например Pages/ProductDetail.razor.
  • Пути к файлам маршрутизируемого компонента соответствуют их URL-адресам в формате 'kebab case', с дефисами, разделяющими слова в шаблоне маршрута компонента. Например, компонент ProductDetail с шаблоном маршрута /product-detail (@page "/product-detail") запрашивается в браузере по относительному URL-адресу /product-detail.

†Регистр Pascal (верхний горбатый регистр) — это соглашение об именовании без пробелов и знаков препинания, где все слова, включая первое, пишутся с прописной буквы.
'Kebab case — это соглашение об именовании без пробелов и препинания, которое использует строчные буквы и дефисы между словами.

В разметке компонента Razor (NavMenu) на странице по умолчанию NavMenu.razor компоненты Home добавляются из коллекции:

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

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

    </nav>
</div>

Метод GetRoutableComponents в блоке @code :

public IEnumerable<string> GetRoutableComponents() => 
    Assembly.GetExecutingAssembly()
        .ExportedTypes
        .Where(t => t.IsSubclassOf(typeof(ComponentBase)))
        .Where(c => c.GetCustomAttributes(inherit: true)
                     .OfType<RouteAttribute>()
                     .Any())
        .Where(c => c.Name != "Home" && c.Name != "Error")
        .OrderBy(o => o.Name)
        .Select(c => c.Name);

Приведенный выше пример не содержит следующие страницы в отрисованном списке компонентов:

  • Home страница: Эта страница перечислена отдельно от автоматически сгенерированных ссылок, поскольку она должна находиться в верхней части списка и устанавливать параметр Match.
  • Error страница ошибок: страница ошибок открывается только фреймворком и не должна быть указана.

Для демонстрации предыдущего кода в примере приложения получите Blazor Web App или Blazor WebAssembly пример приложения.