Gerenciamento de estado Blazor do ASP.NET Core

Observação

Esta não é a versão mais recente deste artigo. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Importante

Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.

Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Este artigo descreve as abordagens comuns para manter os dados de um usuário (estado), enquanto ele usa um aplicativo e entre sessões do navegador.

Observação

Os exemplos de código neste artigo adotam NRTs (tipos de referência anuláveis) e análise estática de estado nulo do compilador do .NET, que têm suporte no ASP.NET Core no .NET 6 ou posterior. Ao direcionar para o ASP.NET Core 5.0 ou anterior, remova a designação de tipo nulo (?) dos tipos nos exemplos do artigo.

Manter o estado do usuário

O lado do servidor Blazor é uma estrutura do aplicativo com estado. Na maioria das vezes, o aplicativo mantém uma conexão com o servidor. O estado do usuário é retido na memória do servidor em um circuito.

Os exemplos de estado do usuário retido em um circuito incluem:

  • A hierarquia das instâncias de componente e a saída de renderização mais recente na interface do usuário renderizada.
  • Os valores de campos e as propriedades nas instâncias de componente.
  • Os dados retidos nas instâncias de serviço de DI (injeção de dependência) que têm como escopo o circuito.

O estado do usuário também pode ser encontrado em variáveis JavaScript no conjunto de memória do navegador por meio de chamadas de interoperabilidade do JavaScript .

Se um usuário tiver uma perda temporária de conexão de rede, Blazor tentará reconectar o usuário ao circuito original com o estado original. No entanto, reconectar um usuário ao circuito original na memória do servidor nem sempre é possível:

  • O servidor não pode reter um circuito desconectado para sempre. O servidor deve liberar um circuito desconectado após um tempo limite ou quando o servidor estiver sob pressão de memória.
  • Em ambientes de implantação com balanceamento de carga de vários servidores, os servidores individuais podem falhar ou ser removidos automaticamente, quando não forem mais necessários para lidar com o volume geral de solicitações. As solicitações de processamento do servidor original para um usuário podem ficar indisponíveis, quando o usuário tenta se reconectar.
  • O usuário pode fechar e reabrir o navegador ou recarregar a página, o que remove qualquer estado mantido na memória do navegador. Por exemplo, os valores de variável JavaScript definidos por meio de chamadas de interoperabilidade do JavaScript são perdidos.

Quando um usuário não pode ser reconectado ao circuito original, o usuário recebe um novo circuito com um estado vazio. Isso é equivalente a fechar e reabrir um aplicativo de desktop.

Persistir o estado entre circuitos

Em geral, mantenha o estado entre circuitos em que os usuários estão criando dados ativamente, não simplesmente lendo dados que já existem.

Para preservar o estado entre circuitos, o aplicativo deve persistir os dados em algum outro local de armazenamento que não seja a memória do servidor. A persistência de estado não é automática. Você deve executar etapas ao desenvolver o aplicativo para implementar a persistência de dados com estado.

A persistência de dados normalmente só é necessária para o estado de alto valor que os usuários se esforçaram para criar. Nos exemplos a seguir, persistir o estado economiza tempo ou ajuda em atividades comerciais:

  • Formulários da Web de várias etapas: demora para um usuário inserir dados novamente em várias etapas concluídas de um formulário da Web de várias etapas, se o estado for perdido. Um usuário perderá o estado nesse cenário, se sair do formulário e retornar mais tarde.
  • Carrinhos de compras: qualquer componente comercialmente importante de um aplicativo que representa uma possível receita pode ser mantido. Um usuário que perde o estado e, portanto, o carrinho de compras, pode comprar menos produtos ou serviços quando retornar ao site mais tarde.

Um aplicativo só pode persistir o estado do aplicativo. As interfaces do usuário não podem ser persistidas, como instâncias de componente e as árvores de renderização. Os componentes e as árvores de renderização geralmente não são serializáveis. Para persistir o estado da interface do usuário, como os nós expandidos de um controle de exibição de árvore, o aplicativo deve usar o código personalizado para modelar o comportamento do estado da interface do usuário como estado serializável do aplicativo.

