BlazorでWeb Storageを利用して一時的にデータを保存する方法

この記事では、BlazorでWeb Storageを利用する方法について解説したいと思います。
ユーザーがリロードすると画面上の情報が失われてしまいます。その際に、データを一時的に保存しておき、状態を維持する方法を見ていきたいと思います。

# Web Storageとは？
HTML5で導入されたブラウザーにKey-Value形式でテキストデータを保存できる仕組みです。ブラウザーにデータを保管する仕組みとしてはCookieがありますが、Cookieよりも保存容量が大きく、常にHttp Headerで送信されるわけではなく、必要なときにだけ取り出せます。
sessionStroageとlocalStorageの2種類があり、目的に応じて使い分けができます。

* sessionStorage
ブラウザーのタブ単位でデータを保存できます。タブを再読込してもデータは維持されますが、タブかブラウザーを閉じると、データは失われます。

* localStorage
ブラウザー単位でデータを保存でき、タブやブラウザー本体を閉じてもデータが維持されます。再度ブラウザーを起動するとデータを再読み込みできます。

# Blazor ServerでWeb Storageを利用する方法

まずは、Blazor ServerでWeb Storageを利用する方法をご紹介したいと思います。
Blazor ServerではMicrosoftが提供している、「ASP.NET Core で保護されたブラウザー ストレージ」を利用することで、簡単にデータを暗号化して保存できます。

## sessionStorageの場合

```CSharp:CounterSessionStorage.razor
@page "/countersession"
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

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

    /// <summary>
    /// コンポーネントのレンダリングが完了した後に呼び出されます
    /// </summary>
    /// <param name="firstRender"></param>
    /// <returns></returns>
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isConnected = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

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

    private async Task IncrementCount()
    {
        currentCount++;
        await ProtectedSessionStore.SetAsync("count", currentCount);
    }
}
```
Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorageを読み込むことにより、ProtectedSessionStorageを利用できます。
sessionStorage、localStorageは、プリレンダリング中に利用できないため、OnInitializedAsyncではなく、OnAfterRenderAsyncに画面読み込み時のデータ読み取り処理を記述します。
これで画面をリロードした際も、同じタブ内であればデータを保存し、再読み込みできます。

また、データが暗号化されていることも、開発者ツールから確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/158398/0e388b9b-6abb-ec0e-f8e8-9513619a1bf9.png)

## localStorageの場合

ProtectedSessionStorageをProtectedLocalStorageに変更するだけです。
localStorageの場合は、ブラウザーを終了してもデータが保存されます。再度ブラウザーを起動すれば保存した値を読み込むことが可能です。localStorageに保存したデータは、有効期限がないため、システムから削除するか、ユーザーが自分で削除しない限りデータが残り続けることに注意してください。

localStorageの場合も、同様にデータが暗号化されます。

# Blazor WebAssemblyでWeb Stroageを利用する方法

Blazor WebAssemblyの場合は、Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorageを利用できないため、サードパーティ製のライブラリを利用した例をご紹介します。

今回は、Blazoredというライブラリを使ってみます。（https://github.com/Blazored）
導入はNugetから、Blazored.SessionStorage、Blazored.LocalStorageを検索すると見つかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/158398/f7d7b4bf-fc41-494e-a877-1a0d4e80fcd7.png)

## sessionStorageの場合
Blazored.SessionStorageを読み込み、builderにサービスを登録します。これで各Razorコンポーネントから利用できます。

```CSharp:Program.cs
using Blazored.SessionStorage;

...

builder.Services.AddBlazoredSessionStorage();

await builder.Build().RunAsync();
```

```CSharp:Counter.razor
@page "/counter"
@inject Blazored.SessionStorage.ISessionStorageService sessionStorage

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    protected override async Task OnInitializedAsync()
    {
        currentCount = await sessionStorage.GetItemAsync<int>("count");
    }

    private void IncrementCount()
    {
        currentCount++;
        sessionStorage.SetItemAsync("count", currentCount);
    }
}
```

Blazor Serverの時と同様にデータの保存、読み込みができます。（Blazoredを利用した場合は、OnInitializedAsyncでも読み込みが可能なようです。）
ただし、そのままではデータの暗号化はされないので、ユーザーが自由にデータの中身を確認できる点については注意してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/158398/4ecd4cdc-d1bd-12bb-7a19-61994f708297.png)

## localStorageの場合

読み込むライブラリが違うだけで、ほとんど同じように実装できます。

```CSharp:Program.cs
using Blazored.LocalStorage;
using BlazorWasmBrowserStorage1;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddBlazoredLocalStorage();

await builder.Build().RunAsync();
```

```CSharp:Counter.razor
@page "/counter"
@inject Blazored.LocalStorage.ILocalStorageService localStorage

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    protected override async Task OnInitializedAsync()
    {
        currentCount = await localStorage.GetItemAsync<int>("count");
    }

    private void IncrementCount()
    {
        currentCount++;
        localStorage.SetItemAsync("count", currentCount);
    }
}

```

# まとめ

BlazorでWeb Storageを利用する方法をご紹介しました。
ページ遷移でデータを保持するだけであれば、サービスをDIコンテナにScopedで登録することでも対応可能ですが、ページリロード時にはデータが消失します。その場合でもデータを保持したい場合には、Web Storageを利用をご検討ください。
皆様のご参考になれば幸いです。

# 参考サイト
この記事は以下の情報を参考にして執筆しました。
- https://docs.microsoft.com/ja-jp/aspnet/core/blazor/state-management?view=aspnetcore-6.0&pivots=server#browser-storage-server