Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Este artigo explica como persistir o estado dos componentes durante a pré-renderização em Blazor aplicações usando o serviço Persistent Component State. Vais aprender a usar o [PersistentState] atributo, o PersistentComponentState serviço diretamente e a criar serializadores personalizados para o estado persistente.
Sem persistir o estado do componente, o estado usado durante a pré-renderização é perdido e deve ser recriado quando o aplicativo estiver totalmente carregado. Se qualquer estado for criado de forma assíncrona, a interface pode piscar à medida que a interface pré-renderizada é substituída por conteúdo de carregamento temporário e depois totalmente renderizada novamente.
Considere o seguinte componente PrerenderedCounter1 contador. O componente define um valor de contador aleatório inicial durante o processo de pré-renderização no método de ciclo de vidaem OnInitialized é executado uma segunda vez.
PrerenderedCounter1.razor:
@page "/prerendered-counter-1"
@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++;
}
Observação
Se a aplicação adotar o de roteamento interativo PrerenderedCounter1 para ver a saída a seguir. Para obter mais informações, consulte a seção de roteamento interativo e pré-renderização.
Execute o aplicativo e inspecione o log do componente. O seguinte é um exemplo de saída.
info: BlazorSample.Components.Pages.PrerenderedCounter1[0]
currentCount set to 41
info: BlazorSample.Components.Pages.PrerenderedCounter1[0]
currentCount set to 92
A primeira contagem registrada ocorre durante a pré-renderização. Novamente, a contagem é definida após a pré-renderização, quando o componente é renderizado novamente. Há também uma cintilação na interface do usuário quando a contagem é atualizada de 41 para 92.
Para manter o valor inicial do contador durante a pré-renderização, o Blazor suporta a persistência do estado numa página pré-renderizada usando o serviço PersistentComponentState (e para componentes incorporados em páginas ou vistas de aplicações Razor Pages ou MVC, o Persist Component State Tag Helper).
Ao inicializar componentes com o mesmo estado usado durante a pré-renderização, todas as etapas de inicialização dispendiosas são executadas apenas uma vez. A interface do usuário renderizada também corresponde à interface do usuário pré-renderizada, portanto, nenhuma cintilação ocorre no navegador.
O estado pré-renderizado persistente é transferido para o cliente, onde é usado para restaurar o estado do componente. Durante a renderização do lado do cliente (CSR, InteractiveWebAssembly), os dados são expostos ao navegador e não devem conter informações confidenciais e privadas. Durante a renderização interativa do lado do servidor (SSR interativo, InteractiveServer), ASP.NET Core Proteção de Dados assegura que os dados são transferidos de forma segura. O modo de renderização InteractiveAuto combina WebAssembly e interatividade do servidor, por isso é necessário considerar a exposição de dados ao navegador, como no caso CSR.
Para persistir no estado pré-renderizado usando o PersistentComponentState serviço, aplique o [PersistentState] atributo às propriedades public. O estado é recuperado quando o componente é renderizado interativamente ou o PersistentComponentState serviço é instanciado.
Use public propriedades porque a reflexão é usada pelo framework para tarefas como cortar código não utilizado e geração de código-fonte.
Por padrão, as propriedades são serializadas usando o System.Text.Json serializador com configurações padrão e persistem no HTML pré-renderizado. A serialização não é segura e requer a preservação dos tipos usados. Para mais informações, consulte Configure o Trimmer para ASP.NET Core Blazor.
O seguinte componente de contador persiste o estado do contador durante a pré-renderização e recupera o estado para inicializar o componente:
- O
[PersistentState]atributo aplica-se à propriedade pública nulaCurrentCountdo tipoint?. - O estado do contador é atribuído quando
nullemOnInitializede restaurado automaticamente quando o componente é renderizado interativamente.
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: @CurrentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
[PersistentState]
public int? CurrentCount { get; set; }
protected override void OnInitialized()
{
if (CurrentCount is null)
{
CurrentCount = Random.Shared.Next(100);
Logger.LogInformation("CurrentCount set to {Count}", CurrentCount);
}
else
{
Logger.LogInformation("CurrentCount restored to {Count}", CurrentCount);
}
}
private void IncrementCount() => CurrentCount++;
}
Quando o componente é executado, CurrentCount é definido apenas uma vez durante a pré-renderização. O valor é restaurado quando o componente é renderizado novamente. O seguinte é um exemplo de saída.
Observação
Se a aplicação adotar o de roteamento interativo
info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
CurrentCount set to 96
info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
CurrentCount restored to 96
No exemplo a seguir que serializa o estado para vários componentes do mesmo tipo:
- Propriedades públicas anotadas com o
[PersistentState]atributo são serializadas durante a pré-renderização. - O
@keyatributo directive é usado para garantir que o estado esteja corretamente associado à instância do componente. - A
Elementpropriedade é inicializada noOnInitializedmétodo de ciclo de vida para evitar exceções de referência nula, da mesma forma que as referências nulas são evitadas para parâmetros de consulta e dados dos formulários.
PersistentChild.razor:
<div>
<p>Current count: @Element.CurrentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</div>
@code {
[PersistentState]
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" />
}
Observação
Um(a) JsonSerializerContext configurado(a) pelo utilizador não é aplicado(a) à desserialização de união em C#. Em vez disso, use [JsonUnion(TypeClassifier = typeof({TYPE CLASSIFIER}))], onde o marcador de posição {TYPE CLASSIFIER} é o classificador de tipo.
Estado de serialização para serviços
No exemplo a seguir que serializa o estado para um serviço de injeção de dependência:
- As propriedades anotadas com o atributo
[PersistentState]são serializadas na pré-renderização e desserializadas quando o aplicativo se torna interativo. - O RegisterPersistentService método de extensão é usado para registrar o serviço para persistência. O modo de renderização é necessário porque o modo de renderização não pode ser inferido a partir do tipo de serviço. Use qualquer um dos seguintes valores:
-
RenderMode.Server: O serviço está disponível para o modo de renderização do Servidor Interativo. -
RenderMode.Webassembly: O serviço está disponível para o modo de renderização Interactive Webassembly. -
RenderMode.InteractiveAuto: O serviço está disponível para os modos de renderização Interactive Server e Interactive Webassembly se um componente for renderizado em qualquer um desses modos.
-
- O serviço é resolvido durante a inicialização de um modo de renderização interativo e as propriedades anotadas com o
[PersistentState]atributo são desserializadas.
Observação
Apenas os serviços persistentes com escopo são suportados no servidor. No cliente de WebAssembly (nos modos de renderização InteractiveAuto ou InteractiveWebAssembly), o serviço tem de estar registado como um singleton.
Consulte o registo de serviços de projetos do cliente para mais detalhes.
As propriedades serializadas são identificadas a partir da instância de serviço real:
- Esta abordagem permite marcar uma abstração como um serviço persistente.
- Permite que as implementações reais sejam internas ou de tipos diferentes.
- Suporta código compartilhado em assemblies diferentes.
- O resultado em cada instância revela as mesmas propriedades.
O seguinte serviço de contagem, CounterTracker, marca a sua propriedade atual de contagem, CurrentCount, com o atributo [PersistentState]. A propriedade pública é serializada durante a pré-renderização e desserializada quando o aplicativo se torna interativo onde quer que o serviço seja injetado.
CounterTracker.cs:
public class CounterTracker
{
[PersistentState]
public int CurrentCount { get; set; }
public void IncrementCount()
{
CurrentCount++;
}
}
Registo de serviços do projeto de servidor
No ficheiro Program do projeto do servidor, regista o serviço de âmbito limitado e regista o serviço de persistência com RegisterPersistentService. No exemplo a seguir, o serviço CounterTracker está disponível para os modos de renderização Interactive Server e Interactive WebAssembly se um componente for renderizado em qualquer um dos modos porque está registrado com RenderMode.InteractiveAuto.
Se o ficheiro Program ainda não usar o espaço de nomes Microsoft.AspNetCore.Components.Web, adicione a seguinte instrução using no topo do ficheiro:
using Microsoft.AspNetCore.Components.Web;
Onde os serviços são registados no ficheiro Program do projeto do servidor:
builder.Services.AddScoped<CounterTracker>();
builder.Services.AddRazorComponents()
.RegisterPersistentService<CounterTracker>(RenderMode.InteractiveAuto);
Registo de serviços de projetos para clientes
Para RenderMode.InteractiveAuto ou RenderMode.InteractiveWebAssembly, o serviço também tem de estar registado no ficheiro Program do projeto .Client. Registe-o como singleton, não com escopo:
builder.Services.AddSingleton<CounterTracker>();
O ComponentStatePersistenceManager do lado do cliente é registado como singleton, pelo que resolve as respetivas dependências a partir do fornecedor de serviços raiz. Evite um registo de serviço com escopo. Se o serviço estiver registado incorretamente como com âmbito no cliente, resolvê-lo a partir do fornecedor raiz lança um DirectScopedResolvedFromRootException em ambientes de desenvolvimento. Para além da exceção, um registo com âmbito produz duas instâncias separadas no cliente: uma resolvida a partir da raiz durante a restauração do estado e outra a partir do âmbito da aplicação para componentes injetados. Isto significa que o estado restaurado não chega à instância que os componentes utilizam. Registar o serviço como singleton no cliente garante que a mesma instância é usada tanto para restauração de estados como para injeção de componentes.
Injete o CounterTracker serviço em um componente e use-o para incrementar um contador. Para fins de demonstração no exemplo a seguir, o valor da propriedade do serviço CurrentCount é definido como 10 somente durante a pré-renderização.
Pages/Counter.razor:
@page "/counter"
@inject CounterTracker CounterTracker
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p>Rendering: @RendererInfo.Name</p>
<p role="status">Current count: @CounterTracker.CurrentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
protected override void OnInitialized()
{
if (!RendererInfo.IsInteractive)
{
CounterTracker.CurrentCount = 10;
}
}
private void IncrementCount()
{
CounterTracker.IncrementCount();
}
}
Para utilizar o componente anterior para demonstrar a persistência da contagem de 10 no CounterTracker.CurrentCount, navegue até ao componente e recarregue o navegador, o que aciona a pré-renderização. Quando a pré-renderização ocorre, você vê RendererInfo.Name brevemente indicar "Static" antes de exibir "Server" após a renderização final. O contador começa em 10.
Use o PersistentComponentState serviço diretamente em vez do modelo declarativo
Como alternativa ao uso do modelo declarativo para persistir o estado com o [PersistentState] atributo, você pode usar o PersistentComponentState serviço diretamente, que oferece maior flexibilidade para cenários complexos de persistência de estado. Chamada PersistentComponentState.RegisterOnPersisting para registrar um retorno de chamada para persistir o estado do componente durante a pré-renderização. O estado é recuperado quando o componente é renderizado interativamente. Faça a chamada no final do código de inicialização para evitar uma possível condição de concorrência durante o desligamento da aplicação.
O exemplo de componente de contador a seguir mantém o estado do contador durante a pré-renderização e recupera esse estado para inicializar o componente.
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();
}
Quando o componente é executado, currentCount é definido apenas uma vez durante a pré-renderização. O valor é restaurado quando o componente é renderizado novamente. O seguinte é um exemplo de saída.
Observação
Se a aplicação adotar o de roteamento interativo
info: BlazorSample.Components.Pages.PrerenderedCounter3[0]
currentCount set to 96
info: BlazorSample.Components.Pages.PrerenderedCounter3[0]
currentCount restored to 96
Para preservar o estado pré-renderizado, decida qual estado deve persistir usando o serviço PersistentComponentState. PersistentComponentState.RegisterOnPersisting Registra um retorno de chamada para persistir o estado do componente durante a pré-renderização. O estado é recuperado quando o componente é renderizado interativamente. Faça a chamada no final do código de inicialização para evitar uma possível condição de concorrência durante o desligamento da aplicação.
O exemplo de componente de contador a seguir mantém o estado do contador durante a pré-renderização e recupera esse estado para inicializar o componente.
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++;
}
Quando o componente é executado, currentCount é definido apenas uma vez durante a pré-renderização. O valor é restaurado quando o componente é renderizado novamente. O seguinte é um exemplo de saída.
Observação
Se a aplicação adotar o de roteamento interativo
info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
currentCount set to 96
info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
currentCount restored to 96
Extensibilidade de serialização para estado de componente persistente
Implemente um serializador personalizado com PersistentComponentStateSerializer<T>. Sem um serializador personalizado registrado, a serialização retorna à serialização JSON existente.
O exemplo seguinte cria um serializador personalizado para int? tipos que utiliza um formato personalizado para demonstrar a extensibilidade da serialização. O serializador prefixa valores inteiros com "CUSTOM:" para os distinguir claramente da serialização JSON.
O serializador é executado independentemente para tipos anuláveis e não anuláveis, pelo que o serializador seguinte não é executado em tipos int; apenas os tipos inteiros anuláveis (int?) são processados.
Fazer login no exemplo seguinte é para fins de demonstração e normalmente não é implementado numa aplicação de produção.
CustomIntSerializer.cs:
using System;
using System.Buffers;
using System.Text;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
namespace BlazorSample;
public class CustomIntSerializer(ILogger<CustomIntSerializer> logger)
: PersistentComponentStateSerializer<int?>
{
public override void Persist(int? value, IBufferWriter<byte> writer)
{
string customFormat =
value is not null ? $"CUSTOM:{value}" : $"CUSTOM:null";
logger.LogInformation(
"Persisting value {Value} with custom format: {CustomFormat}", value,
customFormat);
byte[] bytes = Encoding.UTF8.GetBytes(customFormat);
writer.Write(bytes);
}
public override int? Restore(ReadOnlySequence<byte> data)
{
byte[] bytes = data.ToArray();
string text = Encoding.UTF8.GetString(bytes);
logger.LogInformation("Restoring value from custom format: {CustomFormat}",
text);
if (text.StartsWith("CUSTOM:", StringComparison.Ordinal))
{
var remainingText = text.AsSpan(7);
if (!remainingText.SequenceEqual("null"))
{
if (int.TryParse(remainingText, out int value))
{
return value;
}
}
else
{
return null;
}
}
// Fallback to direct parsing if format is unexpected
return int.TryParse(text, out int fallbackValue) ? fallbackValue : null;
}
}
O serializador personalizado está registado no ficheiro da Program aplicação:
builder.Services.AddSingleton<PersistentComponentStateSerializer<int?>,
CustomIntSerializer>();
O tipo int? é automaticamente persistido e restaurado com o serializador personalizado:
[PersistentState]
public int? CurrentCount { get; set; }
Usando o serializador anterior com o componente PrerenderedCounter2 (PrerenderedCounter2.razor) mostrado na introdução deste artigo, é registada uma saída semelhante à seguinte.
Observação
Se a aplicação adotar o de roteamento interativo
info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
CurrentCount set to 49
info: BlazorSample.CustomIntSerializer[0]
Persisting value 49 with custom format: CUSTOM:49
info: BlazorSample.CustomIntSerializer[0]
Restoring value from custom format: CUSTOM:49
info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
CurrentCount restored to 49
Componentes incorporados em páginas e visualizações (Razor Pages/MVC)
Para componentes incorporados numa página ou vista de uma aplicação Razor Pages ou MVC, deves adicionar a etiqueta Persist Component State Tag Helper com a etiqueta HTML <persist-component-state /> dentro da etiqueta de fecho </body> do layout da aplicação. Isso só é necessário para Razor Pages e aplicativos MVC. Para mais informações, consulte Persist Component State Tag Helper em ASP.NET Core.
Pages/Shared/_Layout.cshtml:
<body>
...
<persist-component-state />
</body>
Roteamento e pré-renderização interativos
Quando o componente Routes não define um modo de renderização, o aplicativo está usando interatividade e navegação por página/componente. Usando a navegação por página/componente, a navegação interna é manipulada pelo roteamento aprimorado depois que o aplicativo se torna interativo. "Navegação interna", neste contexto, significa que o destino URL do evento de navegação é um Blazor endpoint no aplicativo.
Blazor suporta a manipulação do estado persistente do componente durante a navegação avançada. O estado persistido durante a navegação avançada pode ser lido por componentes interativos na página.
Por padrão, o estado do componente persistente só é carregado por componentes interativos quando eles são inicialmente carregados na página. Isso impede que estados importantes, como dados em um formulário da Web editado, sejam substituídos se eventos de navegação aprimorados adicionais para a mesma página ocorrerem após o carregamento do componente.
Se os dados forem apenas de leitura e não mudarem frequentemente, opte por permitir atualizações durante a navegação melhorada definindo AllowUpdates como [PersistentState] no atributo true. Isto é útil para cenários como a exibição de dados em cache que são caros de obter mas que não mudam frequentemente, como dados de previsão meteorológica no exemplo seguinte:
[PersistentState(AllowUpdates = true)]
public WeatherForecast[]? Forecasts { get; set; }
protected override async Task OnInitializedAsync()
{
Forecasts ??= await ForecastService.GetForecastAsync();
}
Observação
PersistentStateAttribute.AllowUpdates não controla se o valor atual da propriedade é capturado pela persistência do estado do circuito quando o circuito pausa, incluindo uma pausa automática causada pela inatividade do tabulador.
Observação
PersistentStateAttribute.AllowUpdates não controla se o valor atual da propriedade é capturado pela persistência do estado do circuito quando o circuito pausa.
Para ignorar a restauração do estado durante a pré-renderização, defina RestoreBehavior como SkipInitialValue:
[PersistentState(RestoreBehavior = RestoreBehavior.SkipInitialValue)]
public string NoPrerenderedData { get; set; }
Para ignorar a restauração do estado durante a reconexão, defina RestoreBehavior como SkipLastSnapshot. Isso pode ser útil para garantir dados atualizados após a reconexão:
[PersistentState(RestoreBehavior = RestoreBehavior.SkipLastSnapshot)]
public int CounterNotRestoredOnReconnect { get; set; }
Chame PersistentComponentState.RegisterOnRestoring para registar um retorno de chamada para controlar de forma imperativa como o estado é restaurado, semelhante a como PersistentComponentState.RegisterOnPersisting fornece controlo total de como o estado é persistido.
O serviço PersistentComponentState só funciona no carregamento inicial da página e não em eventos internos de navegação de página avançada.
Se o aplicativo executar uma navegação completa (não aprimorada) para uma página utilizando o estado de componente persistente, o estado persistente será disponibilizado para o aplicativo usar quando se tornar interativo.
Se um circuito interativo já tiver sido estabelecido e uma navegação aprimorada for realizada em uma página utilizando o estado persistente do componente, o estado não será disponibilizado no circuito existente para o componente usar. Não há pré-renderização para a solicitação de página interna e o serviço PersistentComponentState não tem conhecimento de que uma navegação aprimorada ocorreu. Não há nenhum mecanismo para fornecer atualizações de estado para componentes que já estão em execução em um circuito existente. A razão para isso é que Blazor só suporta a passagem de estado do servidor para o cliente no momento em que o tempo de execução é inicializado, não depois que o tempo de execução é iniciado.
Desativar a navegação melhorada, que reduz o desempenho mas também evita o problema de carregar o estado com PersistentComponentState para pedidos internos de página, está abordado em ASP.NET Core Blazor navegação. Alternativamente, atualize a aplicação para .NET 10 ou posterior, onde Blazor suporta a gestão do estado persistente dos componentes durante navegação melhorada.