Onde persistir o estado

Existem locais comuns para persistir o estado:

Armazenamento do lado do servidor

Para persistência de dados permanente que abrange vários usuários e dispositivos, o aplicativo pode usar o armazenamento do lado do servidor. As opções incluem:

  • Armazenamento de Blobs
  • Armazenamento de par chave-valor
  • Banco de dados relacional
  • Armazenamento de tabela

Depois que os dados são salvos, o estado do usuário é retido e disponibilizado em qualquer novo circuito.

Para obter mais informações sobre as opções de armazenamento de dados do Azure, confira o seguinte:

URL

Para dados transitórios que representam o estado de navegação, modele os dados como parte da URL. Os exemplos de estado do usuário modelados na URL incluem:

  • A ID de uma entidade exibida.
  • O número da página atual em uma grade paginada.

O conteúdo da barra de endereços do navegador é retido:

  • Se o usuário recarregar manualmente a página.
  • Se o servidor Web ficar indisponível e o usuário for forçado a recarregar a página para se conectar a um servidor diferente.

Para obter informações sobre como definir padrões de URL com a diretiva @page, confira Roteamento e navegação Blazor do ASP.NET Core.

Armazenamento do navegador

Para dados transitórios que o usuário está criando ativamente, um local de armazenamento comumente usado é as coleções localStorage e sessionStorage do navegador:

  • localStorage tem como escopo a janela do navegador. Se o usuário recarregar a página ou fechar e reabrir o navegador, o estado persistirá. Se o usuário abrir várias guias do navegador, o estado será compartilhado entre as guias. Os dados persistem em localStorage até serem explicitamente limpos.
  • sessionStorage tem como escopo a guia do navegador. Se o usuário recarregar a guia, o estado será mantido. Se o usuário fechar a guia ou o navegador, o estado será perdido. Se o usuário abrir várias guias do navegador, cada guia terá sua própria versão independente dos dados.

Em geral, sessionStorage é mais seguro de usar. sessionStorage evita o risco de um usuário abrir várias guias e encontrar o seguinte:

  • Bugs no armazenamento de estado entre guias.
  • Comportamento confuso quando uma guia substitui o estado de outras guias.

localStorage é a melhor opção se o aplicativo precisar manter o estado ao fechar e reabrir o navegador.

Advertências para usar o armazenamento do navegador:

  • Semelhante ao uso de um banco de dados do lado do servidor, o carregamento e o salvamento de dados são assíncronos.
  • Ao contrário de um banco de dados do lado do servidor, o armazenamento não fica disponível durante a pré-renderização, pois a página solicitada não existe no navegador durante o estágio de pré-renderização.
  • O armazenamento de alguns kilobytes de dados é um motivo razoável para persistir em aplicativos do lado do servidor Blazor. Além de alguns quilobytes, você deve considerar as implicações de desempenho, pois os dados são carregados e salvos em toda a rede.
  • Os usuários podem exibir ou adulterar os dados. A Proteção de Dados do ASP.NET Core pode mitigar o risco. Por exemplo, o Armazenamento de Navegador Protegido do ASP.NET Core usa a Proteção de Dados do ASP.NET Core.

Os pacotes NuGet de terceiros fornecem APIs para trabalhar com localStorage e sessionStorage. Vale a pena considerar a escolha de um pacote que usa de forma transparente a Proteção de Dados do ASP.NET Core. A Proteção de Dados criptografa os dados armazenados e reduz o possível risco de adulteração de dados armazenados. Se os dados serializados por JSON forem armazenados em texto sem formatação, os usuários poderão ver os dados usando as ferramentas de desenvolvedor do navegador, além de modificar os dados armazenados. Proteger dados nem sempre é um problema, pois a natureza dos dados pode ser trivial. Por exemplo, ler ou modificar a cor armazenada de um elemento de interface do usuário não é um risco de segurança significativo para o usuário ou para a organização. Evite permitir que os usuários inspecionem ou violem dados confidenciais.

