ASP.NET Core Blazor 路由和导航

注意

此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

对于当前版本,请参阅此文的 .NET 8 版本

本文介绍如何管理 Blazor 应用请求路由以及如何使用 NavLink 组件创建导航链接。

重要

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

静态路由与交互式路由

本部分适用于 BlazorWeb 应用。

如果未禁用预呈现,Blazor 路由器(Router 组件,Routes.razor 中的 <Router>)将在静态服务器端呈现(静态 SSR)期间执行到组件的静态路由。 这种类型的路由称为“静态路由”

Routes 组件分配交互式呈现模式时,Blazor 路由器将在服务器上的静态 SSR 及静态路由后变为交互式。 这种类型的路由称为“交互式路由”

静态路由器使用终结点路由和 HTTP 请求路径来确定要呈现的组件。 当路由器变为交互式路由器时,它将使用文档的 URL(浏览器地址栏中的 URL)来确定要呈现的组件。 这意味着,如果文档的 URL 动态更改为另一个有效的内部 URL,交互式路由器可以动态更改呈现的组件,并且该过程中无需执行 HTTP 请求来提取新页面的内容。

交互式路由还阻止预呈现,因为没有使用普通页面请求从服务器请求新页面内容。 有关详细信息,请参阅 Prerender ASP.NET Core Razor 组件

路由模板

Router 组件允许路由到 Razor 组件,并且位于应用的 Routes 组件 (Components/Routes.razor)。

Router 组件允许路由到 Razor 组件。 Router 组件在 App 组件 (App.razor) 中使用。

编译带有 @page 指令的 Razor 组件 (.razor) 时,将为生成的组件类提供一个 RouteAttribute 来指定组件的路由模板。

当应用启动时,将扫描指定为 路由器的 AppAssembly 的程序集,来收集具有 RouteAttribute 的应用组件的路由信息。

在运行时,RouteView 组件:

  • Router 接收 RouteData 以及所有路由参数。
  • 使用指定的组件的布局来呈现该组件,包括任何后续嵌套布局。

对于没有使用 @layout 指令指定布局的组件,可选择使用布局类指定一个 DefaultLayout 参数。 框架的 Blazor 项目模板会指定 MainLayout 组件 (MainLayout.razor) 作为应用的默认布局。 有关布局的详细信息,请参阅 ASP.NET Core Blazor 布局

组件支持使用多个 @page 指令的多个路由模板。 以下示例组件会对 /blazor-route/different-blazor-route 的请求进行加载。

BlazorRoute.razor

@page "/blazor-route"
@page "/different-blazor-route"

<PageTitle>Routing</PageTitle>

<h1>Routing Example</h1>

<p>
    This page is reached at either <code>/blazor-route</code> or 
    <code>/different-blazor-route</code>.
</p>
@page "/blazor-route"
@page "/different-blazor-route"

<h1>Blazor routing</h1>
@page "/blazor-route"
@page "/different-blazor-route"

<h1>Blazor routing</h1>
@page "/blazor-route"
@page "/different-blazor-route"

<h1>Blazor routing</h1>
@page "/blazor-route"
@page "/different-blazor-route"

<h1>Blazor routing</h1>

重要

若要正确解析 URL,应用必须包含 <base> 标记(<head> 内容的位置),并在 href 属性中指定应用基路径。 有关详细信息,请参阅托管和部署 ASP.NET Core Blazor

Router 不与查询字符串值交互。 若要使用查询字符串,请参阅查询字符串部分。

作为使用 @page 指令将路由模板指定为字符串文本的替代方法,可以使用 @attribute 指令指定基于常量的路由模板。

在以下示例中,组件中的 @page 指令替换为 @attribute 指令和 Constants.CounterRoute 中基于常量的路由模板,该模板在应用中的其他位置设置为“/counter”:

- @page "/counter"
+ @attribute [Route(Constants.CounterRoute)]

说明

随着 ASP.NET Core 5.0.1 的发布及任何附加 5.x 版本的推出,Router 组件包含 PreferExactMatches 参数(设置为 @true)。 有关详细信息,请参阅从 ASP.NET Core 3.1 迁移到 5.0

