使用静态服务器端呈现(静态 SSR)的 ASP.NET Core Razor 类库 (RCL)

本文为考虑支持静态服务器端呈现(静态 SSR)的组件库作者提供指导。

Blazor 鼓励开发开源和商业组件库生态系统,其正式名称为 Razor 类库 (RCL)。 开发人员还可以创建可重用组件,以便在自己的公司内跨应用私下共享组件。 理想情况下,开发组件是为了与尽可能多的托管模型和呈现模式兼容。 静态 SSR 引入了比交互式呈现模式更具挑战性的额外限制。

了解静态 SSR 的功能和限制

静态 SSR 是一种组件在服务器处理传入 HTTP 请求时运行的模式。 Blazor 将组件呈现为 HTML,并包含在响应中。 发送响应后,将丢弃服务器端组件和呈现器状态,因此浏览器中只剩下 HTML。

这种模式的好处是托管成本更低且更具可缩放性,因为不需要持续的服务器资源来保存组件状态,浏览器和服务器之间不需要维护持续的连接,并且浏览器中不需要 WebAssembly 负载。

所有现有组件仍可与静态 SSR 一起使用。 然而,这种模式的代价是,由于以下原因,无法运行事件处理程序(例如@onclick†):

  • 浏览器中没有 .NET 代码来运行它们。
  • 服务器已直接丢弃执行事件处理程序或重新呈现相同组件实例所需的任何组件和呈现器状态。

†表单的 @onsubmit 事件处理程序有一个特殊的例外,无论呈现模式如何,它始终正常运行。

这相当于在启动 Blazor 线路或 .NET WebAssembly 运行时之前,组件在预呈现期间的行为方式。

对于唯一作用是生成只读 DOM 内容的组件,静态 SSR 的这些行为完全足够了。 但是,库作者必须考虑在库中包含交互式组件时要采用什么方法。

面向组件作者的选项

主要有以下三种方法:

  • 不要使用交互行为(基本)

    对于唯一作用是生成只读 DOM 内容的组件,开发人员无需执行任何特殊操作。 这些组件自然适用于任何呈现模式。

    示例:

    • 一个“用户卡”组件,加载与人员相对应的数据,并在风格化 UI 中显示这些数据,包括照片、职位和其他详细信息。
    • 充当 HTML <video> 元素包装器的“视频”组件,使其更易于在 Razor 组件中使用。
  • 需要交互式呈现(基本)

    可以选择要求组件仅用于交互式呈现。 这会限制组件的适用性,但意味着你可以自由依赖任意事件处理程序。 即便如此,你仍应避免声明特定 @rendermode 内容,并允许使用你的库的应用作者选择一个。

    示例:

    • 一个视频编辑组件,用户可以在其中拼接视频片段并对其进行重新排序。 即使有方法用纯 HTML 按钮和表单发布来表示这些编辑操作,如果没有真正的交互性,用户体验也是不可接受的。
    • 协作文档编辑器必须实时显示其他用户的活动。
  • 使用交互式行为,但针对静态 SSR 和渐进式增强进行设计(高级)

    许多交互行为仅使用 HTML 功能即可实现。 通过充分了解 HTML 和 CSS,通常可以生成适用于静态 SSR 的有用功能基线。 仍然可以声明实现更高级的可选行为的事件处理程序,这些行为仅在交互式呈现模式下工作。

    示例:

    • 网格组件。 在静态 SSR 下,组件可能仅支持显示数据和在页面之间导航(使用 <a> 链接实现)。 与交互式呈现一起使用时,该组件可以添加实时排序和筛选。
    • 一个选项卡集组件。 只要使用 <a> 链接实现选项卡之间的导航,并且状态仅保存在 URL 查询参数中,该组件就可以在没有 @onclick 的情况下工作。
    • 一个高级文件上传组件。 在静态 SSR 下,该组件可能表现为本机 <input type=file>。 与交互式呈现一起使用时,该组件还可以显示上传进度。
    • 股票行情。 在静态 SSR 下,该组件可能会在呈现 HTML 时显示股票报价。 与交互式呈现一起使用时,该组件可以实时更新股票价格。

