ASP.NET 核心 Blazor 导航

注释

此版本不是本文的最新版本。 有关当前版本,请参阅 本文的 .NET 10 版本

警告

此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 有关当前版本,请参阅 本文的 .NET 10 版本

本文介绍如何在Blazor中触发和处理页面导航。 虽然用户可以使用普通 HTML 链接在不同页面之间导航,但 Blazor 可以增强应用程序中的导航,以避免完整页面重载并提供更流畅的体验。 使用 NavLink 组件创建导航链接,当链接与当前页匹配时自动应用样式设置。 对于 C# 代码中的编程导航和 URI 管理,请使用 NavigationManager 该服务。

本文介绍如何在Blazor中触发和处理页面导航。 使用 NavLink 组件创建导航链接,当链接与当前页匹配时自动应用样式设置。 对于 C# 代码中的编程导航和 URI 管理,请使用 NavigationManager 该服务。

重要

本文中的代码示例展示了对 Navigation 调用的方法,这是在类和组件中注入的 NavigationManager

创建导航链接时,请使用 NavLink 组件代替 HTML 超链接元素 (<a>)。 NavLink 组件的行为方式类似于 <a> 元素,但它根据其 active 是否与当前 URL 匹配来切换 href CSS 类。 active 类可帮助用户了解所显示导航链接中的哪个页面是活动页面。 也可以选择将 CSS 类名分配到 NavLink.ActiveClass,以便在当前路由与 href 匹配时将自定义 CSS 类应用到呈现的链接。

Blazor 应用中的 NavMenu 组件(NavMenu.razor),这是从 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 匹配,并且仅在应用的默认基路径(active)处接收 / CSS 类。 当用户访问Counter组件时,第二个NavLink接收active/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> 属性:

其他 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>
}

在这种情况下,任何在子内容中使用循环变量的子组件,而不仅仅是组件,都需要使用索引变量。

或者,将 foreach 循环用于 Enumerable.Range

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

URI 和导航状态帮助程序

在 C# 代码中使用 NavigationManager 来来管理 URI 和导航。 NavigationManager 提供下表所示的事件和方法。

成员 Description
Uri 获取当前绝对 URI。
BaseUri 获取可在相对 URI 路径之前添加用于生成绝对 URI 的基 URI(带有尾部反斜杠)。 通常,BaseUri 对应于文档的 href 元素(<base>)上的 <head> 属性。
NavigateTo 导航到指定的 URI。 如果 forceLoadfalse,则:
  • 而且当前 URL 中可使用增强型导航,Blazor 的增强型导航已激活。
  • 否则,Blazor 会对请求的 URL 执行整页重载。
如果 forceLoadtrue,则:
  • 客户端路由会被绕过。
  • 无论 URI 是否通常由客户端交互式路由器处理,浏览器都必须从服务器加载新页面。

有关详细信息,请参阅增强型导航和表单处理部分。

如果 replacetrue,则替换浏览器历史记录中的当前 URI,而不是将新的 URI 推送到历史记录堆栈中。

LocationChanged 导航位置更改时触发的事件。 有关详细信息,请参阅位置更改部分。
NotFound 调用以处理请求资源未找到的场景。 有关详细信息,请参阅 “找不到响应 ”部分。
ToAbsoluteUri 将相对 URI 转换为绝对 URI。
ToBaseRelativePath 根据应用的基 URI,将绝对 URI 转换为相对于基本 URI 前缀的 URI。 有关示例,请参阅生成相对于基 URI 前缀的 URI 部分。
RegisterLocationChangingHandler 注册一个处理程序来处理传入的导航事件。 调用 NavigateTo 总是会调用处理程序。
GetUriWithQueryParameter 返回通过更新 NavigationManager.Uri 来构造的 URI(添加、更新或删除单个参数)。 有关详细信息,请参阅查询字符串部分。
成员 Description
Uri 获取当前绝对 URI。
BaseUri 获取可在相对 URI 路径之前添加用于生成绝对 URI 的基 URI(带有尾部反斜杠)。 通常,BaseUri 对应于文档的 href 元素(<base>)上的 <head> 属性。
NavigateTo 导航到指定的 URI。 如果 forceLoadfalse,则:
  • 而且当前 URL 中可使用增强型导航,Blazor 的增强型导航已激活。
  • 否则,Blazor 会对请求的 URL 执行整页重载。