Armazenamento de Navegador Protegido do ASP.NET Core

O Armazenamento de Navegador Protegido do ASP.NET Core utiliza a Proteção de Dados do ASP.NET Core para localStorage e sessionStorage.

Observação

O armazenamento protegido do navegador depende do ASP.NET Core Data Protection e só tem suporte para aplicativos Blazor do lado do servidor.

Aviso

Microsoft.AspNetCore.ProtectedBrowserStorage é um pacote experimental sem suporte que não se destina ao uso em produção.

O pacote só está disponível para ser utilizado em aplicativos ASP.NET Core 3.1.

Configuração

  1. Adicione uma referência de pacote para Microsoft.AspNetCore.ProtectedBrowserStorage.

    Observação

    Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de pacotes (documentação do NuGet). Confirme as versões corretas de pacote em NuGet.org.

  2. No arquivo _Host.cshtml, adicione o seguinte script dentro da marca </body> de fechamento:

    <script src="_content/Microsoft.AspNetCore.ProtectedBrowserStorage/protectedBrowserStorage.js"></script>
    
  3. No Startup.ConfigureServices, chame AddProtectedBrowserStorage para adicionar serviços localStorage e sessionStorage à coleção de serviços:

    services.AddProtectedBrowserStorage();
    

Salvar e carregar dados em um componente

Em qualquer componente que exija carregar ou salvar dados no armazenamento do navegador, use a diretiva @inject para injetar uma instância de uma das seguintes opções:

  • ProtectedLocalStorage
  • ProtectedSessionStorage

A escolha depende do local de armazenamento do navegador que você deseja usar. No exemplo a seguir, sessionStorage é usado:

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

A diretiva @using pode ser colocada no arquivo do aplicativo _Imports.razor, em vez de no componente. O uso do arquivo _Imports.razor disponibiliza o namespace para segmentos maiores do aplicativo ou de todo o aplicativo.

Para persistir o valor currentCount no componente Counter de um aplicativo com base no modelo de projeto Blazor, modifique o método IncrementCount para usar ProtectedSessionStore.SetAsync:

private async Task IncrementCount()
{
    currentCount++;
    await ProtectedSessionStore.SetAsync("count", currentCount);
}

Em aplicativos maiores e mais realistas, o armazenamento de campos individuais é um cenário improvável. Os aplicativos são mais propensos a armazenar objetos de modelo inteiros que incluem o estado complexo. ProtectedSessionStore serializa e desserializa dados JSON automaticamente para armazenar objetos de estado complexos.

No exemplo de código anterior, os dados currentCount são armazenados como sessionStorage['count'] no navegador do usuário. Os dados não são armazenados em texto sem formatação, mas sim protegidos usando a Proteção de Dados do ASP.NET Core. Os dados criptografados podem ser inspecionados, se sessionStorage['count'] for avaliado no console do desenvolvedor do navegador.

Para recuperar os dados currentCount, caso o usuário retorne ao componente Counter posteriormente, incluindo se o usuário estiver em um novo circuito, use ProtectedSessionStore.GetAsync:

protected override async Task OnInitializedAsync()
{
    var result = await ProtectedSessionStore.GetAsync<int>("count");
    currentCount = result.Success ? result.Value : 0;
}
protected override async Task OnInitializedAsync()
{
    currentCount = await ProtectedSessionStore.GetAsync<int>("count");
}

Se os parâmetros do componente incluírem o estado de navegação, chame ProtectedSessionStore.GetAsync e atribua um resultado não null em OnParametersSetAsync, não em OnInitializedAsync. OnInitializedAsync é chamado apenas uma vez, quando o componente é instanciado pela primeira vez. OnInitializedAsync não será chamado novamente mais tarde, se o usuário navegar para uma URL diferente enquanto permanecer na mesma página. Para saber mais, consulte Ciclo de vida de renderização de Razor no ASP.NET Core.