将元素聚焦到导航上

在页面之间进行导航后,FocusOnNavigate 组件基于 CSS 选择器将 UI 焦点设置到元素。

<FocusOnNavigate RouteData="routeData" Selector="h1" />

Router 组件导航到新页面时,FocusOnNavigate 组件将焦点设置到页面的顶层标题 (<h1>)。 这是一种常见策略,可确保在使用屏幕阅读器时公布页面导航。

在找不到内容时提供自定义内容

如果找不到所请求路由的内容,则 Router 组件允许应用指定自定义内容。

Router 组件的 NotFound 参数设置自定义内容:

<Router ...>
    ...
    <NotFound>
        ...
    </NotFound>
</Router>

任意项都可用作 NotFound 参数的内容,例如其他交互式组件。 若要将默认布局应用于 NotFound 内容,请参阅 ASP.NET Core Blazor 布局

重要

Blazor Web 应用不使用 NotFound 参数(<NotFound>...</NotFound> 标记),但该参数支持向后兼容,以避免在框架中发生中断性变更。 服务器端 ASP.NET 核心中间件管道处理服务器上的请求。 使用服务器端技术来处理错误的请求。 有关详细信息,请参阅 ASP.NET Core Blazor 呈现模式

从多个程序集路由到组件

本部分适用于 BlazorWeb 应用。

使用 Router 组件的 AdditionalAssemblies 参数和终结点约定生成器 AddAdditionalAssemblies 来发现其他程序集中的可路由组件。 以下小节说明了何时以及如何使用每个 API。

静态路由

若要从用于静态服务器端呈现(静态 SSR)的其他程序集中发现可路由组件,即使相应路由器过后会变成用以实现交互式呈现的交互式组件,也必须将程序集披露给 Blazor 框架。 使用在服务器项目的 Program 文件中链接到 MapRazorComponents 的其他程序集调用 AddAdditionalAssemblies 方法。

以下示例包括使用项目 _Imports.razor 文件的 BlazorSample.Client 项目程序集中的可路由组件:

app.MapRazorComponents<App>()
    .AddAdditionalAssemblies(typeof(BlazorSample.Client._Imports).Assembly);

注意

上述指南也适用于组件类库方案。 支持静态服务器端呈现(静态 SSR)的 ASP.NET Core Razor 类库 (RCL) 中提供了有关类库和静态 SSR 的其他重要指南。

交互式路由

交互式呈现模式可以分配给 Routes 组件 (Routes.razor),这会使 Blazor 路由器在服务器上的静态 SSR 和静态路由后变为交互式。 例如,<Routes @rendermode="InteractiveServer" /> 会将交互式服务器端呈现(交互式 SSR)分配给 Routes 组件。 Router 组件将从 Routes 组件继承交互式服务器端呈现(交互式 SSR)。 路由器将在服务器上的静态路由后变为交互式。

交互式路由的内部导航不涉及从服务器请求新页面内容。 因此,内部页面请求不会发生预呈现。 有关详细信息,请参阅 Prerender ASP.NET Core Razor 组件

如果在服务器项目中定义了 Routes 组件,则 Router 组件的 AdditionalAssemblies 参数应包括 .Client 项目的程序集。 这样,路由器就可以在以交互方式呈现时正常工作。

在以下示例中,Routes 组件位于服务器项目中,BlazorSample.Client 项目的 _Imports.razor 文件指示要搜索可路由组件的程序集:

<Router
    AppAssembly="..."
    AdditionalAssemblies="new[] { typeof(BlazorSample.Client._Imports).Assembly }">
    ...
</Router>

除了指定至 AppAssembly 的程序集外,还会扫描其他程序集。

注意

上述指南也适用于组件类库方案。

或者,可路由组件仅存在于应用了全局交互式 WebAssembly 或自动呈现的 .Client 项目中,Routes 组件则在 .Client 项目(而不是服务器项目)中进行定义。 在这种情况下,没有具有可路由组件的外部程序集,因此不需要为 AdditionalAssemblies 指定值。

本部分适用于 Blazor Server 应用。

