Partilhar via


ASP.NET Core Blazor gestão de estado

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.

Advertência

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

Importante

Estas informações referem-se a um produto de pré-lançamento que pode ser substancialmente modificado antes de ser lançado comercialmente. A Microsoft não oferece garantias, expressas ou implícitas, em relação às informações fornecidas aqui.

Para a versão atual, consulte a versão .NET 9 deste artigo.

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

Observação

Os exemplos de código neste artigo adotam tipos de referência anuláveis (NRTs) ede análise estática de estado nulo do compilador .NET, que são suportados no ASP.NET Core no .NET 6 ou posterior. Ao direcionar-se para o ASP.NET Core 5.0 ou versões anteriores, remova a designação de tipo nulo (?) dos tipos nos exemplos do artigo.

Manter o estado do usuário

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

Exemplos de estado do usuário mantido em um circuito incluem:

  • A hierarquia de instâncias de componentes e sua saída de renderização mais recente na interface do usuário renderizada.
  • Os valores de campos e propriedades em instâncias de componentes.
  • Os dados mantidos em injeção de dependência (DI) instâncias de serviço que têm escopo para 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 de interoperabilidade JavaScript.

Se um usuário tiver uma perda temporária de conexão de rede, Blazor tentará reconectá-lo ao circuito original com seu 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 e 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áveis JavaScript definidos por meio de chamadas de interoperabilidade JavaScript são perdidos.

Quando um usuário não pode ser reconectado ao seu circuito original, o usuário recebe um novo circuito com um estado vazio. Isso equivale a fechar e reabrir um aplicativo da área de trabalho.

Persistir o estado através de circuitos

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

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

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

  • Formulários Web de várias etapas: Consome tempo para o utilizador reinserir dados para várias etapas concluídas de um formulário de várias etapas se o estado deles for perdido. Um utilizador perde o estado nesse cenário se se afastar do formulário e retornar mais tarde.
  • Carrinhos de compras: qualquer componente comercialmente importante de um aplicativo que represente receita potencial pode ser mantido. Um usuário que perde seu estado e, portanto, seu carrinho de compras, pode comprar menos produtos ou serviços quando retornar ao site mais tarde.

Um aplicativo só pode persistir estado do aplicativo. As interfaces do usuário não podem ser persistentes, como instâncias de componentes e suas árvores de renderização. Componentes e á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 em árvore, o aplicativo deve usar código personalizado para modelar o comportamento do estado da interface do usuário como estado do aplicativo serializável.

Onde persistir estado

Existem locais comuns para o estado persistente:

Armazenamento do lado do servidor

Para persistência permanente de dados 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 chave-valor
  • Base de dados relacional
  • Armazenamento de mesas

Depois que os dados são salvos, o estado do usuário é mantido e disponível em qualquer novo circuito.

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

URL

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

  • A ID de uma entidade visualizada.
  • O número de página atual numa grelha paginada.

O conteúdo da barra de endereço do navegador é mantido:

  • 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, consulte ASP.NET Core Blazor roteamento e navegação.

Armazenamento do navegador

Para dados transitórios que o utilizador está a criar ativamente, um local de armazenamento comumente usado são as coleções localStorage e sessionStorage no navegador.

  • localStorage está limitado ao escopo da instância 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 apagados. Os dados localStorage de um documento carregado numa sessão de "navegação privada" ou "anónima" são limpos quando o último separador "privado" é fechado.
  • sessionStorage tem como escopo a guia do navegador. Se o usuário recarregar a guia, o estado persistirá. 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.

Geralmente, sessionStorage é mais seguro de usar. sessionStorage evita o risco de um utilizador abrir várias abas e encontrar o seguinte:

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

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

Advertências para o uso do 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.
  • A página solicitada não existe no navegador durante a pré-renderização, portanto, o armazenamento local não está disponível durante a pré-renderização.
  • O armazenamento de alguns kilobytes de dados é razoável para persistir para aplicativos Blazor do lado do servidor. Além de alguns kilobytes, você deve considerar as implicações de desempenho porque os dados são carregados e salvos em toda a rede.
  • Os usuários podem visualizar ou adulterar os dados. A proteção de dados do ASP.NET Core pode mitigar o risco. Por exemplo, ASP.NET Core Protected Browser Storage usa ASP.NET Core Data Protection.

Os pacotes NuGet de terceiros fornecem APIs para trabalhar com localStorage e sessionStorage. Vale a pena considerar a escolha de um pacote que use de forma transparente ASP.NET Core Data Protection. A Proteção de Dados encripta os dados armazenados e reduz o risco potencial de adulteração dos 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 e também modificar os dados armazenados. Proteger dados triviais não é um problema. Por exemplo, ler ou modificar a cor armazenada de um elemento da interface do usuário não é um risco de segurança significativo para o usuário ou a organização. Evite permitir que os usuários inspecionem ou adulterem dados confidenciais.

Armazenamento Protegido do Navegador ASP.NET Core

ASP.NET Core Protected Browser Storage aproveita ASP.NET Core Data Protection para localStorage e sessionStorage.

Observação

O Armazenamento Protegido do Navegador depende do ASP.NET Core Data Protection e só é suportado para aplicações Blazor do lado do servidor.

Advertência

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 uso em aplicativos ASP.NET Core 3.1.

Configuração

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

    Observação

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

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

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

    services.AddProtectedBrowserStorage();
    

Salvar e carregar dados em um componente

Em qualquer componente que exija o carregamento ou o salvamento de dados no armazenamento do navegador, use a diretiva @inject para injetar uma instância de um dos seguintes:

  • 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 de _Imports.razor do aplicativo em vez de no componente. O uso do arquivo _Imports.razor torna o namespace disponível para segmentos maiores do aplicativo ou do aplicativo inteiro.

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 estado complexo. ProtectedSessionStore serializa e desserializa automaticamente dados JSON 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 simples, mas são protegidos usando ASP.NET Proteção de Dados Principais. Os dados criptografados podem ser inspecionados se sessionStorage['count'] for avaliado no console do desenvolvedor do navegador.

Para recuperar os dados currentCount se o usuário retornar ao componente Counter mais tarde, inclusive 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ãonull em OnParametersSetAsync, não OnInitializedAsync. OnInitializedAsync só é chamado uma vez quando o componente é instanciado pela primeira vez. OnInitializedAsync não será chamado novamente mais tarde se o usuário navegar para um URL diferente enquanto permanece na mesma página. Para obter mais informações, consulte ASP.NETde ciclo de vida do componente Core Razor .

Advertência

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

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

Manipular o estado de carregamento

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

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

private int? currentCount;

Em vez de exibir incondicionalmente o botão de contagem e 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 a pré-renderização

Durante a pré-renderização:

  • Não existe uma ligação interativa ao navegador do utilizador.
  • O navegador ainda não tem uma página na qual possa executar código JavaScript.

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

Uma maneira de resolver o erro é desativar a pré-renderização. Essa geralmente é a melhor escolha se o aplicativo fizer uso intenso do armazenamento baseado em navegador. A pré-renderização adiciona complexidade e não beneficia o aplicativo porque o aplicativo não pode pré-renderizar nenhum conteúdo útil até que localStorage ou sessionStorage estejam disponíveis.

Para desativar 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 baseados no modelo de projeto Blazor Web App, 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, desative a pré-renderização para o componente HeadOutlet:

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

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

Para desativar a pré-renderização, abra o arquivo e altere o atributo do auxiliar de tag de componente para :

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

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

A pré-renderização pode ser útil para outras páginas que não usam localStorage ou sessionStorage. Para manter a pré-renderização, adie a operação de carregamento até que o navegador esteja conectado ao circuito. Segue-se 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);
    }
}

Excluir a preservação do estado para um provedor comum

Se muitos componentes dependem de armazenamento baseado em navegador, a implementação do código do provedor de estado muitas vezes cria 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 se preocupar com o mecanismo de persistência de estado.

No exemplo a seguir de um componente CounterStateProvider, os dados do contador são persistidos em sessionStorage, e ele lida com a fase de carregamento ao não renderizar o conteúdo filho até que o carregamento de estado esteja concluído.

O componente CounterStateProvider gere a pré-renderização ao não carregar o estado até após a renderização do componente no método de ciclo de vida OnAfterRenderAsync, que não é executado durante a pré-renderização.

A abordagem nesta seção não é capaz de acionar a rerenderização de vários componentes inscritos na mesma página. Se um componente inscrito alterar o estado, ele será renderizado novamente e poderá exibir o estado atualizado, mas um componente diferente na mesma página exibindo esse estado exibirá dados obsoletos até sua próxima rerenderização. Portanto, a abordagem descrita nesta seção é mais adequada para usar o estado em um único componente na página.

CounterStateProvider.razor:

@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 OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isLoaded = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

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

    public async Task IncrementCount()
    {
        CurrentCount++;
        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 OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isLoaded = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

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

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

Observação

Para obter mais informações sobre RenderFragment, consulte componentes do ASP.NET Core Razor.

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

No componente App (Components/App.razor):

<Routes @rendermode="InteractiveServer" />

No componente Routes (Components/Routes.razor):

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

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

Observação

Com o lançamento do ASP.NET Core 5.0.1 e para quaisquer versões adicionais 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)
        {
            await CounterStateProvider.IncrementCount();
        }
    }
}

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

Em geral, recomenda-se o padrão do componente pai do provedor de estado :

  • 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 subconjuntos diferentes de objetos em locais diferentes, é melhor evitar a persistência do estado globalmente.

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

Exemplos de estado do usuário mantido na memória do navegador incluem:

  • A hierarquia de instâncias de componentes e o seu resultado de renderização mais recente na interface do utilizador renderizada.
  • Os valores de campos e propriedades em instâncias de componentes.
  • Dados mantidos em injeção de dependência (DI) instâncias de serviço.
  • Valores definidos através interoperabilidade JavaScript chamadas.

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

Protected Browser Storage (namespaceMicrosoft.AspNetCore.Components.Server.ProtectedBrowserStorage) depende do ASP.NET Core Data Protection e só é suportado para aplicativos Blazor do lado do servidor.