Aviso

Os exemplos nesta seção só funcionarão se o servidor não tiver a pré-renderização habilitada. Com a pré-renderização habilitada, um erro é gerado explicando que as chamadas de interoperabilidade do JavaScript não podem ser emitidas, pois o componente está sendo pré-renderizado.

Desabilite a pré-renderização ou adicione mais um código para trabalhar com a pré-renderização. Para saber mais sobre como gravar um código que funciona com a pré-renderização, consulte confira a seção Manipular pré-renderização.

Manipular o estado de carregamento

Como o armazenamento do navegador é acessado de forma assíncrona em uma conexão de rede, há sempre um período antes que os dados sejam carregados e disponibilizados para um componente. Para obter os melhores resultados, renderize uma mensagem enquanto o carregamento estiver em andamento em vez de exibir dados em branco ou padrão.

Uma abordagem é acompanhar se os dados são null, o que significa que os dados ainda estão sendo carregados. No componente Counter padrão, a contagem é retida em um int. Torne currentCount anulável adicionando um ponto de interrogação (?) ao tipo (int):

private int? currentCount;

Em vez de exibir incondicionalmente a contagem e o botão Increment, exiba esses elementos somente se os dados forem carregados verificando HasValue:

@if (currentCount.HasValue)
{
    <p>Current count: <strong>@currentCount</strong></p>
    <button @onclick="IncrementCount">Increment</button>
}
else
{
    <p>Loading...</p>
}

Manipular pré-renderização

Durante a pré-renderização:

  • Uma conexão interativa com o navegador do usuário não existe.
  • O navegador ainda não tem uma página na qual possa executar o código JavaScript.

localStorage ou sessionStorage não fica disponível durante a pré-renderização. Se o componente tentar interagir com o armazenamento, um erro será gerado explicando que as chamadas de interoperabilidade do JavaScript não podem ser emitidas, pois o componente está sendo pré-renderizado.

Uma maneira de resolver o erro é desabilitar a pré-renderização. Essa geralmente é a melhor opção, se o aplicativo faz uso intensivo do armazenamento baseado em navegador. A pré-renderização adiciona complexidade e não favorece o aplicativo, pois o aplicativo não pode pré-renderizar o conteúdo útil até que localStorage ou sessionStorage esteja disponível.

Para desabilitar a pré-renderização, indique o modo de renderização com o parâmetro prerender definido como false no componente de nível mais alto na hierarquia de componentes do aplicativo que não seja um componente raiz.

Observação

Não há suporte para tornar um componente raiz interativo, como o componente App. Portanto, a pré-renderização não pode ser desabilitada diretamente pelo componente App.

Para aplicativos com base no modelo de projeto do aplicativo Web Blazor, a pré-renderização normalmente é desabilitada quando o componente Routes é usado no componente App (Components/App.razor):

<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)" />

Além disso, desabilite a pré-renderização para o componente HeadOutlet:

<HeadOutlet @rendermode="new InteractiveServerRenderMode(prerender: false)" />

Para obter mais informações, consulte ASP.NET Core Blazor modos de renderização.

Para desabilitar a pré-renderização, abra o arquivo _Host.cshtml e altere o atributo render-mode do Auxiliar de Marca de Componente para Server:

<component type="typeof(App)" render-mode="Server" />

Quando a pré-renderização está desabilitada, a pré-renderização do<head> conteúdo é desabilitada.

A pré-renderização pode ser útil para outras páginas que não usam localStorage ou sessionStorage. Para reter a pré-renderização, adie a operação de carregamento até que o navegador esteja conectado ao circuito. Veja a seguir um exemplo para armazenar um valor de contador:

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedLocalStorage ProtectedLocalStore