使用 Router 组件的 AdditionalAssemblies 参数和终结点约定生成器 AddAdditionalAssemblies 来发现其他程序集中的可路由组件。

在以下示例中,Component1 是在引用的名为 ComponentLibrary组件类库中定义的可路由组件:

<Router
    AppAssembly="..."
    AdditionalAssemblies="new[] { typeof(ComponentLibrary.Component1).Assembly }">
    ...
</Router>

除了指定至 AppAssembly 的程序集外,还会扫描其他程序集。

路由参数

路由器使用路由参数以相同的名称填充相应的组件参数。 路由参数名不区分大小写。 在下面的示例中,text 参数将路由段的值赋给组件的 Text 属性。 对 /route-parameter-1/amazing 发出请求时,内容呈现为 Blazor is amazing!

RouteParameter1.razor

@page "/route-parameter-1/{text}"

<PageTitle>Route Parameter 1</PageTitle>

<h1>Route Parameter Example 1</h1>

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

@code {
    [Parameter]
    public string? Text { get; set; }
}
@page "/route-parameter-1/{text}"

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

@code {
    [Parameter]
    public string? Text { get; set; }
}
@page "/route-parameter-1/{text}"

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

@code {
    [Parameter]
    public string? Text { get; set; }
}
@page "/route-parameter-1/{text}"

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

@code {
    [Parameter]
    public string Text { get; set; }
}
@page "/route-parameter-1/{text}"

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

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

支持可选参数。 在下面的示例中,text 可选参数将 route 段的值赋给组件的 Text 属性。 如果该段不存在,则将 Text 的值设置为 fantastic

不支持可选参数。 在下述示例中,应用了两个 @page 指令。 第一个指令允许导航到没有参数的组件。 第二个指令将 {text} 路由参数分配给组件的 Text 属性。

RouteParameter2.razor

@page "/route-parameter-2/{text?}"

<PageTitle>Route Parameter 2</PageTitle>

<h1>Route Parameter Example 2</h1>

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

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

    protected override void OnParametersSet()
    {
        Text = Text ?? "fantastic";
    }
}
@page "/route-parameter-2/{text?}"

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

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

    protected override void OnParametersSet()
    {
        Text = Text ?? "fantastic";
    }
}
@page "/route-parameter-2/{text?}"

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

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

    protected override void OnParametersSet()
    {
        Text = Text ?? "fantastic";
    }
}
@page "/route-parameter-2/{text?}"

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

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

    protected override void OnParametersSet()
    {
        Text = Text ?? "fantastic";
    }
}
@page "/route-parameter-2"
@page "/route-parameter-2/{text}"

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

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

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

使用 OnInitialized{Async} 方法而不是 OnParametersSet 方法时,如果用户在同一组件内导航,则不会发生属性 Textfantastic 的默认分配。 例如,当用户从 /route-parameter-2/amazing 导航到 /route-parameter-2 时,就会出现这种情况。 随着组件实例持久保存并接受新参数,便不会再次调用 OnInitialized 方法。

注意

路由参数不适用于查询字符串值。 若要使用查询字符串,请参阅查询字符串部分。

路由约束

路由约束强制在路由段和组件之间进行类型匹配。

在以下示例中,到 User 组件的路由仅在以下情况下匹配:

  • 请求 URL 中存在 Id 路由段。
  • Id 段是一个整数 (int) 类型。

User.razor

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

<PageTitle>User</PageTitle>

<h1>User Example</h1>

<p>User Id: @Id</p>

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

注意

路由约束不适用于查询字符串值。 若要使用查询字符串,请参阅查询字符串部分。

下表中显示的路由约束可用。 有关与固定区域性匹配的路由约束,请参阅表下方的警告了解详细信息。

约束 示例 匹配项示例 固定条件
区域性
匹配
bool {active:bool} trueFALSE
datetime {dob:datetime} 2016-12-312016-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 类型(例如 intDateTime)。 这些约束假定 URL 不可本地化。

路由约束也适用于可选参数。 在下面的示例中,Id 是必需的,但 Option 是一个可选的布尔路由参数。

User.razor

