预呈现 ASP.NET Core Razor 组件

本文介绍了 Blazor Web 应用中服务器呈现组件的 Razor 组件预呈现方案。

预呈现是在服务器上初始呈现页面内容的过程(无需为呈现的控件启用事件处理程序)。 服务器会根据初始请求尽快输出页面的 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 相匹配,因此浏览器不会闪烁。

嵌入到页面和视图中的组件 (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>

交互式路由和预呈现

交互式路由的内部导航不涉及从服务器请求新页面内容。 因此,内部页面请求不会发生预呈现。

PersistentComponentState 服务仅适用于初始页面加载,不适用于增强的页面导航事件。 如果应用使用持久性组件状态对页面执行完整(非增强)导航,则应用在变为交互式时可以使用持久状态。 但是,如果已建立交互式线路,并且对呈现持久组件状态的页面执行了增强导航,则该状态在现有线路中不可用。 PersistentComponentState 服务不支持增强的导航,并且没有可以向正在运行的组件提供状态更新的机制。

预呈现指南

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