ASP.NET 核心 Blazor 服务器端状态管理

注释

此版本不是本文的最新版本。 要查看当前版本,请参阅本文的.NET 9 版本

警告

此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 要查看当前版本,请参阅本文的.NET 9 版本

重要

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

要查看当前版本,请参阅本文的.NET 9 版本

本文介绍在服务器端 Blazor 方案中维护用户数据(状态)的常见方法。

维护用户状态

服务器端 Blazor 是有状态的应用框架。 大多数情况下,应用保持与服务器的连接。 用户的状态保留在线路中的服务器内存中

线路中保留的用户状态示例:

  • 呈现的 UI 中组件实例的层次结构及其最新的呈现输出。
  • 组件实例中的字段和属性的值。
  • 在线路范围内的依赖关系注入 (DI) 服务实例中保留的数据。

还可以通过 JavaScript 互操作 调用在浏览器的内存集的 JavaScript 变量中找到用户状态。

如果用户遇到暂时的网络连接丢失问题,Blazor 会尝试将用户重新连接到具有其原始状态的原始线路。 但是,将用户重新连接到服务器内存中的原始电路并非总是能够实现的:

  • 服务器不能永久保留断开连接的线路。 超时后或在服务器面临内存压力时,服务器必须释放断开连接的线路。
  • 在负载均衡的多服务器部署环境中,不再需要单个服务器处理整个请求量时,它可能会失败或被自动删除。 在用户尝试重新连接时,用户的原始服务器处理请求可能会变得不可用。
  • 用户可能会关闭并重新打开其浏览器或重载页面,这会删除浏览器内存中保留的所有状态。 例如,通过 JavaScript 互操作调用设置的 JavaScript 变量值会丢失。

当用户无法重新连接到其原始线路时,该用户会收到新初始化状态的新线路。 这等效于关闭并重新打开桌面应用。

何时保留用户状态

状态暂留并非是自动进行的。 必须在开发应用时采取措施来实现有状态的数据暂留。

通常情况下,在用户主动创建数据,而不是简单地读取已存在的数据时,会跨线路保持状态。

通常,只有用户投入了大量精力所创建的高价值状态才需要数据暂留。 持久化状态可以节省时间或促进商业活动:

  • 多步骤 Web 窗体:如果多步骤 Web 窗体的多个已完成步骤的状态丢失,用户重新输入这些步骤的数据会非常耗时。 如果用户离开窗体并在稍后返回,在这种应用场景下,用户将丢失状态。
  • 购物车:应用中任何代表潜在收入且具有重要商业价值的组件都可以保留。 失去状态的用户(因此,购物车)可能会在以后返回网站时购买更少的产品或服务。

应用只能保留应用状态。 不能保留 UI,如组件实例及其呈现树。 组件和呈现树通常不能序列化。 若要保留 UI 状态(如树视图控件的展开节点),应用必须使用自定义代码将 UI 状态行为建模为可序列化应用状态。

线路状态持久性

在服务器端渲染期间,只要未触发全页刷新,Blazor Web App会话(电路)状态可以在与服务器的连接长时间丢失或主动暂停时持续保留。 这样,用户就可以在以下情况下恢复会话,而不会丢失未保存的工作:

  • 浏览器标签页节流
  • 移动设备用户切换应用
  • 网络中断
  • 主动资源管理(暂停非活动电路)

目前不支持具有线路状态持久性的增强导航,但计划在将来的版本中使用。

如果线路状态可以持久保存,然后稍后恢复,则可以释放服务器资源:

  • 即使断开连接,线路也可能继续执行工作,并消耗 CPU、内存和其他资源。 持久化状态仅消耗开发人员控制的固定内存量。
  • 持久化状态表示应用消耗的内存子集,因此服务器不需要跟踪应用的组件和其他服务器端对象。

以下两种情况会保留状态:

  • 组件状态:组件用于交互式服务器呈现的状态,例如,从数据库检索到的项目列表或用户正在填写的表单。
  • 作用域服务:如当前用户这类保存在服务器端服务中的状态。

Conditions:

  • 该功能仅在交互式服务器渲染中有效。
  • 如果用户刷新页面(应用),则持久状态将丢失。
  • 状态必须为 JSON 可序列化。 循环引用或 ORM 实体可能无法正确序列化。
  • 在循环中呈现组件时使用 @key 来保持唯一性,以避免键冲突。
  • 仅保留必要的状态。 存储过多的数据可能会影响性能。
  • 无自动休眠。 必须主动选择并明确配置状态持久性。
  • 不能保证恢复。 如果状态持久性失败,应用会回退到默认断开连接的体验。

默认情况下,当在AddInteractiveServerComponents文件中调用AddRazorComponentsProgram时,会启用状态持久性。 MemoryCache 是单个应用实例的默认存储实现,存储最多 1,000 条持久化线路两小时,这是可配置的。

使用以下选项更改内存提供程序的默认值:

  • PersistedCircuitInMemoryMaxRetained{CIRCUIT COUNT} 占位符):要保留的最大线路数。 默认值为 1,000 条线路。 例如,使用 2000 保留最多 2,000 个电路的状态。
  • PersistedCircuitInMemoryRetentionPeriod{RETENTION PERIOD} 占位符):最长保留期是 TimeSpan。 默认为 2 小时。 示例:使用TimeSpan.FromHours(3)表示三小时的保留期。