@page "/user/{id:int}/{option:bool?}"

<p>
    Id: @Id
</p>

<p>
    Option: @Option
</p>

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

    [Parameter]
    public bool Option { get; set; }
}

使用包含点的 URL 进行路由

服务器端 默认路由模板假定如果请求 URL 的最后一段包含一个点 (.),则请求一个文件。 例如,相对 URL /example/some.thing 由路由器解释为名为 some.thing 的文件的请求。 在没有额外配置的情况下,如果 some.thing 是指通过 @page 指令路由到一个组件,且 some.thing 是一个路由参数值,那么应用将返回“404 - 未找到”响应。 若要使用具有包含点的一个或多个参数的路由,则应用必须使用自定义模板配置该路由。

请考虑下面的 Example 组件,它可以从 URL 的最后一段接收路由参数。

Example.razor

@page "/example/{param?}"

<p>
    Param: @Param
</p>

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

<p>
    Param: @Param
</p>

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

<p>
    Param: @Param
</p>

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

<p>
    Param: @Param
</p>

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

若要允许托管的 Blazor WebAssembly 解决方案 Server 应用在 param 路由参数中使用一个点来路由请求,请添加一个回退文件路由模板,在该模板的 Program 文件中包含该可选参数:

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

若要配置 Blazor Server 应用,使其在 param 路由参数中使用一个点来路由请求,请添加一个回退页面路由模板,该模板具有Program 文件中的可选参数:

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

有关详细信息,请参阅 ASP.NET Core 中的路由

若要允许托管的 Blazor WebAssembly 解决方案Server 应用在 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 的末尾。

CatchAll.razor

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

<PageTitle>Catch All</PageTitle>

<h1>Catch All Parameters Example</h1>

<p>Add some URI segments to the route and request the page again.</p>

<p>
    PageRoute: @PageRoute
</p>

@code {
    [Parameter]
    public string? PageRoute { get; set; }
}
@page "/catch-all/{*pageRoute}"

@code {
    [Parameter]
    public string? PageRoute { get; set; }
}
@page "/catch-all/{*pageRoute}"

@code {
    [Parameter]
    public string? PageRoute { get; set; }
}
@page "/catch-all/{*pageRoute}"

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

对于具有 /catch-all/{*pageRoute} 路由模板的 URL /catch-all/this/is/a/testPageRoute 的值设置为 this/is/a/test

对捕获路径的斜杠和段进行解码。 对于 /catch-all/{*pageRoute} 的路由模板,URL /catch-all/this/is/a%2Ftest%2A 会生成 this/is/a/test*

URI 和导航状态帮助程序

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

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

位置更改

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

以下组件:

  • 使用 NavigateTo 选择按钮后,导航到应用的 Counter 组件 (Counter.razor)。
  • 通过订阅 NavigationManager.LocationChanged 来处理位置更改事件。
    • 在框架调用 Dispose 时,解除挂接 HandleLocationChanged 方法。 解除挂接该方法可允许组件进行垃圾回收。

    • 选择该按钮时,记录器实现会记录以下信息:

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

Navigate.razor

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

<PageTitle>Navigate</PageTitle>

<h1>Navigate Example</h1>

<button 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;
    }
}
@page "/navigate"
@using Microsoft.Extensions.Logging 
@implements IDisposable
@inject ILogger<Navigate> Logger
@inject NavigationManager Navigation

<h1>Navigate in component code 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;
    }
}
@page "/navigate"
@using Microsoft.Extensions.Logging 
@implements IDisposable
@inject ILogger<Navigate> Logger
@inject NavigationManager Navigation

<h1>Navigate in component code 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;
    }
}
@page "/navigate"
@using Microsoft.Extensions.Logging 
@implements IDisposable
@inject ILogger<Navigate> Logger
@inject NavigationManager Navigation

<h1>Navigate in component code 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;
    }
}
@page "/navigate"
@using Microsoft.Extensions.Logging 
@implements IDisposable
@inject ILogger<Navigate> Logger
@inject NavigationManager Navigation

<h1>Navigate in component code 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 组件生命周期

增强的导航和表单处理

