サーバー側のBlazorアプリでは、ページナビゲーション間で保持される状態を保存したいと思います。どうすればいいですか?
通常のASP.NET Coreセッション状態は、 ASP.NET Coreのセッションとアプリ状態 の次の注意が適用される可能性が高いため、利用できないようです。
SignalR Hub はHTTPコンテキストとは無関係に実行される可能性があるため、セッションは SignalR アプリではサポートされていません。たとえば、長いポーリングリクエストが、リクエストのHTTPコンテキストの有効期間を超えてハブによって開かれている場合に発生する可能性があります。
GitHubの問題 セッションのSignalRへのサポートを追加 では、 Context.Items を使用できることに言及しています。しかし、私はそれを使用する方法がわかりません。つまり、HubConnectionContext
インスタンスにアクセスする方法がわかりません。
セッション状態のオプションは何ですか?
貧乏人の状態へのアプローチは、@ 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>
より良いソリューションはまだ大歓迎です。
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で正常に動作していないことに注意してください。あなたは従うことができます この問題 私は一時的な回避策を投稿しました
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();
}
}