Маршрутизация ASP.NET Core Blazor

В этой статье описывается, как управлять маршрутизацией запросов и как использовать компонент NavLink для создания навигационных ссылок в приложениях Blazor.

Шаблоны маршрутов

Компонент Router позволяет выполнять маршрутизацию в компоненты Razor в приложении Blazor. Компонент Router используется в компоненте App приложений Blazor.

App.razor:

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <p>Sorry, there's nothing at this address.</p>
    </NotFound>
</Router>

Примечание

В выпуске ASP.NET Core 5.0.1 и дальнейших выпусках 5.x компонент Router содержит параметр PreferExactMatches со значением @true. Дополнительные сведения см. в разделе Миграция с ASP.NET Core 3,1 на 5,0.

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <p>Sorry, there's nothing at this address.</p>
    </NotFound>
</Router>

При компиляции компонента Razor (.razor) с директивой @page созданный класс компонента предоставляет атрибут RouteAttribute для указания шаблона маршрута.

При запуске приложения выполняется проверка сборки, указанной как AppAssembly маршрутизатора, для сбора сведений о маршрутах для компонентов приложения с RouteAttribute.

В среде выполнения компонент RouteView выполняет следующие операции:

  • получает RouteData от Router вместе со всеми параметрами маршрута;
  • преобразовывает указанный компонент для просмотра с его макетом, включая все вложенные макеты.

При необходимости укажите параметр DefaultLayout с классом макета для компонентов, которые не задают макет, с помощью директивы @layout. В шаблонах проекта Blazor платформы в качестве макета приложения по умолчанию укажите компонент MainLayout (Shared/MainLayout.razor). Дополнительные сведения о макетах см. в разделе <xref:blazor/layouts>.

Компоненты поддерживают несколько шаблонов маршрутов с помощью нескольких директив @page. Приведенный ниже пример компонента загружается при запросах к /BlazorRoute и /DifferentBlazorRoute.

Pages/BlazorRoute.razor:

@page "/BlazorRoute"
@page "/DifferentBlazorRoute"

<h1>Blazor routing</h1>
@page "/BlazorRoute"
@page "/DifferentBlazorRoute"

<h1>Blazor routing</h1>

Важно!

Для правильного разрешения URL-адресов приложение должно содержать тег <base> в файле wwwroot/index.html (Blazor WebAssembly) или файле Pages/_Host.cshtml (Blazor Server) с базовым путем к приложению, указанным в атрибуте href. Дополнительные сведения см. в разделе Размещение и развертывание ASP.NET Core Blazor.

Предоставление пользовательского содержимого, когда содержимое не найдено

Компонент Router позволяет приложению указать пользовательское содержимое, если содержимое для запрошенного маршрута не найдено.

В компоненте App задайте пользовательское содержимое в шаблоне NotFound компонента Router.

App.razor:

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <h1>Sorry</h1>
        <p>Sorry, there's nothing at this address.</p> b
    </NotFound>
</Router>

Примечание

В выпуске ASP.NET Core 5.0.1 и дальнейших выпусках 5.x компонент Router содержит параметр PreferExactMatches со значением @true. Дополнительные сведения см. в разделе Миграция с ASP.NET Core 3,1 на 5,0.

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <h1>Sorry</h1>
        <p>Sorry, there's nothing at this address.</p> b
    </NotFound>
</Router>

Теги <NotFound> могут содержать произвольные элементы, например другие интерактивные компоненты. Сведения о применении макета по умолчанию к содержимому NotFound см. в разделе <xref:blazor/layouts#apply-a-layout-to-arbitrary-content-layoutview-component>.

Маршрутизация к компонентам из нескольких сборок

Используйте параметр AdditionalAssemblies, чтобы указать дополнительные сборки для компонента Router, которые следует учитывать при поиске маршрутизируемых компонентов. В дополнение к сборке, указанной в AppAssembly, проверяются дополнительные сборки. В приведенном ниже примере Component1 представляет собой маршрутизируемый компонент, определенный в упоминаемой библиотеке классов компонентов. В приведенном ниже примере AdditionalAssemblies приводится поддержка маршрутизации для Component1.

App.razor:

<Router
    AppAssembly="@typeof(Program).Assembly"
    AdditionalAssemblies="new[] { typeof(Component1).Assembly }">
    @* ... Router component elements ... *@
</Router>

Примечание

В выпуске ASP.NET Core 5.0.1 и дальнейших выпусках 5.x компонент Router содержит параметр PreferExactMatches со значением @true. Дополнительные сведения см. в разделе Миграция с ASP.NET Core 3,1 на 5,0.

<Router
    AppAssembly="@typeof(Program).Assembly"
    AdditionalAssemblies="new[] { typeof(Component1).Assembly }">
    @* ... Router component elements ... *@