本部分适用于 BlazorWeb 应用。

Blazor Web 应用可为页面导航和表单处理的请求提供两种路由:

  • 普通导航(跨文档导航):为请求 URL 触发整页重载。
  • 增强型导航(同一文档导航)†:Blazor 拦截该请求并改为执行 fetch 请求。 Blazor 然后将响应内容修补到页面的 DOM 中。 Blazor 的增强型导航和表单处理无需全页重载,并且会保留更多页面状态,因此页面加载速度更快,通常不会丢失用户在页面上的滚动位置。

†以下情况可以使用增强型导航:

  • Blazor 使用 Web 应用脚本(blazor.web.js),而不是 Blazor Server 脚本(blazor.server.js)或 Blazor WebAssembly 脚本(blazor.webassembly.js)。
  • 该功能未显式禁用
  • 目标 URL 位于内部基本 URI 空间(应用的基本路径)内。

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

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

调用 NavigateTo 时:

  • 如果 false 是默认值 forceLoad
    • 而且当前 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,则会获取以下结果:

  • inputURI 中传递 https://localhost:8000/segment 会导致 segmentbaseRelativePath
  • inputURI 中传递 https://localhost:8000/segment1/segment2 会导致 segment1/segment2baseRelativePath

如果应用的基 URI 与 inputURI 的基本 URI 不匹配,则会引发 ArgumentException

inputURI 中传递 https://localhost:8001/segment 将导致以下异常:

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 指令的可路由组件中接收查询参数值。

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

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

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

正确的区域性固定格式设置适用于给定类型 (CultureInfo.InvariantCulture)。

指定 [SupplyParameterFromQuery] 属性的 Name 属性以使用不同于组件参数名称的查询参数名称。 在以下示例中,组件参数的 C# 名称是 {COMPONENT PARAMETER NAME}。 为 {QUERY PARAMETER NAME} 占位符指定了不同的查询参数名称:

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

在使用 /search?filter=scifi%20stars&page=3&star=LeVar%20Burton&star=Gary%20Oldman URL 的以下示例中:

  • Filter 属性解析为 scifi stars
  • Page 属性解析为 3
  • Stars 数组是从名为 star (Name = "star") 的查询参数填充的,并解析为 LeVar BurtonGary Oldman

说明

以下可路由页面组件中的查询字符串参数也适用于无 @page 指令的不可路由组件(例如,其他组件中使用的共享 Search 组件的 Search.razor

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]
    public string? Filter { get; set; }

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

    [SupplyParameterFromQuery(Name = "star")]
    public 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; }
}

使用 NavigationManager.GetUriWithQueryParameter 在当前 URL 上添加、更改或删除一个或多个查询参数:

@inject NavigationManager Navigation

...

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

在上面的示例中:

  • {NAME} 占位符指定查询参数名称。 {VALUE} 占位符将值指定为支持的类型。 本节后面的内容列出了支持的类型。
  • 返回的字符串相当于包含单个参数的当前 URL:
    • 如果查询参数名称在当前 URL 中不存在,则进行添加。
    • 如果查询参数存在于当前 URL 中,则更新为提供的值。
    • 如果提供的值的类型可为空且值为 null,则进行删除。
  • 正确的区域性固定格式设置适用于给定类型 (CultureInfo.InvariantCulture)。
  • 查询参数名称和值是 URL 编码的。
  • 如果存在类型的多个实例,则所有具有匹配查询参数名称的值都会被替换。

调用 NavigationManager.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
  • DateTime
  • decimal
  • double
  • float
  • Guid
  • int
  • long
  • string

支持的类型包括:

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

警告

使用默认启用的压缩时,避免创建安全(经过身份验证/授权)的交互式服务器端组件来呈现来自不受信任的源的数据。 不受信任的源包括路由参数、查询字符串、来自 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;

若要分析查询字符串的参数,一种方法是结合使用 URLSearchParamsJavaScript (JS) 互操作

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

有关使用 JavaScript 模块进行 JavaScript 隔离的详细信息,请参阅在 ASP.NET Core Blazor 中从 .NET 方法调用 JavaScript 函数