@if (isConnected)
{
    <p>Current count: <strong>@currentCount</strong></p>
    <button @onclick="IncrementCount">Increment</button>
}
else
{
    <p>Loading...</p>
}

@code {
    private int currentCount;
    private bool isConnected;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isConnected = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

    private async Task LoadStateAsync()
    {
        var result = await ProtectedLocalStore.GetAsync<int>("count");
        currentCount = result.Success ? result.Value : 0;
    }

    private async Task IncrementCount()
    {
        currentCount++;
        await ProtectedLocalStore.SetAsync("count", currentCount);
    }
}
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@inject ProtectedLocalStorage ProtectedLocalStore

@if (isConnected)
{
    <p>Current count: <strong>@currentCount</strong></p>
    <button @onclick="IncrementCount">Increment</button>
}
else
{
    <p>Loading...</p>
}

@code {
    private int currentCount = 0;
    private bool isConnected = false;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isConnected = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

    private async Task LoadStateAsync()
    {
        currentCount = await ProtectedLocalStore.GetAsync<int>("count");
    }

    private async Task IncrementCount()
    {
        currentCount++;
        await ProtectedLocalStore.SetAsync("count", currentCount);
    }
}

Fatorar a preservação do estado para um local comum

Se muitos componentes dependerem do armazenamento com base no navegador, implementar o código do provedor de estado muitas vezes criará duplicação de código. Uma opção para evitar a duplicação de código é criar um componente pai do provedor de estado que encapsula a lógica do provedor de estado. Os componentes filho podem trabalhar com dados persistentes, sem levar em conta o mecanismo de persistência de estado.

No exemplo a seguir de um componente CounterStateProvider, os dados do contador são persistidos em sessionStorage:

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

@if (isLoaded)
{
    <CascadingValue Value="this">
        @ChildContent
    </CascadingValue>
}
else
{
    <p>Loading...</p>
}

@code {
    private bool isLoaded;

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    public int CurrentCount { get; set; }

    protected override async Task OnInitializedAsync()
    {
        var result = await ProtectedSessionStore.GetAsync<int>("count");
        CurrentCount = result.Success ? result.Value : 0;
        isLoaded = true;
    }

    public async Task SaveChangesAsync()
    {
        await ProtectedSessionStore.SetAsync("count", CurrentCount);
    }
}
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

@if (isLoaded)
{
    <CascadingValue Value="this">
        @ChildContent
    </CascadingValue>
}
else
{
    <p>Loading...</p>
}

@code {
    private bool isLoaded;

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    public int CurrentCount { get; set; }

    protected override async Task OnInitializedAsync()
    {
        CurrentCount = await ProtectedSessionStore.GetAsync<int>("count");
        isLoaded = true;
    }

    public async Task SaveChangesAsync()
    {
        await ProtectedSessionStore.SetAsync("count", CurrentCount);
    }
}

Observação

Para obter mais informações sobre RenderFragment, confira Componentes Razordo ASP.NET Core.

O componente CounterStateProvider manipula a fase de carregamento não renderizando o conteúdo filho até que o carregamento de estado seja concluído.

Para tornar o estado acessível a todos os componentes em um aplicativo, encapsule o componente CounterStateProvider em torno do Router (<Router>...</Router>) no componente Routes com renderização interativa global no lado do servidor (SSR interativa).

No componente App (Components/App.razor):

<Routes @rendermode="InteractiveServer" />

No componente Routes (Components/Routes.razor):

Para usar o componente CounterStateProvider, encapsule uma instância do componente ao redor de qualquer outro componente que exija acesso ao estado do contador. Para tornar o estado acessível a todos os componentes em um aplicativo, encapsule o componente CounterStateProvider ao redor de Router no componente App (App.razor):

<CounterStateProvider>
    <Router ...>
        ...
    </Router>
</CounterStateProvider>

Observação

Com a versão do ASP.NET Core 5.0.1 e para qualquer lançamento adicional do 5.x, o componente Router inclui o parâmetro PreferExactMatches definido como @true. Para obter mais informações, consulte Migrar do ASP.NET Core 3.1 para o 5.0.