services.Configure<CircuitOptions>(options =>
{
    options.PersistedCircuitInMemoryMaxRetained = {CIRCUIT COUNT};
    options.PersistedCircuitInMemoryRetentionPeriod = {RETENTION PERIOD};
});

跨线路保留组件状态是基于现有 PersistentComponentState API 构建的,它继续保留采用交互式呈现模式的预呈现组件的状态。 有关详细信息,请参阅 ASP.NET 核心 Blazor 预呈现状态持久性

[注意]保留预呈现的组件状态适用于任何交互式呈现模式,但线路状态持久性仅适用于 交互式服务器 呈现模式。

批注组件属性 [SupplyParameterFromPersistentComponentState] 以启用线路状态持久性。 以下示例还对具有 @key 指令属性 的项进行键,以便为每个组件实例提供唯一标识符:

@foreach (var item in Items)
{
    <ItemDisplay @key="@($"unique-prefix-{item.Id}")" Item="item" />
}

@code {
    [SupplyParameterFromPersistentComponentState]
    public List<Item> Items { get; set; }

    protected override async Task OnInitializedAsync()
    {
        Items ??= await LoadItemsAsync();
    }
}

若要保留范围服务的状态,请使用 为服务属性 [SupplyParameterFromPersistentComponentState]添加注释,将服务添加到服务集合,并使用服务调用 RegisterPersistentService 扩展方法:

public class CustomUserService
{
    [SupplyParameterFromPersistentComponentState]
    public string UserData { get; set; }
}

services.AddScoped<CustomUserService>();

services.AddRazorComponents()
  .AddInteractiveServerComponents()
  .RegisterPersistentService<CustomUserService>(RenderMode.InteractiveAuto);

【注意】以上示例在组件预呈现中保留 UserData 状态,适用于交互式服务器和交互式 WebAssembly 呈现,因为 RenderMode.InteractiveAuto 设置为 RegisterPersistentService。 但是,线路状态持久性仅适用于 交互式服务器 呈现模式。

若要处理分布式状态持久性(并在配置时充当默认状态持久性机制),请向应用分配一个 HybridCache (API: HybridCache),该应用将配置其自己的暂留期(PersistedCircuitDistributedRetentionPeriod默认情况下为 8 小时)。 HybridCache 之所以使用,是因为它提供了一种统一的分布式存储方法,不需要每个存储提供程序的单独包。

在以下示例中,使用 HybridCache 存储提供程序实现一个

services.AddHybridCache()
    .AddRedis("{CONNECTION STRING}");

services.AddRazorComponents()
    .AddInteractiveServerComponents();

在前面的示例中, {CONNECTION STRING} 占位符表示 Redis 缓存连接字符串,该连接字符串应使用安全方法提供,例如开发环境中的 机密管理器 工具或 Azure Key Vault 以及任何环境中 Azure 部署应用的 Azure 托管标识

暂停和恢复电路

暂停和恢复线路,实现可提高应用可伸缩性的自定义策略。

暂停电路时,会将有关电路的详细信息存储在客户端浏览器存储中,并逐出电路,从而释放服务器资源。 恢复线路会建立一个新线路,并使用持久化状态对其进行初始化。

从 JavaScript 事件处理程序:

  • 调用 Blazor.pauseCircuit 以暂停线路。
  • 呼叫 Blazor.resumeCircuit 以恢复线路。

以下示例假定应用不需要线路,该线路不可见:

window.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    Blazor.pauseCircuit();
  } else if (document.visibilityState === 'visible') {
    Blazor.resumeCircuit();
  }
});

跨线路保留状态

通常情况下,在用户主动创建数据,而不是简单地读取已存在的数据时,会跨线路保持状态。

若要跨线路保留状态,应用必须将数据保存到服务器的内存以外的其他存储位置。 状态暂留并非是自动进行的。 必须在开发应用时采取措施来实现有状态的数据暂留。

通常,只有用户投入了大量精力所创建的高价值状态才需要数据暂留。 在下面的示例中,保留状态可以节省时间或有助于商业活动:

  • 多步骤 Web 窗体:如果多步骤 Web 窗体的多个已完成步骤的状态丢失,用户重新输入这些步骤的数据会非常耗时。 如果用户离开窗体并在稍后返回,在这种应用场景下,用户将丢失状态。
  • 购物车:应用中任何代表潜在收入且具有重要商业价值的组件都可以保留。 如果用户丢失了其状态,进而丢失了其购物车,则在他们稍后返回站点时可购买较少的产品或服务。

应用只能保留应用状态。 不能保留 UI,如组件实例及其呈现树。 组件和呈现树通常不能序列化。 若要保留 UI 状态(如树视图控件的展开节点),应用必须使用自定义代码将 UI 状态行为建模为可序列化应用状态。

服务器端存储

对于跨多个用户和设备的永久数据持久性,应用可以使用服务器端存储。 选项包括:

  • Blob 存储
  • 键值存储
  • 关系型数据库
  • 表存储

保存数据后,将保留用户的状态,并在任何新的线路中可用。

有关 Azure 数据存储选项的详细信息,请参阅以下内容:

浏览器存储

有关详细信息,请参阅 使用受保护的浏览器存储的 ASP.NET Core 状态管理Blazor

其他资源