</Router>

Параметры маршрута

Маршрутизатор использует параметры маршрута для заполнения соответствующих параметров компонента с тем же именем. В именах параметров маршрута регистр не учитывается. В приведенном ниже примере параметр text присваивает значение сегмента маршрута свойству Text компонента. Если запрос выполняется для /RouteParameter/amazing, содержимое тега <h1> отображается как Blazor is amazing!.

Pages/RouteParameter.razor:

@page "/RouteParameter/{text}"

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

@code {
    [Parameter]
    public string Text { get; set; }
}
@page "/RouteParameter/{text}"

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

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

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

Pages/RouteParameter.razor:

@page "/RouteParameter/{text?}"

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

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

    protected override void OnInitialized()
    {
        Text = Text ?? "fantastic";
    }
}

Необязательные параметры не поддерживаются. В приведенном ниже примере применяются две директивы @page. Первая позволяет переходить к компоненту без параметра. Вторая директива присваивает значение параметра маршрута {text} свойству Text компонента.

Pages/RouteParameter.razor:

@page "/RouteParameter"
@page "/RouteParameter/{text}"

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

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

    protected override void OnInitialized()
    {
        Text = Text ?? "fantastic";
    }
}

Чтобы разрешить переход к тому же компоненту с другим значением необязательного параметра, используйте OnParametersSet вместо OnInitialized{Async}. Принимая во внимание предыдущий пример, используйте OnParametersSet, когда пользователь должен иметь возможность переходить от /RouteParameter к /RouteParameter/amazing или от /RouteParameter/amazing к /RouteParameter:

protected override void OnParametersSet()
{
    Text = Text ?? "fantastic";
}

Ограничения маршрута

Ограничение маршрута применяет сопоставление типов в сегменте маршрута к компоненту.

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

  • в URL-адресе запроса имеется сегмент маршрута Id;
  • сегмент Id имеет целочисленный тип (int).

Pages/User.razor:

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

<h1>User Id: @Id</h1>

@code {
    [Parameter]
    public int Id { get; set; }
}
@page "/user/{Id:int}"

<h1>User Id: @Id</h1>

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

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

Ограничение Пример Примеры совпадений Инвариант
culture
соответствие
bool {active:bool} true, FALSE Нет
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm Да
decimal {price:decimal} 49.99, -1,000.01 Да
double {weight:double} 1.234, -1,001.01e8 Да
float {weight:float} 1.234, -1,001.01e8 Да
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638, {CD2C1638-1638-72D5-1638-DEADBEEF1638} Нет
int {id:int} 123456789, -123456789 Да
long {ticks:long} 123456789, -123456789 Да

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

Ограничения маршрута, которые проверяют URL-адрес и могут быть преобразованы в тип CLR (например, int или DateTime), всегда используют инвариантные язык и региональные параметры. Эти ограничения предполагают, что URL-адрес является нелокализуемым.

Маршрутизация URL-адресов, содержащих точки

Для размещенных приложений Blazor WebAssembly и Blazor Server шаблон маршрута по умолчанию на стороне сервера предполагает, что если последний сегмент URL-адреса запроса содержит точку (.), то запрашивается файл. Например, URL-адрес https://localhost.com:5001/example/some.thing интерпретируется маршрутизатором как запрос файла с именем some.thing. Без дополнительной конфигурации приложение возвращает ответ 404 — Not Found (не найдено), если предполагалась маршрутизация some.thing к компоненту с директивой @page, а some.thing — это значение параметра маршрута. Чтобы использовать маршрут с одним параметром или несколькими, содержащими точку, в приложении необходимо настроить маршрут с помощью пользовательского шаблона.

Рассмотрим приведенный ниже компонент Example, который может получать параметр маршрута из последнего сегмента URL-адреса.

Pages/Example.razor:

@page "/example/{param?}"

<p>
    Param: @Param
</p>

@code {
    [Parameter]
    public string Param { get; set; }
}
@page "/example"
@page "/example/{param}"

<p>
    Param: @Param
</p>

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

Чтобы позволить приложению Server размещенного решения Blazor WebAssembly маршрутизировать запрос с точкой в параметре маршрута param, добавьте шаблон резервного маршрута к файлу с дополнительным параметром в Startup.Configure.

Startup.cs:

endpoints.MapFallbackToFile("/example/{param?}", "index.html");

Чтобы позволить приложению Blazor Server маршрутизировать запрос с точкой в параметре маршрута param, добавьте шаблон резервного маршрута к странице с дополнительным параметром в Startup.Configure.

Startup.cs:

endpoints.MapFallbackToPage("/example/{param?}", "/_Host");

Для получения дополнительной информации см. Маршрутизация в ASP.NET Core.