Os componentes encapsulados recebem e podem modificar o estado do contador persistente. O seguinte componente Counter implementa o padrão:

@page "/counter"

<p>Current count: <strong>@CounterStateProvider?.CurrentCount</strong></p>
<button @onclick="IncrementCount">Increment</button>

@code {
    [CascadingParameter]
    private CounterStateProvider? CounterStateProvider { get; set; }

    private async Task IncrementCount()
    {
        if (CounterStateProvider is not null)
        {
            CounterStateProvider.CurrentCount++;
            await CounterStateProvider.SaveChangesAsync();
        }
    }
}

O componente anterior não é necessário para interagir com ProtectedBrowserStorage, nem lida com uma fase de "carregamento".

Para lidar com a pré-renderização, conforme descrito anteriormente, CounterStateProvider pode ser alterado para que todos os componentes que consomem os dados do contador funcionem automaticamente com a pré-renderização. Para obter mais informações, confira a seção Manipular pré-renderização.

Em geral, o padrão de componente pai do provedor de estado é recomendado:

  • Para consumir o estado em muitos componentes.
  • Se houver apenas um objeto de estado de nível superior para persistir.

Para persistir muitos objetos de estado diferentes e consumir diferentes subconjuntos de objetos em locais diferentes, é melhor evitar persistir o estado globalmente.

O estado do usuário criado em um aplicativo Blazor WebAssembly é retido na memória do navegador.

Os exemplos de estado do usuário retido na memória do navegador incluem:

  • A hierarquia das instâncias de componente e a saída de renderização mais recente na interface do usuário renderizada.
  • Os valores de campos e as propriedades nas instâncias de componente.
  • Os dados retidos nas instâncias de serviço de DI (injeção de dependência).
  • Valores definidos por meio de chamadas de interoperabilidade do JavaScript.

Quando um usuário fecha e reabre o navegador ou recarrega a página, o estado do usuário mantido na memória do navegador é perdido.

Observação

O Armazenamento Protegido do Navegador (Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage namespace) depende da Proteção de Dados do ASP.NET Core e é suportado apenas para aplicativos Blazor do lado do servidor.

Persistir o estado entre sessões do navegador

Em geral, mantenha o estado entre sessões do navegador em que os usuários estão criando dados ativamente, não simplesmente lendo dados que já existem.

Para preservar o estado entre sessões do navegador, o aplicativo deve persistir os dados em algum outro local de armazenamento que não seja a memória do navegador. A persistência de estado não é automática. Você deve executar etapas ao desenvolver o aplicativo para implementar a persistência de dados com estado.

A persistência de dados normalmente só é necessária para o estado de alto valor que os usuários se esforçaram para criar. Nos exemplos a seguir, persistir o estado economiza tempo ou ajuda em atividades comerciais:

  • Formulários da Web de várias etapas: demora para um usuário inserir dados novamente em várias etapas concluídas de um formulário da Web de várias etapas, se o estado for perdido. Um usuário perderá o estado nesse cenário, se sair do formulário e retornar mais tarde.
  • Carrinhos de compras: qualquer componente comercialmente importante de um aplicativo que representa uma possível receita pode ser mantido. Um usuário que perde o estado e, portanto, o carrinho de compras, pode comprar menos produtos ou serviços quando retornar ao site mais tarde.

Um aplicativo só pode persistir o estado do aplicativo. As interfaces do usuário não podem ser persistidas, como instâncias de componente e as árvores de renderização. Os componentes e as árvores de renderização geralmente não são serializáveis. Para persistir o estado da interface do usuário, como os nós expandidos de um controle de exibição de árvore, o aplicativo deve usar o código personalizado para modelar o comportamento do estado da interface do usuário como estado serializável do aplicativo.

Onde persistir o estado

Existem locais comuns para persistir o estado:

Armazenamento do lado do servidor

Para persistência de dados permanente que abrange vários usuários e dispositivos, o aplicativo pode usar o armazenamento do lado do servidor independente, acessado por meio de uma API Web. As opções incluem:

  • Armazenamento de Blobs
  • Armazenamento de par chave-valor
  • Banco de dados relacional
  • Armazenamento de tabela

Depois que os dados são salvos, o estado do usuário é retido e disponibilizado em qualquer nova sessão do navegador.

Como os aplicativos Blazor WebAssembly são executados inteiramente no navegador do usuário, eles exigem medidas adicionais para acessar sistemas externos seguros, como serviços de armazenamento e bancos de dados. Aplicativos Blazor WebAssembly são protegidos da mesma maneira que SPAs (aplicativos de página única). Normalmente, um aplicativo autentica um usuário por meio do OIDC (OAuth/OpenID Connect) e interage com serviços de armazenamento e bancos de dados por meio de chamadas à API Web para um aplicativo do lado do servidor. O aplicativo do lado do servidor media a transferência de dados entre o aplicativo Blazor WebAssembly e o serviço de armazenamento ou o banco de dados. O aplicativo Blazor WebAssembly mantém uma conexão efêmera com o aplicativo do lado do servidor, enquanto o aplicativo do lado do servidor tem uma conexão persistente com o armazenamento.

Para saber mais, consulte os recursos a seguir:

Para obter mais informações sobre as opções de armazenamento de dados do Azure, confira o seguinte:

URL

Para dados transitórios que representam o estado de navegação, modele os dados como parte da URL. Os exemplos de estado do usuário modelados na URL incluem:

  • A ID de uma entidade exibida.
  • O número da página atual em uma grade paginada.

O conteúdo da barra de endereços do navegador será retido, se o usuário recarregar manualmente a página.

Para obter informações sobre como definir padrões de URL com a diretiva @page, confira Roteamento e navegação Blazor do ASP.NET Core.

Armazenamento do navegador

Para dados transitórios que o usuário está criando ativamente, um local de armazenamento comumente usado é as coleções localStorage e sessionStorage do navegador:

  • localStorage tem como escopo a janela do navegador. Se o usuário recarregar a página ou fechar e reabrir o navegador, o estado persistirá. Se o usuário abrir várias guias do navegador, o estado será compartilhado entre as guias. Os dados persistem em localStorage até serem explicitamente limpos.
  • sessionStorage tem como escopo a guia do navegador. Se o usuário recarregar a guia, o estado será mantido. Se o usuário fechar a guia ou o navegador, o estado será perdido. Se o usuário abrir várias guias do navegador, cada guia terá sua própria versão independente dos dados.

Observação

localStorage e sessionStorage podem ser usados em aplicativos Blazor WebAssembly, mas apenas gravando o código personalizado ou usando um pacote de terceiros.

Em geral, sessionStorage é mais seguro de usar. sessionStorage evita o risco de um usuário abrir várias guias e encontrar o seguinte:

  • Bugs no armazenamento de estado entre guias.
  • Comportamento confuso quando uma guia substitui o estado de outras guias.

localStorage é a melhor opção se o aplicativo precisar manter o estado ao fechar e reabrir o navegador.

Aviso

Os usuários podem exibir ou adulterar os dados armazenados em localStorage e sessionStorage.

Serviço de contêiner de estado na memória

Os componentes aninhados normalmente associam dados usando a associação encadeada, conforme descrito em Associação de dados Blazor do ASP.NET Core. Os componentes aninhados e não conectados podem compartilhar o acesso aos dados usando um contêiner de estado na memória registrado. Uma classe de contêiner de estado personalizado pode usar um Action atribuível para notificar componentes em diferentes partes do aplicativo sobre as alterações de estado. No exemplo a seguir:

  • Um par de componentes usa um contêiner de estado para rastrear uma propriedade.
  • Um componente no exemplo a seguir está aninhado no outro componente, mas o aninhamento não é necessário para que essa abordagem funcione.