如果 forceLoadtrue,则:
  • 客户端路由会被绕过。
  • 无论 URI 是否通常由客户端交互式路由器处理,浏览器都必须从服务器加载新页面。

有关详细信息,请参阅增强型导航和表单处理部分。

如果 replacetrue,则替换浏览器历史记录中的当前 URI,而不是将新的 URI 推送到历史记录堆栈中。

LocationChanged 导航位置更改时触发的事件。 有关详细信息,请参阅位置更改部分。
ToAbsoluteUri 将相对 URI 转换为绝对 URI。
ToBaseRelativePath 根据应用的基 URI,将绝对 URI 转换为相对于基本 URI 前缀的 URI。 有关示例,请参阅生成相对于基 URI 前缀的 URI 部分。
RegisterLocationChangingHandler 注册一个处理程序来处理传入的导航事件。 调用 NavigateTo 总是会调用处理程序。
GetUriWithQueryParameter 返回通过更新 NavigationManager.Uri 来构造的 URI(添加、更新或删除单个参数)。 有关详细信息,请参阅查询字符串部分。
成员 Description
Uri 获取当前绝对 URI。
BaseUri 获取可在相对 URI 路径之前添加用于生成绝对 URI 的基 URI(带有尾部反斜杠)。 通常,BaseUri 对应于文档的 href 元素(<base>)上的 <head> 属性。
NavigateTo 导航到指定的 URI。 如果 forceLoadtrue,则:
  • 客户端路由会被绕过。
  • 无论 URI 是否通常由客户端路由器处理,浏览器都必须从服务器加载新页面。
如果 replacetrue,则替换浏览器历史记录中的当前 URI,而不是将新的 URI 推送到历史记录堆栈中。
LocationChanged 导航位置更改时触发的事件。 有关详细信息,请参阅位置更改部分。
ToAbsoluteUri 将相对 URI 转换为绝对 URI。
ToBaseRelativePath 根据应用的基 URI,将绝对 URI 转换为相对于基本 URI 前缀的 URI。 有关示例,请参阅生成相对于基 URI 前缀的 URI 部分。
RegisterLocationChangingHandler 注册一个处理程序来处理传入的导航事件。 调用 NavigateTo 总是会调用处理程序。
GetUriWithQueryParameter 返回通过更新 NavigationManager.Uri 来构造的 URI(添加、更新或删除单个参数)。 有关详细信息,请参阅查询字符串部分。
成员 Description
Uri 获取当前绝对 URI。
BaseUri 获取可在相对 URI 路径之前添加用于生成绝对 URI 的基 URI(带有尾部反斜杠)。 通常,BaseUri 对应于文档的 href 元素(<base>)上的 <head> 属性。
NavigateTo 导航到指定的 URI。 如果 forceLoadtrue,则:
  • 客户端路由会被绕过。
  • 无论 URI 是否通常由客户端路由器处理,浏览器都必须从服务器加载新页面。
如果 replacetrue,则替换浏览器历史记录中的当前 URI,而不是将新的 URI 推送到历史记录堆栈中。
LocationChanged 导航位置更改时触发的事件。 有关详细信息,请参阅位置更改部分。
ToAbsoluteUri 将相对 URI 转换为绝对 URI。
ToBaseRelativePath 根据应用的基 URI,将绝对 URI 转换为相对于基本 URI 前缀的 URI。 有关示例,请参阅生成相对于基 URI 前缀的 URI 部分。
GetUriWithQueryParameter 返回通过更新 NavigationManager.Uri 来构造的 URI(添加、更新或删除单个参数)。 有关详细信息,请参阅查询字符串部分。
成员 Description
Uri 获取当前绝对 URI。
BaseUri 获取可在相对 URI 路径之前添加用于生成绝对 URI 的基 URI(带有尾部反斜杠)。 通常,BaseUri 对应于文档的 href 元素(<base>)上的 <head> 属性。
NavigateTo 导航到指定的 URI。 如果 forceLoadtrue,则:
  • 客户端路由会被绕过。
  • 无论 URI 是否通常由客户端路由器处理,浏览器都必须从服务器加载新页面。
