web-dev-qa-db-ja.com

サーバー側のブレイザーにセッションデータを保存する方法

サーバー側のBlazorアプリでは、ページナビゲーション間で保持される状態を保存したいと思います。どうすればいいですか?

通常のASP.NET Coreセッション状態は、 ASP.NET Coreのセッションとアプリ状態 の次の注意が適用される可能性が高いため、利用できないようです。

SignalR Hub はHTTPコンテキストとは無関係に実行される可能性があるため、セッションは SignalR アプリではサポートされていません。たとえば、長いポーリングリクエストが、リクエストのHTTPコンテキストの有効期間を超えてハブによって開かれている場合に発生する可能性があります。

GitHubの問題 セッションのSignalRへのサポートを追加 では、 Context.Items を使用できることに言及しています。しかし、私はそれを使用する方法がわかりません。つまり、HubConnectionContextインスタンスにアクセスする方法がわかりません。

セッション状態のオプションは何ですか?

9
Codo

貧乏人の状態へのアプローチは、@ JohnBによって示唆されています:scopedサービスを使用します。サーバー側のBlazorでは、SignalR接続に結び付けられたスコープサービス。これは、取得できるセッションに最も近いものです。それは確かに単一のユーザーにプライベートです。しかし、それは簡単に失われます。ページをリロードするか、ブラウザのアドレスリストのURLを変更すると、新しいSignalR接続が開始され、新しいサービスインスタンスが作成されるため、状態が失われます。

したがって、最初に状態サービスを作成します。

public class SessionState
{
    public string SomeProperty { get; set; }
    public int AnotherProperty { get; set; }
}

次に、StartupクラスのAppプロジェクト(サーバープロジェクトではない)でサービスを構成します。

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<SessionState>();
    }

    public void Configure(IBlazorApplicationBuilder app)
    {
        app.AddComponent<Main>("app");
    }
}

これで、Blazorページに状態を挿入できます。

@inject SessionState state

 <p>@state.SomeProperty</p>
 <p>@state.AnotherProperty</p>

より良いソリューションはまだ大歓迎です。

5
Codo

Steve Sandersonは状態を保存する方法を詳しく説明します

サーバー側のブレイザーの場合は、Cookie、クエリパラメーターなどのJavaScriptのストレージ実装を使用する必要があります。たとえば、 local/session storage を使用できます。

現在、 BlazorStorage または_Microsoft.AspNetCore.ProtectedBrowserStorage_のようなIJSRuntimeを介してそれを実装するNuGetパッケージがあります

トリッキーな部分は、サーバー側のブレイザーがページを事前にレンダリングしていることです。そのため、Razorビューコードは、クライアントのブラウザーに表示される前にサーバーで実行および実行されます。これにより、IJSRuntime、したがってlocalStorageが現在利用できないという問題が発生します。 事前レンダリングを無効にするか、サーバーが生成したページがクライアントのブラウザーに送信されてサーバーに接続が戻るまで待機する必要があります

事前レンダリング中、ユーザーのブラウザーへのインタラクティブな接続はなく、ブラウザーにはJavaScriptを実行できるページがまだありません。そのため、その時点でlocalStorageまたはsessionStorageと対話することはできません。試してみると、現時点ではJavaScript相互運用呼び出しを発行できないのと同様のエラーが発生します。これは、コンポーネントが事前レンダリングされているためです。

事前レンダリングを無効にするには:

(...)__Host.razor_ファイルを開き、_Html.RenderComponentAsync_への呼び出しを削除します。次に、_Startup.cs_ファイルを開き、endpoints.MapBlazorHub()への呼び出しをendpoints.MapBlazorHub<App>("app")に置き換えます。ここで、Appはルートコンポーネントのタイプと「app」ですドキュメント内のルートコンポーネントを配置する場所を指定するCSSセレクタです。

事前レンダリングを続けたい場合:

_@inject YourJSStorageProvider storageProvider

    bool isWaitingForConnection;

    protected override async Task OnInitAsync()
    {
        if (ComponentContext.IsConnected)
        {
            // Looks like we're not prerendering, so we can immediately load
            // the data from browser storage
            string mySessionValue = storageProvider.GetKey("x-my-session-key");
        }
        else
        {
            // We are prerendering, so have to defer the load operation until later
            isWaitingForConnection = true;
        }
    }

    protected override async Task OnAfterRenderAsync()
    {
        // By this stage we know the client has connected back to the server, and
        // browser services are available. So if we didn't load the data earlier,
        // we should do so now, then trigger a new render.
        if (isWaitingForConnection)
        {
            isWaitingForConnection = false;
            //load session data now
            string mySessionValue = storageProvider.GetKey("x-my-session-key");
            StateHasChanged();
        }
    }
_

ページ間で状態を保持したい実際の答えについては、CascadingParameterを使用する必要があります。クリス・セイティーはこれを次のように説明しています

値とパラメーターのカスケードは、従来のコンポーネントパラメーターを使用せずに、コンポーネントからそのすべての子孫に値を渡す方法です。

これは、すべての状態データを保持し、選択したストレージプロバイダーを介してロード/保存できるメソッドを公開するクラスになるパラメーターになります。これは Chris SaintyのブログSteve Sandersonのメモ または Microsoft docs で説明されています

更新: MicrosoftはBlazorの状態管理を説明する新しいドキュメントを公開しました

Update2:現在のBlazorStorageは、最新の.NET SDKプレビューを使用したサーバー側のBlazorで正常に動作していないことに注意してください。あなたは従うことができます この問題 私は一時的な回避策を投稿しました

2
Konrad Bartecki

Blazored/LocalStorage を使用してセッションデータを保存する方法の完全なコード例を次に示します。たとえば、ログインしたユーザーの保存などに使用されます。バージョン3.0.100-preview9-014004

@page "/login"
@inject Blazored.LocalStorage.ILocalStorageService localStorage

<hr class="mb-5" />
<div class="row mb-5">

    <div class="col-md-4">
        @if (UserName == null)
        {
            <div class="input-group">
                <input class="form-control" type="text" placeholder="Username" @bind="LoginName" />
                <div class="input-group-append">
                    <button class="btn btn-primary" @onclick="LoginUser">Login</button>
                </div>
            </div>
        }
        else
        {
            <div>
                <p>Logged in as: <strong>@UserName</strong></p>
                <button class="btn btn-primary" @onclick="Logout">Logout</button>
            </div>
        }
    </div>
</div>

@code {

    string UserName { get; set; }
    string UserSession { get; set; }
    string LoginName { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await GetLocalSession();

            localStorage.Changed += (sender, e) =>
            {
                Console.WriteLine($"Value for key {e.Key} changed from {e.OldValue} to {e.NewValue}");
            };

            StateHasChanged();
        }
    }

    async Task LoginUser()
    {
        await localStorage.SetItemAsync("UserName", LoginName);
        await localStorage.SetItemAsync("UserSession", "PIOQJWDPOIQJWD");
        await GetLocalSession();
    }

    async Task GetLocalSession()
    {
        UserName = await localStorage.GetItemAsync<string>("UserName");
        UserSession = await localStorage.GetItemAsync<string>("UserSession");
    }

    async Task Logout()
    {
        await localStorage.RemoveItemAsync("UserName");
        await localStorage.RemoveItemAsync("UserSession");
        await GetLocalSession();
    }
}
0
jsmars