Importante

O exemplo nesta seção demonstra como criar um serviço de contêiner de estado na memória, registrar o serviço e usar o serviço nos componentes. O exemplo não persiste dados sem desenvolvimento adicional. Para o armazenamento persistente de dados, o contêiner de estado deve adotar um mecanismo de armazenamento subjacente que sobreviva, quando a memória do navegador for limpa. Isso pode ser feito com localStorage/sessionStorage ou alguma outra tecnologia.

StateContainer.cs:

public class StateContainer
{
    private string? savedString;

    public string Property
    {
        get => savedString ?? string.Empty;
        set
        {
            savedString = value;
            NotifyStateChanged();
        }
    }

    public event Action? OnChange;

    private void NotifyStateChanged() => OnChange?.Invoke();
}

Aplicativos do lado do cliente (arquivo Program):

builder.Services.AddSingleton<StateContainer>();

Aplicativos do lado do servidor (arquivo Program, ASP.NET Core no .NET 6 ou posterior):

builder.Services.AddScoped<StateContainer>();

Aplicativos do lado do servidor (Startup.ConfigureServices de Startup.cs, ASP.NET Core anteriores à versão 6.0):

services.AddScoped<StateContainer>();

Shared/Nested.razor:

@implements IDisposable
@inject StateContainer StateContainer

<h2>Nested component</h2>

<p>Nested component Property: <b>@StateContainer.Property</b></p>

<p>
    <button @onclick="ChangePropertyValue">
        Change the Property from the Nested component
    </button>
</p>

@code {
    protected override void OnInitialized()
    {
        StateContainer.OnChange += StateHasChanged;
    }

    private void ChangePropertyValue()
    {
        StateContainer.Property = 
            $"New value set in the Nested component: {DateTime.Now}";
    }

    public void Dispose()
    {
        StateContainer.OnChange -= StateHasChanged;
    }
}

StateContainerExample.razor:

@page "/state-container-example"
@implements IDisposable
@inject StateContainer StateContainer

<h1>State Container Example component</h1>

<p>State Container component Property: <b>@StateContainer.Property</b></p>

<p>
    <button @onclick="ChangePropertyValue">
        Change the Property from the State Container Example component
    </button>
</p>

<Nested />

@code {
    protected override void OnInitialized()
    {
        StateContainer.OnChange += StateHasChanged;
    }

    private void ChangePropertyValue()
    {
        StateContainer.Property = "New value set in the State " +
            $"Container Example component: {DateTime.Now}";
    }

    public void Dispose()
    {
        StateContainer.OnChange -= StateHasChanged;
    }
}

Os componentes anteriores implementam IDisposable, e os delegados OnChange não são assinados nos métodos Dispose, que são chamados pela estrutura quando os componentes são descartados. Para saber mais, consulte Ciclo de vida de renderização de Razor no ASP.NET Core.

Abordagens adicionais

Ao implementar o armazenamento de estado personalizado, uma abordagem útil é adotar valores e parâmetros em cascata:

  • Para consumir o estado em muitos componentes.
  • Se houver apenas um objeto de estado de nível superior para persistir.

Solucionar problemas

Em um serviço personalizado de gerenciamento de estado, um retorno de chamada invocado fora do contexto de sincronização do Blazor deve encapsular a logica do retorno de chamada em ComponentBase.InvokeAsync para movê-lo para o contexto de sincronização do renderizador.

Quando o serviço de gerenciamento de estado não chama StateHasChanged no contexto de sincronização de Blazor, o erro a seguir é gerado:

System.InvalidOperationException: 'O thread atual não está associado ao Dispatcher. Use InvokeAsync() para alternar a execução para o Dispatcher ao disparar a renderização ou o estado do componente.'

Para obter mais informações e um exemplo de como solucionar esse erro, consulte ASP.NET Core Razor renderização de componentes.

Recursos adicionais