LocationChanged 导航位置更改时触发的事件。
ToAbsoluteUri 将相对 URI 转换为绝对 URI。
ToBaseRelativePath 根据应用的基 URI,将绝对 URI 转换为相对于基本 URI 前缀的 URI。 有关示例,请参阅生成相对于基 URI 前缀的 URI 部分。

位置更改

对于 LocationChanged 事件,LocationChangedEventArgs 提供了下述导航事件信息:

以下 组件:

  • 使用 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 核心 Razor 组件处置

对于静态服务器端呈现(静态 SSR)期间的重定向, NavigationManager 依赖于引发 NavigationException 框架捕获的重定向,该框架将错误转换为重定向。 调用后 NavigateTo 存在的代码未调用。 使用 Visual Studio 时,调试器会中断异常,因此当 Visual Studio UI 中处理 此异常类型时 ,需要取消选中“中断”复选框,以避免调试器停止将来重定向。

可以使用 <BlazorDisableThrowNavigationException> MSBuild 属性设置为 true 在应用的项目文件中选择加入不再引发 NavigationException。 此外,在调用 NavigateTo 后执行代码时,它之前不会运行。 在 .NET 10 或更高版本 Blazor Web App 的项目模板中默认启用此行为:

<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>

注释

在 .NET 10 或更高版本中,可以通过将 MSBuild 属性设置为NavigationException<BlazorDisableThrowNavigationException>应用的项目文件中来选择不引发true该属性。 若要利用新的 MSBuild 属性和行为,请将应用升级到 .NET 10 或更高版本。

找不到响应

NavigationManager 提供了一种机制 NotFound,用于在静态服务器端渲染(静态 SSR)或全局交互式渲染过程中,处理请求资源未找到的情况。

  • 静态 SSR:调用 NavigationManager.NotFound 会将 HTTP 状态代码设置为 404。

  • 交互式呈现:向路由器(Blazor)发出Router信号以呈现“找不到”内容。

  • 流式呈现:如果 增强的导航 处于活动状态,流式呈现将在找不到内容时无需重新加载页面。 当增强的导航被阻止时,框架会重定向到“找不到”内容,并刷新页面。

注释

以下讨论提到可以将“未找到” Razor 组件分配给 Router 组件的 NotFoundPage 参数。 该参数与NavigationManager.NotFound协同作用,本部分稍后将更具体地描述。

流式呈现只能呈现具有路由的组件,例如 NotFoundPage 分配(NotFoundPage="...")或 状态代码页重新执行中间件页分配UseStatusCodePagesWithReExecute)。 DefaultNotFound 404 内容(“Not found”纯文本)没有路由,因此无法在流呈现期间使用。

注释

.NET 10 或更高版本不支持“未找到”呈现片段(<NotFound>...</NotFound>)。

NavigationManager.NotFound 内容渲染使用以下技术,无论响应是否已开始(按顺序):

  • 如果 NotFoundEventArgs.Path 已设置,则呈现已分配页面的内容。
  • 如果 Router.NotFoundPage 已设置,则呈现分配的页面。
  • 已配置的状态码页面重执行中间件页面。
  • 如果没有采用上述任何方法,则不采取任何措施。

在处理浏览器地址路由问题(如 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>

组件 RoutesRoutes.razor) 通过NotFound参数将NotFoundPage组件设置为“未找到”页:

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

