注意
此版本不是本文的最新版本。 有关当前版本,请参阅本文的 .NET 9 版本。
本文介绍了 Razor 中服务器呈现组件的 Blazor Web App 组件预呈现场景。
预呈现是最初在服务器上呈现页面内容的过程,而无需为呈现的控件启用事件处理程序。 服务器会根据初始请求尽快输出页面的 HTML UI,这会让应用感觉对用户的响应更强。 预呈现还可以通过呈现搜索引擎用于计算网页排名的初始 HTTP 响应的内容,来改进搜索引擎优化 (SEO)。
保留预渲染状态
在不保留预呈现状态的情况下,在预呈现期间使用的状态将丢失,并且在完全加载应用时必须重新创建。 如果任何状态是异步创建的,那么当组件重新渲染时,用户界面可能会闪烁,因为预渲染的用户界面将被替换。
请考虑以下 PrerenderedCounter1
计数器组件。 该组件在预呈现期间在 OnInitialized
生命周期方法中设置初始随机计数器值。 与客户端建立 SignalR 连接后,组件重新呈现,并且当第二次执行 OnInitialized
时,将替换初始计数值。
PrerenderedCounter1.razor
:
@page "/prerendered-counter-1"
@rendermode @(new InteractiveServerRenderMode(prerender: true))
@inject ILogger<PrerenderedCounter1> Logger
<PageTitle>Prerendered Counter 1</PageTitle>
<h1>Prerendered Counter 1</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount;
protected override void OnInitialized()
{
currentCount = Random.Shared.Next(100);
Logger.LogInformation("currentCount set to {Count}", currentCount);
}
private void IncrementCount() => currentCount++;
}
运行应用并检查组件的日志记录。 下面是示例输出。
注意
如果应用采用 交互式路由,并且通过内部 增强导航访问页面,则不会进行预呈现。 因此,必须为 PrerenderedCounter1
组件执行完整页面重载才能查看以下输出。 有关详细信息,请参阅 交互式路由和预呈现 部分。
info: BlazorSample.Components.Pages.PrerenderedCounter1[0]
currentCount set to 41
info: BlazorSample.Components.Pages.PrerenderedCounter1[0]
currentCount set to 92
第一个记录的计数发生在预呈现期间。 重新呈现组件时,会在预呈现后再次设置计数。 当计数从 41 更新到 92 时,UI 也会闪烁。
为了在预呈现期间保留计数器的初始值,Blazor 支持使用 PersistentComponentState 服务在预呈现页面中保留状态(对于嵌入到 Razor Pages 或 MVC 应用的页面或视图中的组件,使用持久组件状态标记帮助程序)。
若要保留预呈现状态,请使用 [SupplyParameterFromPersistentComponentState]
属性在属性中保留状态。 在预呈现期间,具有此属性的属性会通过 PersistentComponentState 服务自动持久化。 当组件以交互方式呈现或实例化服务时,将检索状态。
默认情况下,使用具有默认设置的 System.Text.Json 序列化程序对属性进行序列化。 序列化不是更安全的,需要保留所使用的类型。 有关详细信息,请参阅配置适用于 ASP.NET Core Blazor 的裁边器。
以下计数器组件在预呈现期间保留计数器状态,并检索用于初始化组件的状态:
- 该
[SupplyParameterFromPersistentComponentState]
属性应用于CounterState
类型(State
)。 - 计数器的状态在
null
OnInitialized
时分配,并在组件以交互方式呈现时自动还原。
PrerenderedCounter2.razor
:
@page "/prerendered-counter-2"
@inject ILogger<PrerenderedCounter2> Logger
<PageTitle>Prerendered Counter 2</PageTitle>
<h1>Prerendered Counter 2</h1>
<p role="status">Current count: @State?.CurrentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
[SupplyParameterFromPersistentComponentState]
public CounterState? State { get; set; }
protected override void OnInitialized()
{
if (State is null)
{
State = new() { CurrentCount = Random.Shared.Next(100) };
Logger.LogInformation("CurrentCount set to {Count}",
State.CurrentCount);
}
else
{
Logger.LogInformation("CurrentCount restored to {Count}",
State.CurrentCount);
}
}
private void IncrementCount()
{
if (State is not null)
{
State.CurrentCount++;
}
}
public class CounterState
{
public int CurrentCount { get; set; }
}
}
执行组件时,CurrentCount
仅在预呈现期间设置一次。 重新呈现组件时,会还原该值。 下面是示例输出。
info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
CurrentCount set to 96
info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
CurrentCount restored to 96
在以下示例中,序列化同一类型的多个组件的状态:
- 使用
[SupplyParameterFromPersistentComponentState]
属性批注的属性在预呈现期间进行序列化和反序列化。 -
指令
@key
属性用于确保状态与组件实例正确关联。 - 该
Element
属性在OnInitialized
生命周期方法中初始化,以避免出现 Null 引用异常,这与避免查询参数和表单数据中的 Null 引用类似。
PersistentChild.razor
:
<div>
<p>Current count: @Element.CurrentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</div>
@code {
[SupplyParameterFromPersistentComponentState]
public State Element { get; set; }
protected override void OnInitialized()
{
Element ??= new State();
}
private void IncrementCount()
{
Element.CurrentCount++;
}
private class State
{
public int CurrentCount { get; set; }
}
}
Parent.razor
:
@page "/parent"
@foreach (var element in elements)
{
<PersistentChild @key="element.Name" />
}
在以下示例中,序列化依赖项注入服务的状态:
- 使用
[SupplyParameterFromPersistentComponentState]
属性批注的属性在预呈现期间进行序列化,并在应用变为交互时进行反序列化。 - 该方法
AddPersistentService
用于注册服务以保持持久性。 呈现模式是必需的,因为无法从服务类型推断呈现模式。 使用以下任何值:-
RenderMode.Server
:该服务可用于交互式服务器呈现模式。 -
RenderMode.Webassembly
:该服务可用于交互式 Webassembly渲染模式。 -
RenderMode.InteractiveAuto
:如果组件在任一模式下呈现,则服务同时可用于交互式服务器和交互式 Web 汇编呈现模式。
-
- 服务在初始化交互式呈现模式时被解析,并反序列化带有
[SupplyParameterFromPersistentComponentState]
特性批注的属性。
注意
仅支持持久化范围限定的服务。
CounterService.cs
:
public class CounterService
{
[SupplyParameterFromPersistentComponentState]
public int CurrentCount { get; set; }
public void IncrementCount()
{
CurrentCount++;
}
}
在 Program.cs
中:
builder.Services.AddPersistentService<CounterService>(RenderMode.InteractiveAuto);
从实际服务实例标识序列化属性:
- 此方法允许将抽象标记为持久服务。
- 使实际实现可以是内部或不同类型的。
- 支持不同程序集中的共享代码。
- 导致每个实例公开相同的属性。
作为使用 [SupplyParameterFromPersistentComponentState]
属性进行状态持久化的声明性模型的替代方法,可以直接使用 PersistentComponentState 服务,这为复杂的状态持久化方案提供了更大的灵活性。 调用 PersistentComponentState.RegisterOnPersisting 以注册回调以在预呈现期间保留组件状态。 在组件以交互方式渲染时,会检索状态。 在初始化代码结束时进行调用,以避免在应用关闭期间出现潜在的争用条件。
以下计数器组件在预呈现期间保留计数器状态,然后检索状态以初始化组件。
PrerenderedCounter3.razor
:
@page "/prerendered-counter-3"
@implements IDisposable
@inject ILogger<PrerenderedCounter3> Logger
@inject PersistentComponentState ApplicationState
<PageTitle>Prerendered Counter 3</PageTitle>
<h1>Prerendered Counter 3</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount;
private PersistingComponentStateSubscription persistingSubscription;
protected override void OnInitialized()
{
if (!ApplicationState.TryTakeFromJson<int>(
nameof(currentCount), out var restoredCount))
{
currentCount = Random.Shared.Next(100);
Logger.LogInformation("currentCount set to {Count}", currentCount);
}
else
{
currentCount = restoredCount!;
Logger.LogInformation("currentCount restored to {Count}", currentCount);
}
// Call at the end to avoid a potential race condition at app shutdown
persistingSubscription = ApplicationState.RegisterOnPersisting(PersistCount);
}
private Task PersistCount()
{
ApplicationState.PersistAsJson(nameof(currentCount), currentCount);
return Task.CompletedTask;
}
private void IncrementCount() => currentCount++;
void IDisposable.Dispose() => persistingSubscription.Dispose();
}
执行组件时,currentCount
仅在预呈现期间设置一次。 重新呈现组件时,会还原该值。 下面是示例输出。
info: BlazorSample.Components.Pages.PrerenderedCounter3[0]
currentCount set to 96
info: BlazorSample.Components.Pages.PrerenderedCounter3[0]
currentCount restored to 96
若要保留预呈现状态,请决定使用 PersistentComponentState 服务保留什么状态。 PersistentComponentState.RegisterOnPersisting 注册回调以在预呈现期间保留组件状态。 在组件以交互方式渲染时,会检索状态。 在初始化代码结束时进行调用,以避免在应用关闭期间出现潜在的争用条件。
以下计数器组件在预呈现期间保留计数器状态,然后检索状态以初始化组件。
PrerenderedCounter2.razor
:
@page "/prerendered-counter-2"
@implements IDisposable
@inject ILogger<PrerenderedCounter2> Logger
@inject PersistentComponentState ApplicationState
<PageTitle>Prerendered Counter 2</PageTitle>
<h1>Prerendered Counter 2</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount;
private PersistingComponentStateSubscription persistingSubscription;
protected override void OnInitialized()
{
if (!ApplicationState.TryTakeFromJson<int>(
nameof(currentCount), out var restoredCount))
{
currentCount = Random.Shared.Next(100);
Logger.LogInformation("currentCount set to {Count}", currentCount);
}
else
{
currentCount = restoredCount!;
Logger.LogInformation("currentCount restored to {Count}", currentCount);
}
// Call at the end to avoid a potential race condition at app shutdown
persistingSubscription = ApplicationState.RegisterOnPersisting(PersistCount);
}
private Task PersistCount()
{
ApplicationState.PersistAsJson(nameof(currentCount), currentCount);
return Task.CompletedTask;
}
void IDisposable.Dispose() => persistingSubscription.Dispose();
private void IncrementCount() => currentCount++;
}
执行组件时,currentCount
仅在预呈现期间设置一次。 重新呈现组件时,会还原该值。 下面是示例输出。
info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
currentCount set to 96
info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
currentCount restored to 96
通过使用在预呈现期间使用的相同状态来初始化组件,将只执行一次成本高昂的初始化步骤。 呈现的 UI 也与预呈现 UI 相匹配,因此浏览器不会闪烁。
持久预呈现状态将传输到客户端,用于还原组件状态。 在客户端呈现(CSR)期间,InteractiveWebAssembly
数据将暴露给浏览器,并且不得包含敏感的隐私信息。 在交互式服务器端呈现(交互式 SSR)期间,InteractiveServer
ASP.NET 核心数据保护可确保数据安全传输。
InteractiveAuto
呈现模式结合了 WebAssembly 和服务器交互性,因此有必要考虑向浏览器公开数据,就像在 CSR 案例中一样。
嵌入到页面和视图中的组件 (Razor Pages/MVC)
对于嵌入到 Razor Pages 或 MVC 应用的页面或视图中的组件,必须添加持久组件状态标记帮助程序,并将 <persist-component-state />
HTML 标记置于应用布局的结束 </body>
标记内。 这仅对于 Razor Pages 和 MVC 应用是必需的。 有关详细信息,请参阅 ASP.NET Core 中的持久组件状态标记帮助程序。
Pages/Shared/_Layout.cshtml
:
<body>
...
<persist-component-state />
</body>
交互式路由和预呈现
当 Routes
组件未定义呈现模式时,应用将使用每页/组件交互和导航。 使用每页/组件导航时,在应用变为交互式后,内部导航由增强路由处理。 在此上下文中,†内部是指导航事件的 URL 目标是应用内的 Blazor 终结点。
PersistentComponentState 服务仅适用于初始页面加载,不适用于内部增强的页面导航事件。
如果应用使用持久性组件状态对页面执行完整(非增强)导航,则应用在变为交互式时可以使用持久状态。
如果已建立交互式电路,并且对利用持久性组件状态的页面执行增强导航,则现有电路中不提供 状态,供组件使用。 内部页面请求没有预渲染,并且 PersistentComponentState 服务不了解发生了增强导航。 没有向已在现有线路上运行的组件提供状态更新的机制。 原因是,Blazor 仅支持在运行时初始化时将状态从服务器传递到客户端,而不是在运行时启动之后。
.NET 10(2025 年 11 月)正在考虑有关应对此应用场景的 Blazor 框架的其他工作。 有关不支持的解决方法‡ 详细信息和社区讨论,请参阅支持跨增强页面导航的持久性组件状态 (dotnet/aspnetcore
#51584)。 不支持的解决方法未被微软认可在 Blazor 应用中使用。
自行承担风险地使用第三方包、方法和代码。
PersistentComponentState中介绍了如何禁用增强导航,这虽然会降低性能,但也能避免使用内部页面请求的 Blazor 加载状态的问题。
预呈现指南
预呈现指南按主题组织在 Blazor 文档中。 以下链接涵盖整个文档中所有按主题设置的预呈现指南:
基础
- 概述:客户端和服务器呈现概念
- 路由
- 静态路由与交互式路由
- 从多个程序集路由到组件:交互式路由
- 在预呈现时执行了 OnNavigateAsync 两次:
OnNavigateAsync
- 初创企业
- 环境:读取环境中的环境客户端 Blazor Web App
- 处理错误:预呈现
- SignalR
组件
-
在预呈现期间控制
<head>
内容 - 呈现模式
- 与预呈现相关的 Razor 组件生命周期主题
-
组件初始化 (
OnInitialized{Async}
) -
组件呈现后 (
OnAfterRender{Async}
) - 预呈现后的有状态重新连接
- 使用 JavaScript 互操作预呈现:此部分还出现在有关从 .NET 调用 JavaScript 和从 JavaScript 调用 .NET 的两篇 JS 互操作文章中。
- 处理呈现时的不完整异步操作:针对在服务器上预呈现期间,由于长时间运行的生命周期任务引起的延迟呈现的指南。
-
组件初始化 (
-
QuickGrid
组件示例应用: QuickGrid 示例 Blazor 应用 托管在 GitHub Pages 上。 由于使用社区维护的BlazorWasmPrerendering.Build
GitHub 项目进行静态预呈现,因此站点加载速度较快。 - 在将组件集成到 Razor Pages 和 MVC 应用时预呈现
-
在预呈现期间控制
身份验证和授权
状态管理:处理预呈现:除了处理预呈现部分外,本文的其他几个部分也包括有关预呈现的注释。
对于 .NET 7 或更早版本,请参阅 Blazor WebAssembly 安全附加方案:预呈现身份验证。 查看本部分中的内容后,将文档文章版本选择器下拉列表重置为最新的 .NET 版本,以确保在后续访问时加载最新版本的文档页面。