Параметры маршрута catch-all

В компонентах поддерживаются параметры маршрута catch-all, которые захватывают пути в нескольких папках.

Параметры маршрута catch-all

  • Его имя должно соответствовать имени сегмента маршрута. Регистр в имени не учитывается.
  • Тип string. Платформа не обеспечивает автоматическое приведение.
  • В конце URL-адреса.

Pages/CatchAll.razor:

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

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

Для URL-адреса /catch-all/this/is/a/test с шаблоном маршрута /catch-all/{*pageRoute} значение PageRoute равно this/is/a/test.

Косые черты и сегменты захваченного пути декодированы. Для шаблона маршрута /catch-all/{*pageRoute} URL-адрес /catch-all/this/is/a%2Ftest%2A возвращает this/is/a/test*.

Параметры маршрута catch-all поддерживаются в ASP.NET Core 5.0 и более поздних версий. Для получения дополнительных сведений выберите вариант этой статьи для версии 5.0.

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

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

Член Описание
Uri Возвращает текущий абсолютный URI.
BaseUri Получает базовый URI (с завершающей косой чертой), который можно добавить в начало относительных путей URI для получения абсолютного URI. Как правило, BaseUri соответствует атрибуту href элемента документа <base> в wwwroot/index.html (Blazor WebAssembly) или Pages/_Host.cshtml (Blazor Server).
NavigateTo Переходит по указанному URI. Если значение forceLoad равно true:
  • маршрутизация на стороне клиента обходится;
  • браузер принуждается к загрузке новой страницы с сервера, независимо от того, обрабатывается ли универсальный код ресурса клиентским маршрутизатором.
LocationChanged Событие, возникающее при изменении расположения навигации.
ToAbsoluteUri Преобразует относительный URI в абсолютный.
ToBaseRelativePath Если указан базовый URI (например, URI, возвращенный ранее BaseUri), преобразует абсолютный URI в URI относительно базового префикса.

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

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

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

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

      BlazorSample.Pages.Navigate: Information: URL of new location: https://localhost:5001/counter

Pages/Navigate.razor:

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

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

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

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

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

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

    public void Dispose()
    {
        NavigationManager.LocationChanged -= HandleLocationChanged;
    }
}
@page "/navigate"
@using Microsoft.Extensions.Logging 
@implements IDisposable
@inject ILogger<Navigate> Logger
@inject NavigationManager NavigationManager

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

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

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

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

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

    public void Dispose()
    {
        NavigationManager.LocationChanged -= HandleLocationChanged;
    }
}

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

Строка запроса и параметры анализа

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

@inject NavigationManager NavigationManager

...

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

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

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

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

<script>
  window.createQueryString = (queryString) => {
    return new URLSearchParams(queryString);
  };
</script>

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

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

Следующий компонент NavMenu создает панель навигации Bootstrap, которая демонстрирует использование компонентов NavLink:

Примечание

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

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

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

В предыдущем примере NavLink href="" элемента Home соответствует URL-адресу домашней страницы и получает только класс CSS active в URL-адресе базового пути приложения по умолчанию (например, https://localhost:5001/). Второй NavLink получает класс active, когда пользователь посещает любой URL-адрес с префиксом component (например, https://localhost:5001/component и https://localhost:5001/component/another-segment).

Дополнительные атрибуты компонента 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 = 0; c < 10; c++)
{
    var current = c;
    <li ...>
        <NavLink ... href="@c">
            <span ...></span> @current
        </NavLink>
    </li>
}

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

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

@foreach(var c in Enumerable.Range(0,10))
{
    <li ...>
        <NavLink ... href="@c">
            <span ...></span> @c
        </NavLink>
    </li>
}

Интеграция маршрутизации конечных точек ASP.NET Core

Этот раздел относится только к приложениям Blazor Server.

Blazor Server интегрирован с функцией маршрутизации конечных точек ASP.NET Core. Приложение ASP.NET Core настроено для приема входящих подключений для интерактивных компонентов с помощью MapBlazorHub в Startup.Configure.

Startup.cs:

Типичная конфигурация — маршрутизация всех запросов на страницу Razor, которая выступает в качестве узла для серверной части приложения Blazor Server. По соглашению страница узла обычно называется _Host.cshtml и находится в папке Pages приложения.

Маршрут, указанный в файле узла, называется резервным маршрутом, так как он работает с низким приоритетом в соответствии с правилами маршрутизации. Резервный маршрут используется, если другие маршруты не сопоставляются. Это позволяет приложению использовать другие контроллеры и страницы, не мешая маршрутизации компонента в приложении Blazor Server.

Сведения о настройке MapFallbackToPage для размещения сервера по некорневому URL-адресу см. в разделе Размещение и развертывание ASP.NET Core Blazor.