Partilhar via


ASP.NET Persistência do estado pré-renderizado do núcleo Blazor

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 algum estado for criado de forma assíncrona, a UI pode piscar quando a UI pré-renderizada for substituída ao rerenderizar o componente.

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 . Quando o componente é renderizado interativamente, o valor de contagem inicial é substituído quando 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 e a página for alcançada através de uma navegação interna aprimorada, a pré-renderização não ocorrerá. Portanto, você deve executar uma recarga de página inteira para o componente 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 Data Protection garante que os dados sejam transferidos com segurança. 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 preservar o estado pré-renderizado, use o atributo [PersistentState] para persistir o estado nas propriedades. As propriedades com esse atributo são automaticamente persistidas usando o serviço PersistentComponentState durante a pré-renderização. O estado é recuperado quando o componente é renderizado interativamente ou o serviço é instanciado.

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 obter mais informações, consulte Configurar 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 é aplicado ao tipo anulável int (CurrentCount).
  • O estado do contador é atribuído quando null em OnInitialized e 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 e a página for alcançada através de uma navegação interna aprimorada, a pré-renderização não ocorrerá. Portanto, deve executar uma recarga completa da página para que o componente possa visualizar a saída a seguir. Para obter mais informações, consulte a seção de roteamento interativo e pré-renderização.

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:

  • As propriedades anotadas com o atributo [PersistentState] são serializadas durante a pré-renderização.
  • O @key atributo directive é usado para garantir que o estado esteja corretamente associado à instância do componente.
  • A Element propriedade é inicializada no OnInitialized mé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" />
}

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

Somente serviços com escopo persistente são suportados.

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 é serializada durante a pré-renderização e desserializada quando a aplicação se torna interativa, independentemente de onde o serviço seja injetado.

CounterTracker.cs:

public class CounterTracker
{
    [PersistentState]
    public int CurrentCount { get; set; }

    public void IncrementCount()
    {
        CurrentCount++;
    }
}

No arquivo Program, registe o serviço com um escopo definido e registe o serviço para 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 Program arquivo ainda não usar o Microsoft.AspNetCore.Components.Web namespace, adicione a seguinte using instrução à parte superior do arquivo:

using Microsoft.AspNetCore.Components.Web;

Onde os serviços estão registados no ficheiro Program:

builder.Services.AddScoped<CounterTracker>();

builder.Services.AddRazorComponents()
    .RegisterPersistentService<CounterTracker>(RenderMode.InteractiveAuto);

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 e a página for alcançada através de uma navegação interna aprimorada, a pré-renderização não ocorrerá. Portanto, deve executar uma recarga completa da página para que o componente possa visualizar a saída a seguir. Para obter mais informações, consulte a seção de roteamento interativo e pré-renderização.

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 e a página for alcançada através de uma navegação interna aprimorada, a pré-renderização não ocorrerá. Portanto, deve executar uma recarga completa da página para que o componente possa visualizar a saída a seguir. Para obter mais informações, consulte a seção de roteamento interativo e pré-renderização.

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 serializador personalizado é registrado no arquivo do Program aplicativo. No exemplo a seguir, o CustomUserSerializer é registrado para o TUser tipo:

builder.Services.AddSingleton<PersistentComponentStateSerializer<TUser>, 
    CustomUserSerializer>();

O tipo é automaticamente persistido e restaurado com o serializador personalizado:

[PersistentState] 
public User? CurrentUser { get; set; } = new();

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 obter mais informações, consulte Persist Component State Tag Helper in 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 ponto de extremidade dentro do aplicativo.

Blazor suporta a manipulação do estado persistente do componente durante a navegação avançada. O estado persistido durante a navegação aprimorada 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 somente leitura e não forem alterados com frequência, opte por permitir atualizações durante a navegação aprimorada definindo AllowUpdates = true o [PersistentState] atributo. Isso é útil para cenários como a exibição de dados armazenados em cache que são caros para buscar, mas não mudam com frequência. O exemplo a seguir demonstra o uso de para dados de AllowUpdates previsão do tempo:

[PersistentState(AllowUpdates = true)]
public WeatherForecast[]? Forecasts { get; set; }

protected override async Task OnInitializedAsync()
{
    Forecasts ??= await ForecastService.GetForecastAsync();
}

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

Chamada PersistentComponentState.RegisterOnRestoring para registrar um retorno de chamada para controlar imperativamente como o estado é restaurado, semelhante a como PersistentComponentState.RegisterOnPersisting fornece controle total de como o estado é persistente.

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 do estado de carregamento com PersistentComponentState para requisições internas de página, está abordado na navegação do ASP.NET CoreBlazor. Como alternativa, atualize o aplicativo para o .NET 10 ou posterior, onde Blazor oferece suporte à manipulação do estado persistente do componente durante a navegação aprimorada.