预呈现 ASP.NET Core Razor 组件

注意

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

重要

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

有关当前版本,请参阅本文.NET 9 版本。

本文介绍了 Razor 中服务器呈现组件的 Blazor Web App 组件预呈现场景。

预呈现是最初在服务器上呈现页面内容的过程,而无需为呈现的控件启用事件处理程序。 服务器会根据初始请求尽快输出页面的 HTML UI,这会让应用感觉对用户的响应更强。 预呈现还可以通过呈现搜索引擎用于计算网页排名的初始 HTTP 响应的内容,来改进搜索引擎优化 (SEO)

保留预呈现状态

在不保留预呈现状态的情况下,在预呈现期间使用的状态将丢失,并且在完全加载应用时必须重新创建。 如果有任何状态是异步创建的,则 UI 可能会闪烁,因为预呈现组件时,将替换预呈现的 UI。

请考虑以下 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 应用的页面或视图中的组件,使用持久组件状态标记帮助程序)。

若要保留预呈现状态,请决定使用 PersistentComponentState 服务保留什么状态。 PersistentComponentState.RegisterOnPersisting 注册回调以在暂停应用之前保留组件状态。 在应用恢复时检索状态。

以下示例演示了一般模式:

  • {TYPE} 占位符表示要持久保存的数据类型。
  • {TOKEN} 占位符是状态标识符字符串。 请考虑使用 nameof({VARIABLE}),其中 {VARIABLE} 占位符是保存状态的变量的名称。 对状态标识符使用 nameof() 可避免使用带引号的字符串。
@implements IDisposable
@inject PersistentComponentState ApplicationState

...

@code {
    private {TYPE} data;
    private PersistingComponentStateSubscription persistingSubscription;

    protected override async Task OnInitializedAsync()
    {
        persistingSubscription = 
            ApplicationState.RegisterOnPersisting(PersistData);

        if (!ApplicationState.TryTakeFromJson<{TYPE}>(
            "{TOKEN}", out var restored))
        {
            data = await ...;
        }
        else
        {
            data = restored!;
        }
    }

    private Task PersistData()
    {
        ApplicationState.PersistAsJson("{TOKEN}", data);

        return Task.CompletedTask;
    }

    void IDisposable.Dispose()
    {
        persistingSubscription.Dispose();
    }
}

以下计数器组件在预呈现期间保留计数器状态,然后检索状态以初始化组件。

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()
    {
        persistingSubscription =
            ApplicationState.RegisterOnPersisting(PersistCount);

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

    private Task PersistCount()
    {
        ApplicationState.PersistAsJson(nameof(currentCount), currentCount);

        return Task.CompletedTask;
    }

    void IDisposable.Dispose() => persistingSubscription.Dispose();

    private void IncrementCount() => currentCount++;
}

执行组件时,currentCount 仅在预呈现期间设置一次。 重新呈现组件时,会还原该值。 下面是示例输出。

注意

如果应用采用 交互式路由,并且通过内部 增强导航访问页面,则不会进行预呈现。 因此,必须为 PrerenderedCounter2 组件执行完整页面重载才能查看以下输出。 有关详细信息,请参阅 交互式路由和预呈现 部分。

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)期间,InteractiveServerASP.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 应用中使用。 自行承担风险地使用第三方包、方法和代码。

ASP.NET Core Blazor路由和导航中介绍了如何禁用增强导航,这虽然会降低性能,但也能避免使用内部页面请求的 PersistentComponentState 加载状态的问题。

预呈现指南

预呈现指南按主题组织在 Blazor 文档中。 以下链接涵盖整个文档中所有按主题设置的预呈现指南: