ASP.NET Core Blazor 路由和导航
注意
此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .NET 8 版本。
本文介绍如何管理 Blazor 应用请求路由以及如何使用 NavLink 组件创建导航链接。
重要
本文中的代码示例展示了对 Navigation
调用的方法,这是在类和组件中注入的 NavigationManager。
静态路由与交互式路由
本部分适用于 Blazor Web App。
如果启用预渲染,则 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
)。
编译带有 @page
指令的 Razor 组件 (.razor
) 时,将为生成的组件类提供一个 RouteAttribute 来指定组件的路由模板。
当应用启动时,将扫描指定为 路由器的 AppAssembly
的程序集,来收集具有 RouteAttribute 的应用组件的路由信息。
在运行时,RouteView 组件:
对于没有使用 @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"
<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。
作为使用 @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 App 不使用 NotFound 参数(<NotFound>...</NotFound>
标记),但该参数受到支持以实现向后兼容,从而避免在框架中发生中断性变更。 服务器端 ASP.NET 核心中间件管道处理服务器上的请求。 使用服务器端技术来处理错误的请求。 有关详细信息,请参阅 ASP.NET Core Blazor 呈现模式。
从多个程序集路由到组件
本部分适用于 Blazor Web App。
使用 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}"
<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?}"
<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{Async}
生命周期方法时,如果用户在同一组件内导航,则不会将 Text
属性默认分配给 fantastic
。 例如,当用户从 /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} |
%> | 否 |
datetime |
{dob:datetime} |
%> | 是 |
decimal |
{price:decimal} |
%> | 是 |
double |
{weight:double} |
%> | 是 |
float |
{weight:float} |
%> | 是 |
guid |
{id:guid} |
%> | 否 |
int |
{id:int} |
%> | 是 |
long |
{ticks:long} |
%> | 是 |
nonfile |
{parameter:nonfile} |
不 BlazorSample.styles.css ,不 favicon.ico |
是 |
警告
验证 URL 的路由约束并将转换为始终使用固定区域性的 CLR 类型(例如 int
或 DateTime)。 这些约束假定 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; }
}
避免在路由参数中捕获文件
以下路由模板无意中捕获其可选路由参数 (Optional
) 中的静态资产路径。 例如,捕获应用的样式表 (.styles.css
) 会中断应用的样式:
@page "/{optional?}"
...
@code {
[Parameter]
public string? Optional { get; set; }
}
若要将路由参数限制为捕获非文件路径,请使用路由模板中的 :nonfile
约束:
@page "/{optional:nonfile?}"
使用包含点的 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}"
<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/test
,PageRoute
的值设置为 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。 如果 forceLoad 为 false ,则:
forceLoad 为 true ,则:
有关详细信息,请参阅增强型导航和表单处理部分。 如果 |
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。 如果 forceLoad 为 true ,则:
replace 为 true ,则替换浏览器历史记录中的当前 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。 如果 forceLoad 为 true ,则:
replace 为 true ,则替换浏览器历史记录中的当前 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。 如果 forceLoad 为 true ,则:
|
LocationChanged | 导航位置更改时触发的事件。 |
ToAbsoluteUri | 将相对 URI 转换为绝对 URI。 |
ToBaseRelativePath | 根据应用的基 URI,将绝对 URI 转换为相对于基本 URI 前缀的 URI。 有关示例,请参阅生成相对于基 URI 前缀的 URI 部分。 |
位置更改
对于 LocationChanged 事件,LocationChangedEventArgs 提供了下述导航事件信息:
- Location:新位置的 URL。
- IsNavigationIntercepted:如果为
true
,则 Blazor 拦截了浏览器中的导航。 如果为false
,则 NavigationManager.NavigateTo 导致了导航发生。
以下组件:
- 使用 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"
@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 组件生命周期。
增强的导航和表单处理
本部分适用于 Blazor Web App。
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 空间(应用的基本路径)内。
如果启用了服务器端路由和增强型导航,则仅针对从交互式运行时启动的编程导航调用位置更改处理程序。 在未来版本中,其他类型的导航(如跟随链接)也可能调用位置更改处理程序。
当出现增强型导航时,通常会调用向交互式服务器和 WebAssembly 运行时注册的 LocationChanged
事件处理程序。 在某些情况下,位置更改处理程序可能无法拦截增强型导航。 例如,用户可能在交互式运行时可用之前切换到另一个页面。 因此,应用逻辑不依赖于调用位置更改处理程序非常重要,因为无法保证处理程序的执行。
调用 NavigateTo 时:
- 如果
forceLoad
为默认值false
:- 而且当前 URL 中可使用增强型导航,Blazor 的增强型导航已激活。
- 否则,Blazor 会对请求的 URL 执行整页重载。
- 如果
forceLoad
为true
:Blazor 会对请求的 URL 执行整页重载,无论增强型导航是否可用。
可以通过调用 NavigationManager.Refresh(bool forceLoad = false)
来刷新当前页,它会始终执行增强型导航(如果可用)。 如果增强型导航不可用,Blazor 将执行整页重载。
Navigation.Refresh();
将 true
传递给 forceLoad
参数以确保始终执行整页重载,即使增强的导航可用:
Navigation.Refresh(true);
默认启用增强型导航,但可使用 data-enhance-nav
HTML 属性按每个链接进行分层控制。
以下示例禁用增强型导航:
<a href="redirect" data-enhance-nav="false">
GET without enhanced navigation
</a>
<ul data-enhance-nav="false">
<li>
<a href="redirect">GET without enhanced navigation</a>
</li>
<li>
<a href="redirect-2">GET without enhanced navigation</a>
</li>
</ul>
如果目标为非 Blazor 终结点,则增强型导航不适用,客户端 JavaScript 会在完整页面加载时重试。 这可确保对于不应修补到现有页面中的外部页面,框架不会产生混淆。
若要启用增强的表单处理,请将 Enhance 参数添加到 EditForm 表单或将 data-enhance
属性添加到 HTML 表单(<form>
):
<EditForm ... Enhance ...>
...
</EditForm>
<form ... data-enhance ...>
...
</form>
增强的表单处理不是分层的,也不会传输到子表单:
不支持:无法在上级元素上设置增强型导航来为表单启用增强型导航。
<div ... data-enhance ...>
<form ...>
<!-- NOT enhanced -->
</form>
</div>
增强的表单发布仅适用于 Blazor 终结点。 将增强型表单发布到非 Blazor 终结点会导致错误。
若要禁用增强型导航,请执行以下操作:
- 对于 EditForm,请从表单元素中移除 Enhance 参数(或将其设置为
false
:Enhance="false"
)。 - 对于 HTML
<form>
,请从表单元素中移除data-enhance
特性(或将其设置为false
:data-enhance="false"
)。
如果更新后的内容不是服务器呈现的一部分,Blazor 的增强型导航和表单处理可能会撤消对 DOM 的动态更改。 若要保留元素的内容,请使用 data-permanent
特性。
在以下示例中,当页面加载时,<div>
元素的内容由脚本动态更新:
<div data-permanent>
...
</div>
在客户端上启动 Blazor 后,可使用 enhancedload
事件侦听增强的页面更新。 这允许将更改重新应用于可能已通过增强的页面更新撤消的 DOM。
Blazor.addEventListener('enhancedload', () => console.log('Enhanced update!'));
若要在全局禁用增强型导航和表单处理,请参阅 ASP.NET Core Blazor 启动。
加载 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
会导致segment
的baseRelativePath
。 - 在
inputURI
中传递https://localhost:8000/segment1/segment2
会导致segment1/segment2
的baseRelativePath
。
如果应用的基 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 来维护与应用程序所做的每个位置更改相关的导航历史记录状态。 维护历史记录状态在外部重定向场景中特别有用,例如在使用外部 identity 提供程序对用户进行身份验证时。 有关详细信息,请参阅导航选项部分。
导航选项
将 NavigationOptions 传递给 NavigateTo 以控制以下行为:
- ForceLoad:绕过客户端路由并强制浏览器从服务器加载新页面,无论 URI 是否由客户端路由器处理。 默认值为
false
。 - ReplaceHistoryEntry:替换历史记录堆栈中的当前条目。 如果为
false
,则将新条目附加到历史记录堆栈。 默认值为false
。 - HistoryEntryState:获取或设置要附加到历史记录条目的状态。
Navigation.NavigateTo("/path", new NavigationOptions
{
HistoryEntryState = "Navigation state"
});
有关在处理位置更改时获取与目标历史记录条目关联的状态的详细信息,请参阅处理/防止位置更改部分。
查询字符串
使用 [SupplyParameterFromQuery]
属性指定组件参数来自查询字符串。
将 [SupplyParameterFromQuery]
属性与 [Parameter]
属性结合使用,指定可路由组件的组件参数可以来自查询字符串。
说明
组件参数只能在具有 @page
指令的可路由组件中接收查询参数值。
只有可路由组件直接接收查询参数,以避免破坏自上而下的信息流,并通过框架和应用使参数处理顺序清晰明确。 此设计可避免采用特定参数处理顺序编写的应用代码中的细微 bug。 你可以自由地定义自定义级联参数或直接分配给常规组件参数,以便将查询参数值传递给不可路由的组件。
查询字符串提供的组件参数支持以下类型:
- 上述类型的可为空变体。
- 上述类型的数组,无论它们是可为空还是不可为空。
正确的区域性固定格式设置适用于给定类型 (CultureInfo.InvariantCulture)。
指定 [SupplyParameterFromQuery]
属性的 Name 属性以使用不同于组件参数名称的查询参数名称。 在以下示例中,组件参数的 C# 名称是 {COMPONENT PARAMETER NAME}
。 为 {QUERY PARAMETER NAME}
占位符指定了不同的查询参数名称:
与组件参数属性 ([Parameter]
) 不同,[SupplyParameterFromQuery]
属性除了可以标记为 public
外,还可以标记为 private
。
[SupplyParameterFromQuery(Name = "{QUERY PARAMETER NAME}")]
private string? {COMPONENT PARAMETER NAME} { get; set; }
与组件参数属性 ([Parameter]
) 一样,[SupplyParameterFromQuery]
属性始终是 .NET 6/7 中的 public
属性。 在 .NET 8 或更高版本中,[SupplyParameterFromQuery]
属性可以标记为 public
或 private
。
[Parameter]
[SupplyParameterFromQuery(Name = "{QUERY PARAMETER NAME}")]
public string? {COMPONENT PARAMETER NAME} { get; set; }
在使用 /search?filter=scifi%20stars&page=3&star=LeVar%20Burton&star=Gary%20Oldman
URL 的以下示例中:
Filter
属性解析为scifi stars
。Page
属性解析为3
。Stars
数组是从名为star
(Name = "star"
) 的查询参数填充的,并解析为LeVar Burton
和Gary 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]
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
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
参数或将其替换为35
、16
、87
和240
。
Navigation.GetUriWithQueryParameters(
new Dictionary<string, object?>
{
["full name"] = "Morena Baccarin",
["ping"] = new int?[] { 35, 16, null, 87, 240 }
})
当前 URL | 生成的 URL |
---|---|
scheme://host/?full%20name=David%20Krumholtz&ping=8&ping=300 |
scheme://host/?full%20name=Morena%20Baccarin&ping=35&ping=16&ping=87&ping=240 |
scheme://host/?ping=8&full%20name=David%20Krumholtz&ping=300 |
scheme://host/?ping=35&full%20name=Morena%20Baccarin&ping=16&ping=87&ping=240 |
scheme://host/?ping=8&ping=300&ping=50&ping=68&ping=42 |
scheme://host/?ping=35&ping=16&ping=87&ping=240&full%20name=Morena%20Baccarin |
使用添加或修改的查询字符串进行导航
若要使用添加或修改的查询字符串进行导航,请将生成的 URL 传递给 NavigateTo。
下面的示例调用:
- GetUriWithQueryParameter 以使用值
Morena Baccarin
添加或替换name
查询参数。 - 调用 NavigateTo 以触发到新 URL 的导航。
Navigation.NavigateTo(
Navigation.GetUriWithQueryParameter("name", "Morena Baccarin"));
可从 NavigationManager.Uri 属性中获取请求的查询字符串:
@inject NavigationManager Navigation
...
var query = new Uri(Navigation.Uri).Query;
若要分析查询字符串的参数,一种方法是结合使用 URLSearchParams
和 JavaScript (JS) 互操作:
export createQueryString = (string queryString) => new URLSearchParams(queryString);
有关使用 JavaScript 模块进行 JavaScript 隔离的详细信息,请参阅在 ASP.NET Core Blazor 中从 .NET 方法调用 JavaScript 函数。
对命名元素的哈希处理路由
使用以下方法,通过对命名元素的哈希处理 (#
) 引用,导航到该元素。 对组件中的元素路由,以及对外部组件中的元素路由,均使用根相对路径。 前导正斜杠 (/
) 是可选的。
以下每种方法的示例均演示了如何导航到 Counter
组件中 id
为 targetElement
的元素:
带
href
的 Anchor 元素 (<a>
):<a href="/counter#targetElement">
带
href
的 NavLink 组件:<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…</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
。 有关详细信息,请参阅使用 JavaScript 互操作预呈现。
为了防止 OnNavigateAsync 中的开发人员代码执行两次,App
组件可以存储 NavigationContext,以供 OnAfterRender{Async}
使用,可以在其中检查 firstRender
。 有关详细信息,请参阅使用 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 提供的处理程序上下文包括以下属性:
- TargetLocation:获取目标位置。
- HistoryEntryState:获取与目标历史记录条目关联的状态。
- IsNavigationIntercepted:获取是否从链接截获了导航。
- CancellationToken:获取 CancellationToken 以确定导航是否已取消,例如,确定用户是否触发了不同的导航。
- PreventNavigation:调用以阻止导航继续。
一个组件可以在 OnAfterRender{Async}
生命周期方法中注册多个位置更改处理程序。 导航调用在整个应用(跨多个组件)中注册的所有位置更改处理程序,并且任何内部导航都并行执行它们。 除了调用 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 以控制导航历史记录堆栈的条目和状态的详细信息,请参阅导航选项部分。
有关其他示例代码,请参阅 BasicTestApp
(dotnet/aspnetcore
参考源)中的 NavigationManagerComponent
。
注意
指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)。
只要呈现导航事件,NavigationLock
组件就会将其拦截,除非决定继续或取消,否则将一直有效“锁定”任何给定的导航。 当导航拦截的范围可以限定为组件的生存期时,使用 NavigationLock
。
NavigationLock 参数:
- ConfirmExternalNavigation 设置浏览器对话框以提示用户确认或取消外部导航。 默认值为
false
。 显示确认对话框需要在使用浏览器地址栏中的 URL 触发外部导航之前与页面进行初始用户交互。 有关交互要求的详细信息,请参阅 Window:beforeunload
事件(MDN 文档)。 - OnBeforeInternalNavigation 为内部导航事件设置回调。
在下面的 NavLock
组件中:
- 在成功导航到
https://www.microsoft.com
之前,用户必须尝试确认过 Microsoft 网站的链接可访问。 - 如果用户拒绝通过生成 JS
confirm
对话框的 JavaScript (JS) 互操作调用确认导航,则调用 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();
}
}
}
有关其他示例代码,请参阅 BasicTestApp
(dotnet/aspnetcore
参考源)中的 ConfigurableNavigationLock
组件。
NavLink
组件
创建导航链接时,请使用 NavLink 组件代替 HTML 超链接元素 (<a>
)。 NavLink 组件的行为方式类似于 <a>
元素,但它根据其 href
是否与当前 URL 匹配来切换 active
CSS 类。 active
类可帮助用户了解所显示导航链接中的哪个页面是活动页面。 也可以选择将 CSS 类名分配到 NavLink.ActiveClass,以便在当前路由与 href
匹配时将自定义 CSS 类应用到呈现的链接。
有两个 NavLinkMatch 选项可分配给 <NavLink>
元素的 Match
属性:
- NavLinkMatch.All:NavLink 在与当前整个 URL 匹配的情况下处于活动状态。
- NavLinkMatch.Prefix(默认):NavLink 在与当前 URL 的任何前缀匹配的情况下处于活动状态。
在前面的示例中,HomeNavLinkhref=""
与 home 页 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 = 1; c < 4; c++)
{
var ct = c;
<li ...>
<NavLink ...>
<span ...></span> Product #@ct
</NavLink>
</li>
}
在子内容中使用循环变量的任何子组件(而不仅仅是 NavLink
组件)都要求必须在此方案中使用索引变量。
或者,将 foreach
循环用于 Enumerable.Range:
@foreach (var c in Enumerable.Range(1, 3))
{
<li ...>
<NavLink ...>
<span ...></span> Product #@c
</NavLink>
</li>
}
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() =>
Assembly.GetExecutingAssembly()
.ExportedTypes
.Where(t => t.IsSubclassOf(typeof(ComponentBase)))
.Where(c => c.GetCustomAttributes(inherit: true)
.OfType<RouteAttribute>()
.Any())
.Where(c => c.Name != "Home" && c.Name != "Error")
.OrderBy(o => o.Name)
.Select(c => c.Name);
前面的示例未在呈现的组件列表中包含以下页面:
Home
页面:该页面与自动生成的链接分开列出,因为它应显示在列表顶部并设置Match
参数。Error
页面:该错误页面仅由框架导航到达,不应列出。
如需示例应用中可在本地运行的上述代码示例,请获取 Blazor Web App 或 Blazor WebAssembly 示例应用。
ASP.NET Core 终结点路由集成
本部分适用于通过线路运行的 Blazor Web App。
本部分适用于 Blazor Server 应用。
Blazor Web App 已集成到 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。