对于任何这些策略,还可以选择使用 JavaScript 实现交互功能。

要在这些方法中进行选择,可重用 Razor 组件作者必须进行成本/权益权衡。 如果组件支持所有呈现模式(包括静态 SSR),则其会更有用并且拥有更广泛的潜在用户群。 但是,设计和实现支持并充分利用每种呈现模式的组件需要更多工作。

何时使用 @rendermode 指令

在大多数情况下,可重用组件作者不应指定呈现模式,即使需要交互性也是如此。 这是因为组件作者不知道应用是否通过 InteractiveAuto 启用对 InteractiveServer 和/或 InteractiveWebAssembly 的支持。 通过不指定 @rendermode,组件作者将选择权留给应用程序开发人员。

即使组件作者认为需要交互性,应用作者仍然可能认为只使用静态 SSR 就足够了。 例如,具有拖动和缩放交互性的映射组件似乎需要交互性。 但是,某些方案可能只要求呈现静态地图图像并避免拖动/缩放功能。

可重用组件作者应在其组件上使用 @rendermode 指令的唯一原因是该实现从根本上与一种特定呈现模式耦合,并且如果在其他模式下使用,肯定会导致错误。 考虑一个组件,其核心用途是使用 Windows 或特定于 Linux 的 API 直接与主机操作系统交互。 可能无法在 WebAssembly 上使用此类组件。 如果是这样,则为组件声明 @rendermode InteractiveServer 是合理的。

流式渲染

可重用 Razor 组件可以自由地声明 @attribute [StreamRendering],用于流呈现[StreamRendering] 属性 API)。 这会导致静态 SSR 期间进行增量 UI 更新。 由于相同的数据加载模式会在交互式呈现期间产生增量 UI 更新(不管是否存在 [StreamRendering] 属性),组件在所有情况下都可以正常运行。 即使在服务器上禁止流式处理静态 SSR 的情况下,组件仍然呈现其正确的最终状态。

可重用 Razor 组件可以使用链接和增强的导航。 无论是否有交互式 Router 组件,也无论是否在 DOM 的上级级别启用/禁用增强型导航,HTML <a> 标记都应产生等效的行为。

跨呈现模式使用表单

可重用 Razor 组件可能包括表单(<form><EditForm>),因为这些组件可以实现为在静态和交互式呈现模式下等效工作。

请考虑以下示例:

<EditForm Enhance FormName="NewProduct" Model="Model" OnValidSubmit="SaveProduct">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p><label>Name: <InputText @bind-Value="Item.Name" /></label></p>

    <button type="submit">Submit</button>
</EditForm>

@code {
    [SupplyParameterFromForm]
    private Product? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    private async Task Save()
    {
        ...
    }
}

EnhanceFormNameSupplyParameterFromFormAttribute API 仅在静态 SSR 期间使用,在交互式呈现期间被忽略。 表单在交互式和静态 SSR 期间都正常工作。

避免特定于静态 SSR 的 API

HttpContext 仅在静态 SSR 期间可用,因此在创建跨呈现模式工作的组件时不要依赖 HttpContext。 在交互式呈现期间使用 HttpContext API 没有意义,因为这些时候没有活动的 HTTP 请求。 考虑设置 HTTP 状态代码或写入 HTTP 响应没有意义。

可重用组件可在可用时自由接收 HttpContext,如下所示:

[CascadingParameter]
public HttpContext? Context { get; set; }

该值在交互式呈现期间为 null,并且仅在静态 SSR 期间设置。

在许多情况下,有比使用 HttpContext 更好的替代方法。 如果需要知道当前 URL 或执行重定向,NavigationManager 上的 API 适用于所有呈现模式。 如果需要了解用户的身份验证状态,请使用 Blazor 的 AuthenticationStateProvider 服务(AuthenticationStateProvider 文档),而不使用 HttpContext.User