La gestione del webstorage sul nostro browser ci riserva diverse sorprese e grattacapi. Questo perché dobbiamo stare attenti a quello che salviamo e a come lo salviamo. Vediamo di capirci qualcosa.
WebStorage
Per chi come me arriva da un mondo desktop, andare a salvare delle informazioni, dei settings utente o qualunque altra cosa, significava necessariamente accedere al disco fisso del PC locale. Sul Web non è necessariamente così. La prima cosa che dobbiamo tenere a mente è che non sempre il client e il server sono sulla stessa macchina, per cui non possiamo accedere al disco dell’utente (a meno di particolari situazioni ma non è questo lo scopo) e le informazioni da salvare devono rimanere all’interno del browser. Fino all’avvento di HTML5, l’unico modo era quello di utilizzare i famosi cookies. Oggi abbiamo un’area dedicata ad ogni origine e protocollo e solo l’applicazione della stessa origine può accedervi.
Oggi ci vengono messi a disposizione due storage:
- Local Storage
- Session storage
Ogni storage contiene diverse funzioni:
setItem(key, value)
– archivia una coppia chave/valore.getItem(key)
– preleva un valore data una chiave.removeItem(key)
– cancella la coppia chiave/valoreclear()
– cancella tutto lo storage per quella applicazionekey(index)
– restituisce la chiave data una posizionelength
– restituisce il numero di elementi salvati
Local Storage
Il Local Storage è una zona permanente e accessibile tramite JavaScript utilizzando comandi tipo
window.localStorage.setItem("chiave","valore");
per scrivere un valore, oppure
window.localStorage.getItem("chiave");
per leggere un valore a partire da una chiave.
Chiudendo e riaprendo il browser, gli elementi salvati non vengono cancellati. Possiamo salvare le preferenze dell’utente (tema, lingua ecc…) ma non salvate MAI informazioni sensibili come password o dati bancari.
Session Storage
Il Session Storage è simile al local storage ma con l’unica differenza che è volatile. Alla chiusura dell’applicazione web (ovvero della sessione) i dati al suo interno vengono cancellati.
Aggiungiamo il webstorage a Blazor
Per poter aggiungere le funzionalità di webstorage a Blazor, possiamo semplicemente crearci una coppia di funzioni in questo modo
<script>
function BlazorSetLocalStorage(key, value) {
window.localStorage.setItem(key, value);
}
function BlazorGetLocalStorage(key) {
return window.localStorage.getItem(key);
}
</script>
per poi richiamarle tramite JSInterop in questo modo
jsRuntime.InvokeVoidAsync("BlazorSetLocalStorage", keyName, value);
Questo tipo di approccio va bene se dobbiamo richiamare da un solo punto la scrittura o la lettura dello storage. Diventa complicato quando abbiamo più punti dell’applicazione che devono accedere allo storage.
Creiamo un progetto Blazor Wasm…
La cosa migliore da fare se dobbiamo accedere allo storage da più parti, è quella di crearci una libreria che faccia tutto il lavoro e centralizzi gli accessi.
Creiamo, come siamo già abituati, un progetto Blazor Wasm e lo chiameremo BlazorWebStorage

poi scegliamo le opzioni

A questo punto il progetto di prova lo abbiamo creato. Questo ci servirà soltanto a provare il nostro local storage.
Creiamo ora una classe nel progetto Shared che ci servirà più tardi per maneggiare correttamente i dati del nostro web storage, la chiameremo ValueToStore e sarà fatta così:
public class ValueToStore
{
/// <summary>
/// Gets or sets my value.
/// </summary>
/// <value>
/// My value.
/// </value>
public string? MyValue { get; set; }
/// <summary>
/// Gets or sets my key.
/// </summary>
/// <value>
/// My key.
/// </value>
public string? MyKey { get; set; }
}
…e la libreria
Ora aggiungiamo un progetto alla nostra solution di tipo “Razor Class Library” e la chiameremo WebStorageManagement in questo modo:

Cancelliamo ora i files esistenti dato che non ci serviranno.

Ovvero:
- background.png
- exampleJsInterop.js
- Component1.razor
- ExampleJsInterop.cs
Ora siamo pronti per creare la nostra libreria.
Per comodità creiamo una cartella Models dove metteremo diversi files per la gestione del nostro webstorage e aggiungeremo una cartella “scripts” sotto “wwwroot” dove metteremo i nostri files JavaScript.