对命名元素的哈希处理路由

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

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

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

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

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

向应用添加以下 HashedRouting 组件。

HashedRouting.razor

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

<PageTitle>Hashed routing</PageTitle>

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

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

<div 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");
    }
}

用户与 <Navigating> 内容的交互

如果在导航期间出现明显延迟,例如在 Blazor WebAssembly 应用中延迟加载程序集时或在与 Blazor 服务器端应用进行缓慢的网络连接时出现明显延迟,Router 组件可以向用户指示正在发生页面转换。

在指定 Router 的组件顶部,为 Microsoft.AspNetCore.Components.Routing 命名空间添加 @using 指令:

@using Microsoft.AspNetCore.Components.Routing

Navigating 参数提供内容,以便在页面转换事件期间进行显示。

在路由器元素内容 (<Router>...</Router>) 中:

<Navigating>
    <p>Loading the requested page&hellip;</p>
</Navigating>

有关使用 Navigating 属性的示例,请参阅 ASP.NET Core Blazor WebAssembly 中的延迟加载程序集

使用 OnNavigateAsync 处理异步导航事件

Router 组件支持 OnNavigateAsync 功能。 当用户执行以下操作时,将调用 OnNavigateAsync 处理程序:

  • 通过直接在自己的浏览器中导航到某个路由,首次访问该路由。
  • 使用链接或 NavigationManager.NavigateTo 调用导航到一个新路由。
<Router AppAssembly="typeof(App).Assembly" 
    OnNavigateAsync="OnNavigateAsync">
    ...
</Router>

@code {
    private async Task OnNavigateAsync(NavigationContext args)
    {
        ...
    }
}
<Router AppAssembly="typeof(Program).Assembly" 
    OnNavigateAsync="OnNavigateAsync">
    ...
</Router>

@code {
    private async Task OnNavigateAsync(NavigationContext args)
    {
        ...
    }
}

有关使用 OnNavigateAsync 的示例,请参阅 ASP.NET Core Blazor WebAssembly 中的延迟加载程序集

在服务器上预呈现内容时,OnNavigateAsync 会执行两次

  • 第一次是在请求的终结点组件最初静态呈现时。
  • 第二次是浏览器呈现终结点组件时。

为了防止 OnNavigateAsync 中的开发人员代码执行两次,Routes 组件可以存储 NavigationContext,以供 OnAfterRender{Async} 使用,可以在其中检查 firstRender。 有关详细信息,请参阅“Blazor 生命周期”一文中的使用 JavaScript 互操作预呈现

为了防止 OnNavigateAsync 中的开发人员代码执行两次,App 组件可以存储 NavigationContext,以供 OnAfterRender{Async} 使用,可以在其中检查 firstRender。 有关详细信息,请参阅“Blazor 生命周期”一文中的使用 JavaScript 互操作预呈现

处理 OnNavigateAsync 中的取消

传递到 OnNavigateAsync 回调的 NavigationContext 对象包含的 CancellationToken 在发生新导航事件时进行设置。 设置此取消标记时,OnNavigateAsync 回调必须引发,以避免在过时的导航中继续运行 OnNavigateAsync 回调。

如果用户导航到某个终结点,但随后立即导航到新的终结点,则应用不应继续为第一个终结点运行 OnNavigateAsync 回叫。

在下面的示例中:

  • 取消标记在对 PostAsJsonAsync 的调用中传递,用户离开 /about 终结点后它可取消 POST。
  • 如果用户离开 /store 终结点,则会在产品预提取操作期间设置取消操作。
@inject HttpClient Http
@inject ProductCatalog Products

<Router AppAssembly="typeof(App).Assembly" 
    OnNavigateAsync="OnNavigateAsync">
    ...
</Router>

@code {
    private async Task OnNavigateAsync(NavigationContext context)
    {
        if (context.Path == "/about") 
        {
            var stats = new Stats { Page = "/about" };
            await Http.PostAsJsonAsync("api/visited", stats, 
                context.CancellationToken);
        }
        else if (context.Path == "/store")
        {
            var productIds = new[] { 345, 789, 135, 689 };

            foreach (var productId in productIds) 
            {
                context.CancellationToken.ThrowIfCancellationRequested();
                Products.Prefetch(productId);
            }
        }
    }
}
@inject HttpClient Http
@inject ProductCatalog Products