在以下示例组件中:

  • NotFoundContext服务和NavigationManager一起注入。
  • OnInitializedAsync 中,HandleNotFound 是分配给 OnNotFound 事件的事件处理程序。 HandleNotFound 调用 NotFoundContext.UpdateContext 为组件中 NotFound 找不到的内容设置标题和消息。
  • 组件通常使用路由参数中的 ID 从数据存储(如数据库)获取电影或用户。 在以下示例中,不会返回任何实体(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组件。 以下示例中作为路由参数传入的实体 ID 是模拟值,不影响实际行为,因为组件并未使用这些值,而是模拟找不到电影或用户的场景。

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 App 可为页面导航和表单处理请求提供两种路由:

  • 普通导航(跨文档导航):为请求 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-nav 属性设置为 false

如果启用了服务器端路由和增强型导航,则仅针对从交互式运行时启动的编程导航调用位置更改处理程序。 在未来版本中,其他类型的导航(如跟随链接)也可能调用位置更改处理程序。

当出现增强型导航时,通常会调用向交互式服务器和 WebAssembly 运行时注册的 LocationChanged 事件处理程序。 在某些情况下,位置更改处理程序可能无法拦截增强型导航。 例如,用户可能在交互式运行时可用之前切换到另一个页面。 因此,应用逻辑不依赖于调用位置更改处理程序非常重要,因为无法保证处理程序的执行。

调用 NavigateTo 时:

  • 如果 forceLoadfalse,这是默认设置:
    • 而且当前 URL 中可使用增强型导航,Blazor 的增强型导航已激活。
    • 否则,Blazor 会对请求的 URL 执行整页重载。
  • 如果 forceLoadtrue: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 参数(或将其设置为 falseEnhance="false")。
  • 对于 HTML <form>,请从表单元素中移除 data-enhance 特性(或将其设置为 falsedata-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 启动

加载 JavaScript 时,需要特别注意采用静态服务器端呈现(静态 SSR)的增强型导航。 有关详细信息,请参阅 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 会导致 baseRelativePathsegment
  • https://localhost:8000/segment1/segment2 中传递 inputURI 会导致 baseRelativePathsegment1/segment2

如果应用程序的基 URI 与 inputURI 的基 URI 不匹配,则会抛出 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 使用浏览器的历史记录 API 来维护与应用程序所做的每个位置更改相关的导航历史记录状态。 维护历史状态在外部重定向场景中特别有用,例如 使用外部标识提供者对用户进行身份验证时。 有关详细信息,请参阅导航选项部分。

NavigationOptions 传递给 NavigateTo 以控制以下行为:

  • ForceLoad:绕过客户端路由并强制浏览器从服务器加载新页面,无论 URI 是否由客户端路由器处理。 默认值为 false
  • ReplaceHistoryEntry:替换历史记录堆栈中的当前条目。 如果为 false,则将新条目附加到历史记录堆栈。 默认值为 false
  • HistoryEntryState:获取或设置要附加到历史记录条目的状态。
Navigation.NavigateTo("/path", new NavigationOptions
{
    HistoryEntryState = "Navigation state"
});

有关在处理位置更改时获取与目标历史记录条目关联的状态的详细信息,请参阅处理/防止位置更改部分。

查询字符串

使用 [SupplyParameterFromQuery] 属性指定组件参数来自查询字符串。

[SupplyParameterFromQuery] 属性[Parameter] 属性结合使用,指定可路由组件的组件参数可以来自查询字符串

注释

组件参数只能在具有 @page 指令的可路由组件中接收查询参数值。

只有可路由组件直接接收查询参数,以避免破坏自上而下的信息流,并通过框架和应用使参数处理顺序清晰明确。 此设计可以避免假设特定参数处理顺序而编写的应用程序代码中产生的微妙错误。 你可以自由地定义自定义级联参数或直接分配给常规组件参数,以便将查询参数值传递给不可路由的组件。

查询字符串提供的组件参数支持以下类型:

  • boolDateTimedecimaldoublefloatGuidintlongstring
  • 上述类型的可为空变体。
  • 上述类型的数组,无论它们是可为空还是不可为空。

正确的与文化无关的格式设置适用于给定类型 (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] 属性可以标记为 publicprivate

[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 BurtonGary 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 中,则更新为提供的值。
    • 如果所提供值的类型是可空且值为null,则将其删除。
  • 正确的与文化无关的格式设置适用于给定类型 (CultureInfo.InvariantCulture)。
  • 查询参数名称和值是 URL 编码的。
  • 如果存在类型的多个实例,则所有具有匹配查询参数名称的值都会被替换。

调用 GetUriWithQueryParameters 以创建从 Uri 构造的 URI(添加、更新或删除多个参数)。 对于每个值,框架使用 value?.GetType() 来确定每个查询参数的运行时类型,并选择正确的与文化无关的格式设置。 对于不支持的类型,框架将引发错误。

@inject NavigationManager Navigation

...

Navigation.GetUriWithQueryParameters({PARAMETERS})

{PARAMETERS} 占位符是一个 IReadOnlyDictionary<string, object>

GetUriWithQueryParameters 传递 URI 字符串,以从提供的 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

支持的类型包括:

  • 上述类型的可为空变体。
  • 上述类型的数组,无论它们是可为空还是不可为空。

警告

使用默认启用的压缩时,避免创建安全(经过身份验证/授权)的交互式服务器端组件来呈现来自不受信任的源的数据。 不受信任的源包括路由参数、查询字符串、来自 JS 互操作的数据,以及第三方用户可以控制的任何其他数据源(数据库、外部服务)。 有关详细信息,请参阅 ASP.NET Core BlazorSignalR 指南ASP.NET Core Blazor 交互式服务器端呈现的威胁缓解指南

如果参数存在,则替换查询参数值

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 参数或将其替换为 351687240
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

下面的示例调用:

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 隔离的详细信息,请参阅在 ASP.NET Core Blazor 中从 .NET 方法调用 JavaScript 函数

使用以下方法,通过对命名元素的哈希处理 (#) 引用,导航到该元素。 对组件中的元素路由,以及对外部组件中的元素路由,均使用根相对路径。 前导正斜杠 (/) 是可选的。

以下每种方法的示例均演示了如何导航到 id 组件中 targetElementCounter 的元素:

  • <a> 的 Anchor 元素 (href):

    <a href="/counter#targetElement">
    
  • NavLinkhref 组件:

    <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 提供的处理程序上下文包括以下属性:

一个组件可以在 OnAfterRender{Async} 生命周期方法中注册多个位置更改处理程序。 导航调用在整个应用(跨多个组件)中注册的所有位置更改处理程序,并且任何内部导航都并行执行它们。 除了调用 NavigateTo 处理程序之外:

  • 选择内部链接时,这些链接指向应用基路径下的 URL。
  • 在浏览器中使用前进和后退按钮进行导航时。

处理程序仅针对应用中的内部导航执行。 如果用户选择导航到另一个站点的链接或手动将地址栏更改为另一个站点,则不会执行位置更改处理程序。

实现 IDisposable 并释放已注册的处理程序以取消注册它们。 有关详细信息,请参阅 ASP.NET Core Razor 组件处置

重要

处理位置更改时,请勿尝试通过 JavaScript (JS) 互操作执行 DOM 清理任务。 在客户端上使用 MutationObserver 中的 模式。 有关详细信息,请参阅 ASP.NET Core BlazorJavaScript 互操作性(JS 互操作)

在以下示例中,为导航事件注册了位置更改处理程序。

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 以控制导航历史记录堆栈的条目和状态的详细信息,请参阅导航选项部分。

有关其他示例代码,请参阅 NavigationManagerComponentBasicTestApp 参考源)中的 dotnet/aspnetcore

注释

指向 .NET 引用源的文档链接通常会加载存储库的默认分支,该分支代表正在进行的 .NET 下一版本的开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉菜单。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)

只要呈现导航事件,NavigationLock 组件就会将其拦截,除非决定继续或取消,否则将一直有效“锁定”任何给定的导航。 当导航拦截的范围可以限定为组件的生存期时,使用 NavigationLock

NavigationLock 参数:

在下面的 NavLock 组件中:

  • 在成功导航到 https://www.microsoft.com 之前,用户必须尝试确认过 Microsoft 网站的链接可访问。
  • 如果用户拒绝通过生成 PreventNavigation

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

有关其他示例代码,请参阅 ConfigurableNavigationLockBasicTestApp 参考源)中的 dotnet/aspnetcore 组件

NavLink 组件条目可以通过反射从应用的组件动态创建。 以下示例演示了进一步自定义的一般方法。

在以下演示中,应用组件会使用一致的标准命名约定。

  • 可路由组件文件名使用 Pascal 命名法†,例如 Pages/ProductDetail.razor
  • 可路由组件的文件路径与其 kebab 命名法‡的 URL 匹配,组件的路由模板中各单词之间有连字符。 例如,一个具有路由模板 ProductDetail (/product-detail) 的 @page "/product-detail" 组件在浏览器中通过相对 URL /product-detail 被请求。

†Pascal 大小写(大写 camel 形式)是不带空格和标点符号的命名约定,其中每个单词的首字母大写(包括第一个单词)。
‡Kebab 大小写是一种命名约定,不使用空格和标点符号,它使用小写字母,且单词之间有短划线。

在默认 Razor 页面下的 NavMenu 组件 (NavMenu.razor) 的 Home 标记中,NavLink 组件从集合中添加:

<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 AppBlazor WebAssembly 示例应用