Il primo file che creeremo sarà localStorage.js
nel quale ci limiteremo a implementare solo i metodi setItem
, getItem
e clear
e sarà fatto in questo modo:
export function BlazorSetLocalStorage(key, value) {
window.localStorage.setItem(key, value);
}
export function BlazorGetLocalStorage(key) {
return window.localStorage.getItem(key);
}
export function BlazorClearLocalStorage() {
return window.localStorage.clear();
}
In modo del tutto simile sarà fatto il file sessionStorage.js
export function BlazorSetSessionStorage(key, value) {
window.sessionStorage.setItem(key, value);
}
export function BlazorGetSessionStorage(key) {
return window.sessionStorage.getItem(key);
}
export function BlazorClearSessionStorage() {
return window.sessionStorage.clear();
}
Nella cartella Models creeremo tutta la parte in C#. Metteremo due interfacce IStorageService.cs e IWebStorageService.cs che conterranno le definizioni per i metodi dei rispettivi storage (IStorageService) e il punto di accesso ai due storage (IWebStorageService). Queste interfacce ci porteranno tre classi:
- WebStorageService.cs (implementa IWebStorageService)
- LocalStorage.cs (implementa IStorageService)
- SessionStorage.cs (implementa IStorageService)
La classe WebStorageService conterrà due proprietà di tipo LocalStorage e SessionStorage che serviranno ad accedere ai rispettivi storage. La classe sarà la seguente:
public class WebStorageService : IWebStorageService
{
private readonly IJSRuntime? jsRuntime;
public LocalStorage LocalStorage { get; set; }
public SessionStorage SessionStorage { get; set; }
public WebStorageService(IJSRuntime jsRuntime)
{
this.jsRuntime = jsRuntime;
LocalStorage = new LocalStorage(jsRuntime);
SessionStorage = new SessionStorage(jsRuntime);
}
}
Mentre le classi LocalStorage e SessionStorage saranno le seguenti:
public class LocalStorage : IStorageService
{
private IJSObjectReference? module = null;
private readonly IJSRuntime? jsRuntime;
public LocalStorage(IJSRuntime jsRuntime)
{
this.jsRuntime = jsRuntime;
}
public async Task Init()
{
module = await jsRuntime.InvokeAsync<IJSObjectReference>
("import", "./_content/WebStorageManagement/scripts/localStorage.js");
}
public async Task Save(string keyName, string value)
{
if (module is not null)
await module!.InvokeVoidAsync("BlazorSetLocalStorage", keyName, value);
}
public async Task<string> Read(string keyName)
{
if (module is not null)
{
return await module!.InvokeAsync<string>("BlazorGetLocalStorage", keyName);
}
else
{
return string.Empty;
}
}
public async Task Clear()
{
if (module is not null)
await module!.InvokeVoidAsync("BlazorClearLocalStorage");
}
}
public class SessionStorage : IStorageService
{
private IJSObjectReference? module = null;
private readonly IJSRuntime? jsRuntime;
public SessionStorage(IJSRuntime jsRuntime)
{
this.jsRuntime = jsRuntime;
}
public async Task Init()
{
module = await jsRuntime.InvokeAsync<IJSObjectReference>
("import", "./_content/WebStorageManagement/scripts/sessionStorage.js");
}
public async Task Save(string keyName, string value)
{
if (module is not null)
await module!.InvokeVoidAsync("BlazorSetSessionStorage", keyName, value);
}
public async Task<string> Read(string keyName)
{
if (module is not null)
{
return await module!.InvokeAsync<string>("BlazorGetSessionStorage", keyName);
}
else
{
return string.Empty;
}
}
public async Task Clear()
{
if (module is not null)
await module!.InvokeVoidAsync("BlazorClearSessionStorage");
}
}
Un accento particolare va messo sui metodi Init delle due classi appena illustrate. Notate il caricamento dinamico dei due file JavaScript. Questo ci permette di avere i file all’interno della libreria ed evitare di “sporcare” l’applicazione principale. Inoltre questi files verranno effettivamente scaricati sul client solo quando effettivamente richiesti.
L’ultima cosa che ci rimane da fare è creare un file Program.cs nella nostra libreria che conterrà un extension method da richiamare dal file Program.cs del nostro Client dell’applicazione principale in Blazor.
public static class Program
{
public static IServiceCollection AddWebStorageManagement(this IServiceCollection services)
{
services.AddSingleton<IWebStorageService, WebStorageService>();
return services; ;
}
}
Quindi sarà richiamato come segue:
// Add localstorage
builder.Services.AddWebStorageManagement();
Il front-end di prova
Per provare la nostra libreria, dobbiamo per forza usare un front-end. Aggiungiamo un file Indez.razor.cs come code behind della nostra pagina Index.
public partial class Index
{
public ValueToStore? LocalValueToWrite { get; set; } = new ValueToStore();
public ValueToStore? SessionValueToWrite { get; set; } = new ValueToStore();
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await webStorage.LocalStorage.Init();
await webStorage.SessionStorage.Init();
}
}
#region SessionStorage
private void SubmitSession()
{
SaveSessionStorage();
}
private async void SaveSessionStorage()
{
if (SessionValueToWrite is not null && SessionValueToWrite.MyKey is not null && SessionValueToWrite.MyValue is not null)
await webStorage.SessionStorage.Save(SessionValueToWrite.MyKey, SessionValueToWrite.MyValue);
}
private async void ReadSessionStorage()
{
if (SessionValueToWrite is not null && SessionValueToWrite.MyKey is not null && SessionValueToWrite.MyValue is not null)
{
SessionValueToWrite.MyValue = await webStorage.SessionStorage.Read(SessionValueToWrite.MyKey);
StateHasChanged();
}
}
private async void ClearSessionStorage()
{
if (SessionValueToWrite is not null && SessionValueToWrite.MyKey is not null && SessionValueToWrite.MyValue is not null)
{
await webStorage.SessionStorage.Clear();
if (SessionValueToWrite is null)
{
SessionValueToWrite = new ValueToStore();
}
else
{
SessionValueToWrite.MyKey = String.Empty;
SessionValueToWrite.MyValue = String.Empty;
}
StateHasChanged();
}
}
#endregion
#region LocalStorage
private void Submit()
{
SaveLocalStorage();
}
private async void SaveLocalStorage()
{
if (LocalValueToWrite is not null && LocalValueToWrite.MyKey is not null && LocalValueToWrite.MyValue is not null)
{
await webStorage.LocalStorage.Save(LocalValueToWrite.MyKey, LocalValueToWrite.MyValue);
}
}
private async void ReadLocalStorage()
{
if (LocalValueToWrite is not null && LocalValueToWrite.MyKey is not null && LocalValueToWrite.MyValue is not null)
{
LocalValueToWrite.MyValue = await webStorage.LocalStorage.Read(LocalValueToWrite.MyKey);
StateHasChanged();
}
}
private async void ClearLocalStorage()
{
if (LocalValueToWrite is not null && LocalValueToWrite.MyKey is not null && LocalValueToWrite.MyValue is not null)
{
await webStorage.LocalStorage.Clear();
if (LocalValueToWrite is null)
{
LocalValueToWrite = new ValueToStore();
}
else
{
LocalValueToWrite.MyKey = String.Empty;
LocalValueToWrite.MyValue = String.Empty;
}
StateHasChanged();
}
}
#endregion
}
Mentre il front end sarà fatto così:
@page "/"
@using WebStorageManagement.Models
@using Microsoft.AspNetCore.Components
@inject IWebStorageService webStorage
<PageTitle>Index</PageTitle>
<h1>Local storage</h1>
<div style="padding: 15px; margin-bottom: 30px; border: 1px solid black;">
<EditForm Model=@LocalValueToWrite OnSubmit="@Submit">
<input type="text" placeholder="Enter the Key" @bind="LocalValueToWrite!.MyKey" />
<input type="text" placeholder="Enter the Value to store" @bind="LocalValueToWrite.MyValue" />
<div>
<input type="submit" value="Submit" class="btn btn-primary" />
<button class="btn btn-primary" @onclick="ReadLocalStorage">READ Local Storage value</button>
<button class="btn btn-secondary" @onclick="ClearLocalStorage">Clear Local Storage</button>
</div>
</EditForm>
</div>
<h1>Session storage</h1>
<div style="padding: 15px; margin-bottom: 30px; border: 1px solid black;">
<EditForm Model=@SessionValueToWrite OnSubmit="@SubmitSession">
<input type="text" placeholder="Enter the Key" @bind="SessionValueToWrite!.MyKey" />
<input type="text" placeholder="Enter the Value to store" @bind="SessionValueToWrite.MyValue" />
<div>
<input type="submit" value="Submit" class="btn btn-primary" />
<button class="btn btn-primary" @onclick="ReadSessionStorage">READ Session Storage Value</button>
<button class="btn btn-secondary" @onclick="ClearSessionStorage">Clear Session storage</button>
</div>
</EditForm>
</div>
Ed ecco qua la nostra applicazione funzionante e utilizza il webstorage correttamente come ci aspettavamo.

Come al solito potrete trovare tutto il codice a questo indirizzo.