Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
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:
-
localStoragetem o escopo na 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 emlocalStorageaté serem explicitamente limpos. Os dadoslocalStoragerelativos a um documento carregado em uma sessão de "navegação privada" ou sessão "anônima" são apagados quando a última guia "privada" é fechada. -
sessionStoragetem 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.
- A página solicitada não existe no navegador durante a pré-renderização, portanto, o armazenamento local fica indisponível durante a 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 vê-los e modificá-los usando as ferramentas de desenvolvedor do navegador. Proteger dados triviais não é um problema. 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 Protegido do Navegador no 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
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.
No arquivo
_Host.cshtml, adicione o seguinte script dentro da marca</body>de fechamento:<script src="_content/Microsoft.AspNetCore.ProtectedBrowserStorage/protectedBrowserStorage.js"></script>No
Startup.ConfigureServices, chameAddProtectedBrowserStoragepara adicionar serviçoslocalStorageesessionStorageà 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:
ProtectedLocalStorageProtectedSessionStorage
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 componentes do Razor 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 escrever um código que funciona com a pré-renderização, 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 é armazenada 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 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 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 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, desabilite a pré-renderização para o componente HeadOutlet:
<HeadOutlet @rendermode="new InteractiveServerRenderMode(prerender: false)" />
Para obter mais informações, consulte Os componentes do Prerender ASP.NET CoreRazor.
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 provedor 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 mantidos em sessionStorage, e ele lida com a fase de carregamento evitando renderizar o respectivo conteúdo filho até que o carregamento do estado seja concluído.
O componente CounterStateProvider lida com a pré-renderização evitando carregar o estado até que o componente seja renderizado em OnAfterRenderAsync lifecycle method, que não é executado durante a pré-renderização.
A abordagem nesta seção não é capaz de disparar a renderização de vários componentes inscritos na mesma página. Se um componente inscrito alterar o estado, ele fará a renderização novamente e poderá exibir o estado atualizado, mas um componente diferente na mesma página exibindo aquele estado exibirá dados obsoletos até sua próxima própria renderizaçã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, confira Componentes Razordo ASP.NET Core.
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 .NET 5.0.1 e para quaisquer versões adicionais de 5.x, o Router componente inclui o PreferExactMatches parâmetro definido como @true. Para obter mais informações, consulte Migrar do ASP.NET Core 3.1 para o .NET 5.
Componentes encapsulados recebem e podem modificar o estado persistente do contador. 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, o padrão de componente pai fornecido pelo 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 de forma global.