Dans une application Blazor côté serveur, je voudrais stocker un état qui est conservé entre la navigation entre les pages. Comment puis-je le faire?
L'état de session ASP.NET Core normal ne semble pas être disponible, car la note suivante dans Session et application dans ASP.NET Core s'applique probablement:
La session n'est pas prise en charge dans les applications SignalR car un SignalR Hub peut s'exécuter indépendamment d'un contexte HTTP. Par exemple, cela peut se produire lorsqu'une longue requête d'interrogation est maintenue ouverte par un concentrateur au-delà de la durée de vie du contexte HTTP de la requête.
Le problème GitHub Ajout de la prise en charge de SignalR pour la session mentionne que vous pouvez utiliser Context.Items . Mais je n'ai aucune idée de comment l'utiliser, c'est-à-dire que je ne sais pas à chaud pour accéder à l'instance HubConnectionContext
.
Quelles sont mes options pour l'état de session?
L'approche du pauvre envers l'État est suggérée par @JohnB: Utilisez un service délimité. Dans Blazor côté serveur, service de portée lié à la connexion SignalR. C'est la chose la plus proche d'une session que vous pouvez obtenir. C'est certainement privé pour un seul utilisateur. Mais il se perd aussi facilement. Le rechargement de la page ou la modification de l'URL dans les chargements de la liste d'adresses du navigateur démarre une nouvelle connexion SignalR, crée une nouvelle instance de service et perd ainsi l'état.
Créez d'abord le service d'État:
public class SessionState
{
public string SomeProperty { get; set; }
public int AnotherProperty { get; set; }
}
Configurez ensuite le service dans la classe Démarrage du projet App (pas le projet serveur):
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<SessionState>();
}
public void Configure(IBlazorApplicationBuilder app)
{
app.AddComponent<Main>("app");
}
}
Vous pouvez maintenant injecter l'état dans n'importe quelle page Blazor:
@inject SessionState state
<p>@state.SomeProperty</p>
<p>@state.AnotherProperty</p>
De meilleures solutions sont toujours super bienvenues.
Steve Sanderson explique en profondeur comment sauvegarder l'état.
Pour le blazor côté serveur, vous devrez utiliser n'importe quelle implémentation de stockage en JavaScript qui pourrait être des cookies, des paramètres de requête ou, par exemple, vous pouvez utiliser stockage local/de session .
Il existe actuellement des packages NuGet implémentant cela via IJSRuntime
comme BlazorStorage ou Microsoft.AspNetCore.ProtectedBrowserStorage
Maintenant, la partie délicate est que le blazor côté serveur pré-rend les pages, donc votre code de vue Razor sera exécuté et exécuté sur un serveur avant même qu'il ne soit affiché dans le navigateur du client. Cela provoque un problème où IJSRuntime
et donc localStorage
n'est pas disponible pour le moment. Vous devrez désactiver la pré-rendu ou attendre que la page générée par le serveur soit envoyée au navigateur du client et rétablir une connexion au serveur
Pendant le pré-rendu, il n'y a pas de connexion interactive avec le navigateur de l'utilisateur, et le navigateur n'a pas encore de page dans laquelle il peut exécuter JavaScript. Il n'est donc pas possible d'interagir avec localStorage ou sessionStorage à ce moment-là. Si vous essayez, vous obtiendrez une erreur similaire à des appels d'interopérabilité JavaScript ne peuvent pas être émis pour le moment. En effet, le composant est en cours de pré-rendu.
Pour désactiver le pré-rendu:
(...) ouvrez votre fichier
_Host.razor
et supprimez l'appel àHtml.RenderComponentAsync
. Ensuite, ouvrez votre fichierStartup.cs
Et remplacez l'appel àendpoints.MapBlazorHub()
parendpoints.MapBlazorHub<App>("app")
, oùApp
est le type de votre composant racine et "app "est un sélecteur CSS spécifiant où dans le document le composant racine doit être placé.
Lorsque vous souhaitez conserver la pré-rendu:
@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();
}
}
Maintenant, pour la réponse réelle où vous souhaitez conserver l'état entre les pages, vous devez utiliser un CascadingParameter
. Chris Sainty explique cela comme
Les valeurs et paramètres en cascade sont un moyen de transmettre une valeur d'un composant à tous ses descendants sans avoir à utiliser les paramètres de composant traditionnels.
Ce serait un paramètre qui serait une classe qui contient toutes vos données d'état et expose des méthodes qui peuvent se charger/enregistrer via un fournisseur de stockage de votre choix. Ceci est expliqué sur le blog de Chris Sainty , note de Steve Sanderson ou Microsoft docs
Mise à jour: Microsoft a publié de nouveaux documents expliquant la gestion de l'état de Blazor
Update2: Veuillez noter qu'actuellement BlazorStorage ne fonctionne pas correctement pour Blazor côté serveur avec l'aperçu SDK .NET le plus récent. Vous pouvez suivre ce problème où j'ai posté une solution temporaire
Voici un exemple de code complet de la façon dont vous pouvez utiliser Blazored/LocalStorage pour enregistrer les données de session. Utilisé par exemple pour stocker l'utilisateur connecté, etc. Fonctionnement confirmé à partir de la version 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();
}
}