<Router AppAssembly="typeof(Program).Assembly" 
    OnNavigateAsync="OnNavigateAsync">
    ...
</Router>

@code {
    private async Task OnNavigateAsync(NavigationContext context)
    {
        if (context.Path == "/about") 
        {
            var stats = new Stats { Page = "/about" };
            await Http.PostAsJsonAsync("api/visited", stats, 
                context.CancellationToken);
        }
        else if (context.Path == "/store")
        {
            var productIds = new[] { 345, 789, 135, 689 };

            foreach (var productId in productIds) 
            {
                context.CancellationToken.ThrowIfCancellationRequested();
                Products.Prefetch(productId);
            }
        }
    }
}

说明

如果取消 NavigationContext 中的取消标记会导致意外的行为(例如,呈现上一次导航中的组件),则不会引发。

处理/阻止位置更改

RegisterLocationChangingHandler 注册一个处理程序来处理传入的导航事件。 LocationChangingContext 提供的处理程序上下文包括以下属性:

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

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

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

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

重要事项

处理位置更改时,请勿尝试通过 JavaScript (JS) 互操作执行 DOM 清理任务。 在客户端上使用 JS 中的 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 以控制导航历史记录堆栈的条目和状态的详细信息,请参阅导航选项部分。

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

注意

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

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

NavigationLock 参数:

在下面的 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();
        }
    }
}

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

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

有两个 NavLinkMatch 选项可分配给 <NavLink> 元素的 Match 属性:

在前面的示例中,HomeNavLinkhref="" 与主页 URL 匹配,并且仅在应用程序的默认基路径 (/) 处接收 active CSS 类。 当用户访问带有 component 前缀的任何 URL(例如,/component/component/another-segment)时,第二个 NavLink 接收 active 类。

其他 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="product-number/@c">
            <span ...></span> Product #@current
        </NavLink>
    </li>
}

子内容中使用循环变量的任何子组件(而不仅仅是 NavLink 组件)都要求必须在此方案中使用索引变量。

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

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

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

对于以下演示,一致的标准命名约定会用于应用的组件:

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

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

在默认的 Home 页面下 NavMenu 组件 (NavMenu.razor) 的 Razor 标记中,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>

@code 块中的 GetRoutableComponents 方法:

public IEnumerable<string> GetRoutableComponents()
{
    return 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 应用Blazor WebAssembly 示例应用

ASP.NET Core 终结点路由集成

本部分适用于通过线路运行的 Blazor Web 应用。

本部分适用于 Blazor Server 应用。

Blazor Web 应用已集成到 ASP.NET Core 终结点路由中。 ASP.NET Core 应用配置为接受 Program 文件中带有 MapRazorComponents 的交互式组件的传入连接。 默认根组件(加载的第一个组件)是 App 组件 (App.razor):

app.MapRazorComponents<App>();

Blazor Server 已集成到 ASP.NET Core 终结点路由中。 ASP.NET Core 应用配置为接受 Program 文件中带有 MapBlazorHub 的交互式组件的传入连接:

app.UseRouting();

app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

Blazor Server 已集成到 ASP.NET Core 终结点路由中。 ASP.NET Core 应用配置为接受 Startup.Configure 中带有 MapBlazorHub 的交互式组件的传入连接。

典型的配置是将所有请求路由到 Razor 页面,该页面充当 Blazor Server应用的服务器端部分的主机。 按照约定,主机页面通常在应用的 Pages 文件夹中被命名为 _Host.cshtml

主机文件中指定的路由称为回退路由,因为它在路由匹配中以较低的优先级运行。 其他路由不匹配时,会使用回退路由。 这让应用能够使用其他控制器和页面,而不会干扰 Blazor Server应用中的组件路由。

若要了解如何为非根 URL 服务器托管配置 MapFallbackToPage,请参阅托管和部署 ASP.NET Core Blazor