Manter o estado persistente nas sessões do navegador

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

Para preservar o estado nas sessões do navegador, o aplicativo deve manter os dados em algum outro local de armazenamento que não a memória do navegador. A persistência do estado não é automática. Você deve tomar medidas 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 gastaram esforço para criar. Nos exemplos a seguir, o estado persistente economiza tempo ou auxilia em atividades comerciais:

  • Formulários da Web de várias etapas: é demorado para um usuário reinserir os dados de diferentes etapas concluídas de um formulário se o estado destas for perdido. Um usuário perde o estado nesse cenário se navegar para fora do formulário e retornar mais tarde.
  • Carrinhos de compras: qualquer componente comercialmente importante de um aplicativo que represente receita potencial pode ser mantido. Um usuário que perde seu estado e, portanto, seu carrinho de compras, pode comprar menos produtos ou serviços quando retornar ao site mais tarde.

Um aplicativo só pode persistir estado do aplicativo. As interfaces do usuário não podem ser persistentes, como instâncias de componentes e suas árvores de renderização. Componentes e árvores de renderização geralmente não são serializáveis. Para persistir o estado da UI, como os nós expandidos de um controlo de visualização em árvore, a aplicação deve usar código personalizado para modelar o comportamento do estado da UI como estado da aplicação serializável.

Onde persistir o estado da aplicação

Existem locais comuns para o estado persistente:

Armazenamento do lado do servidor

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

  • Armazenamento de BLOB
  • Armazenamento de chave-valor
  • Base de dados relacional
  • Armazenamento de mesas

Depois que os dados são salvos, o estado do usuário é mantido e fica disponível em qualquer nova sessão do navegador.

Como Blazor WebAssembly aplicativos 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. Blazor WebAssembly aplicativos são protegidos da mesma maneira que os aplicativos de página única (SPAs). Normalmente, um aplicativo autentica um usuário por meio de OAuthOpenID Connect (OIDC) e, em seguida, interage com serviços de armazenamento e bancos de dados por meio de chamadas de API da Web para um aplicativo do lado do servidor. O aplicativo do lado do servidor medeia a transferência de dados entre o aplicativo Blazor WebAssembly e o serviço de armazenamento ou 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 obter mais informações, consulte os seguintes recursos:

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

URL

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

  • A ID de uma entidade visualizada.
  • O número de página atual numa grade paginada.

O conteúdo da barra de endereço do navegador é mantido se o usuário recarregar manualmente a página.

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

Armazenamento do navegador

Para dados transitórios que o utilizador está a criar ativamente, uma localização de armazenamento comumente usada é as coleções localStorage e sessionStorage do navegador.

  • localStorage tem como escopo a instância do navegador. Se o usuário recarregar a página ou fechar e reabrir o navegador, o estado persistirá. Se o utilizador abrir várias abas do navegador, o estado será partilhado entre as abas. Os dados persistem em localStorage até serem explicitamente eliminados. Os dados localStorage de um documento carregado numa sessão de "navegação privada" ou "anónima" são limpos quando o último separador "privado" é fechado.
  • sessionStorage tem como escopo a guia do navegador. Se o usuário recarregar a guia, o estado persistirá. Se o utilizador 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 escrevendo código personalizado ou usando um pacote de terceiros.

Geralmente, sessionStorage é mais seguro de usar. sessionStorage evita o risco de um utilizador abrir várias abas e encontrar o seguinte:

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

localStorage é a melhor escolha se o aplicativo precisar manter o estado mesmo após o navegador ser fechado e reaberto.

Advertência

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

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

Os componentes aninhados normalmente ligam dados usando de ligação encadeada, conforme descrito em ASP.NETde vinculação de dados do Core Blazor . Os componentes aninhados e não aninhados 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 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, mas o aninhamento não é necessário para que esta 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 em componentes. O exemplo não armazena dados sem um desenvolvimento adicional. Para armazenamento persistente de dados, o contêiner de estado deve adotar um mecanismo de armazenamento subjacente que sobreviva quando a memória do navegador é 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 (arquivoProgram):

builder.Services.AddSingleton<StateContainer>();

Aplicativos do lado do servidor (arquivoProgram, 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 à 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 associados ao OnChange são desinscritos nos métodos Dispose, que são chamados pelo framework quando os componentes são eliminados. Para obter mais informações, consulte ASP.NETde ciclo de vida do componente Core Razor .

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.

Solução de problemas

Ao usar um serviço de gestão de estado personalizado em que deseja suportar modificação de estado a partir de fora do contexto síncrono do Blazor(por exemplo, a partir de um temporizador ou de um serviço em segundo plano), todos os componentes consumidores devem envolver a chamada StateHasChanged em ComponentBase.InvokeAsync. Isso garante que a notificação de alteração seja tratada no contexto de sincronização do renderizador.

Quando o serviço de gerenciamento de estado não chama StateHasChanged no contexto de sincronização do Blazor, o seguinte erro é lançado:

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

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

Recursos adicionais