Gestion de l’état d’ASP.NET Core Blazor

Remarque

Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 8 de cet article.

Important

Ces informations portent sur la préversion du produit, qui est susceptible d’être en grande partie modifié avant sa commercialisation. Microsoft n’offre aucune garantie, expresse ou implicite, concernant les informations fournies ici.

Pour la version actuelle, consultez la version .NET 8 de cet article.

Cet article décrit les approches courantes pour gérer les données (état) d’un utilisateur pendant qu’il utilise une application et entre les sessions de navigateur.

Remarque

Les exemples de code de cet article utilisent les types de référence null (NRT, nullable reference types) et l'analyse statique de l'état null du compilateur .NET, qui sont pris en charge dans ASP.NET Core 6 et ses versions ultérieures. Lorsque vous ciblez ASP.NET Core 5.0 ou version antérieure, supprimez la désignation de type Null (?) des types dans les exemples de l’article.

Gérer l’état utilisateur

Blazor côté serveur est un framework d’application avec état. La plupart du temps, l’application maintient une connexion au serveur. L’état de l’utilisateur est conservé dans la mémoire du serveur dans un circuit.

Voici quelques exemples d’état utilisateur conservé dans un circuit :

  • Hiérarchie des instances de composant et leur sortie de rendu la plus récente dans l’interface utilisateur rendue.
  • Valeurs des champs et des propriétés dans les instances de composant.
  • Données conservées dans des instances de service d’injection de dépendances (DI) qui sont limitées au circuit.

L’état utilisateur peut également se trouver dans des variables JavaScript dans le jeu de mémoire du navigateur via des appels d’interopérabilité JavaScript.

Si un utilisateur subit une perte temporaire de connexion réseau, Blazor tente de reconnecter l’utilisateur à son circuit d’origine avec son état d’origine. Toutefois, il n’est pas toujours possible de reconnecter un utilisateur à son circuit d’origine dans la mémoire du serveur :

  • Le serveur ne peut pas conserver un circuit déconnecté indéfiniment. Le serveur doit libérer un circuit déconnecté après un délai d’expiration ou lorsque le serveur a besoin de mémoire.
  • Dans les environnements de déploiement multiserveurs et à charge équilibrée, des serveurs individuels peuvent échouer ou être automatiquement supprimés lorsqu’ils ne sont plus nécessaires pour gérer le volume global de requêtes. Le serveur d’origine qui traite les requêtes d’un utilisateur peut devenir indisponible lorsque l’utilisateur tente de se reconnecter.
  • L’utilisateur peut fermer et rouvrir son navigateur ou recharger la page, ce qui supprime tout état conservé dans la mémoire du navigateur. Par exemple, les valeurs des variables JavaScript définies par le biais d’appels d’interopérabilité JavaScript sont perdues.

Lorsqu’un utilisateur ne peut pas être reconnecté à son circuit d’origine, il reçoit un nouveau circuit avec un état vide. Cela revient à fermer et à rouvrir une application de bureau.

Conserver l’état sur les circuits

En règle générale, conservez l’état sur les circuits où les utilisateurs créent activement des données, et pas simplement en lisant des données qui existent déjà.

Pour conserver l’état sur les circuits, l’application doit conserver les données dans un autre emplacement de stockage que la mémoire du serveur. La persistance d’état n’est pas automatique. Vous devez prendre des mesures lors du développement de l’application pour implémenter la persistance des données avec état.

La persistance des données n’est généralement requise que pour l’état à valeur élevée que les utilisateurs ont consacré des efforts à créer. Dans les exemples suivants, l’état persistant fait gagner du temps ou facilite les activités commerciales :

  • Formulaires web à plusieurs étapes : il est fastidieux pour un utilisateur de saisir à nouveau des données pour plusieurs étapes terminées d’un formulaire web à plusieurs étapes si leur état est perdu. Un utilisateur perd l’état dans ce scénario s’il quitte le formulaire et revient ultérieurement.
  • Paniers d’achat : tout composant important sur le plan commercial d’une application qui représente le chiffre d’affaires potentiel peut être conservé. Un utilisateur qui perd son état, et donc son panier d’achat, est susceptible d’acheter moins de produits ou de services lorsqu’il revient sur le site plus tard.

Une application peut uniquement conserver l’état de l’application. Les interfaces utilisateur ne peuvent pas être conservées, comme les instances de composant et leurs arborescences de rendu. Les composants et les arborescences de rendu ne sont généralement pas sérialisables. Pour conserver l’état de l’interface utilisateur, comme les nœuds développés d’un contrôle d’arborescence, l’application doit utiliser du code personnalisé pour modéliser le comportement de l’état de l’interface utilisateur comme état de l’application sérialisable.

Où conserver l’état

Il existe des emplacements courants pour conserver l’état :

Stockage côté serveur

Pour une persistance permanente des données qui s’étend sur plusieurs utilisateurs et appareils, l’application peut utiliser le stockage côté serveur. Voici les options :

  • Stockage Blob
  • Stockage de clé-valeur
  • Base de données relationnelle
  • Stockage de tables

Une fois les données enregistrées, l’état de l’utilisateur est conservé et disponible dans tout nouveau circuit.

Pour plus d’informations sur les options de stockage de données Azure, consultez les rubriques suivantes :

URL

Pour les données temporaires représentant l’état de navigation, modélisez les données dans le cadre de l’URL. Voici quelques exemples d’état utilisateur modélisé dans l’URL :

  • ID d’une entité consultée.
  • Numéro de page actuel dans une grille paginée.

Le contenu de la barre d’adresse du navigateur est conservé :

  • Si l’utilisateur recharge manuellement la page.
  • Si le serveur web devient indisponible et que l’utilisateur est obligé de recharger la page pour se connecter à un autre serveur.

Pour plus d’informations sur la définition de modèles d’URL avec la directive @page, consultez Routage et navigation avec ASP.NET Core Blazor.

Stockage du navigateur

Pour les données temporaires que l’utilisateur crée activement, les collections localStorage et sessionStorage sont un emplacement de stockage couramment utilisé :

  • localStorage est limité à la fenêtre du navigateur. Si l’utilisateur recharge la page ou ferme et rouvre le navigateur, l’état persiste. Si l’utilisateur ouvre plusieurs onglets de navigateur, l’état est partagé entre les onglets. Les données sont conservées dans localStorage jusqu’à leur effacement explicite.
  • sessionStorage est limité à l’onglet du navigateur. Si l’utilisateur recharge l’onglet, l’état persiste. Si l’utilisateur ferme l’onglet ou le navigateur, l’état est perdu. Si l’utilisateur ouvre plusieurs onglets de navigateur, chaque onglet a sa propre version indépendante des données.

En général, sessionStorage est plus sûr à utiliser. sessionStorage évite le risque qu’un utilisateur ouvre plusieurs onglets et rencontre les problèmes suivants :

  • Bogues dans le stockage d’état entre les onglets.
  • Comportement confus lorsqu’un onglet remplace l’état d’autres onglets.

localStorage est le meilleur choix si l’application doit conserver l’état pendant la fermeture et la réouverture du navigateur.

Mises en garde pour l’utilisation du stockage du navigateur :

  • À l’instar de l’utilisation d’une base de données côté serveur, le chargement et l’enregistrement des données sont asynchrones.
  • Contrairement à une base de données côté serveur, le stockage n’est pas disponible pendant le prérendu, car la page demandée n’existe pas dans le navigateur pendant la phase de prérendu.
  • Il est raisonnable de stocker quelques kilo-octets de données pour les applications Blazor côté serveur. Au-delà de quelques kilo-octets, vous devez prendre en compte les implications en matière de performances, car les données sont chargées et enregistrées sur le réseau.
  • Les utilisateurs pourraient afficher ou falsifier les données. La protection des données ASP.NET Core peut atténuer le risque. Par exemple, le stockage du navigateur protégé ASP.NET Core utilise la protection des données ASP.NET Core.

Les packages NuGet tiers fournissent des API permettant d’utiliser localStorage et sessionStorage. Il est utile de choisir un package qui utilise de manière transparente la protection des données ASP.NET Core. La protection des données chiffre les données stockées et réduit le risque potentiel de falsification des données stockées. Si les données sérialisées JSON sont stockées en texte brut, les utilisateurs peuvent voir les données à l’aide des outils de développement du navigateur et également modifier les données stockées. La sécurisation des données n’est pas toujours un problème, car ces dernières peuvent être de nature triviale. Par exemple, la lecture ou la modification de la couleur stockée d’un élément d’interface utilisateur n’est pas un risque de sécurité significatif pour l’utilisateur ou l’organisation. Évitez d’autoriser les utilisateurs à inspecter ou à falsifier les données sensibles.

Stockage du navigateur protégé ASP.NET Core

Le stockage par navigateur protégé ASP.NET Core tire parti de la protection des données ASP.NET Core pour localStorage et sessionStorage.

Remarque

Le stockage du navigateur protégé s’appuie sur la protection des données ASP.NET Core et est uniquement pris en charge pour les applications Blazor côté serveur.

Avertissement

Microsoft.AspNetCore.ProtectedBrowserStorage est un package expérimental non pris en charge qui n’est pas destiné à une utilisation en production.

Le package est uniquement disponible pour être utilisé dans les applications ASP.NET Core 3.1.

Configuration

  1. Ajoutez une référence de package à Microsoft.AspNetCore.ProtectedBrowserStorage.

    Remarque

    Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez les articles figurant sous Installer et gérer des packages dans Flux de travail de la consommation des packages (documentation NuGet). Vérifiez les versions du package sur NuGet.org.

  2. Dans le fichier _Host.cshtml, ajoutez le script suivant à l’intérieur de la balise </body> fermante :

    <script src="_content/Microsoft.AspNetCore.ProtectedBrowserStorage/protectedBrowserStorage.js"></script>
    
  3. Dans Startup.ConfigureServices, appelez AddProtectedBrowserStorage pour ajouter des services localStorage et sessionStorage à la collection de services :

    services.AddProtectedBrowserStorage();
    

Enregistrer et charger des données dans un composant

Dans tout composant nécessitant le chargement ou l’enregistrement de données dans le stockage du navigateur, utilisez la directive @inject pour injecter une des instances suivantes :

  • ProtectedLocalStorage
  • ProtectedSessionStorage

Le choix dépend de l’emplacement de stockage du navigateur que vous souhaitez utiliser. sessionStorage est utilisé dans l’exemple suivant :

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

La directive @using peut être placée dans le fichier _Imports.razor de l’application au lieu du composant. L’utilisation du fichier _Imports.razor rend l’espace de noms disponible pour les segments plus volumineux de l’application ou l’ensemble de l’application.

Pour conserver la valeur currentCount dans le composant Counter d’une application en fonction du modèle de projet Blazor, modifiez la méthode IncrementCount pour utiliser ProtectedSessionStore.SetAsync :

private async Task IncrementCount()
{
    currentCount++;
    await ProtectedSessionStore.SetAsync("count", currentCount);
}

Dans les applications plus grandes et plus réalistes, le stockage de champs individuels est un scénario peu probable. Les applications sont plus susceptibles de stocker des objets de modèle entiers qui incluent un état complexe. ProtectedSessionStore sérialise et désérialise automatiquement les données JSON pour stocker des objets d’état complexes.

Dans l’exemple de code précédent, les données currentCount sont stockées en tant que sessionStorage['count'] dans le navigateur de l’utilisateur. Les données ne sont pas stockées en texte brut, mais sont plutôt protégées à l’aide de la protection des données ASP.NET Core. Les données chiffrées peuvent être inspectées si sessionStorage['count'] est évalué dans la console du développeur du navigateur.

Pour récupérer les données currentCount si l’utilisateur revient au composant Counter ultérieurement, notamment si l’utilisateur se trouve sur un nouveau circuit, utilisez ProtectedSessionStore.GetAsync :

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

Si les paramètres du composant incluent l’état de navigation, appelez ProtectedSessionStore.GetAsync et affectez un résultat non null dans OnParametersSetAsync, et non OnInitializedAsync. OnInitializedAsync n’est appelé qu’une seule fois lorsque le composant est instancié pour la première fois. OnInitializedAsync n’est pas appelé ultérieurement si l’utilisateur accède à une autre URL tout en restant sur la même page. Pour plus d’informations, consultez le cycle de vie des composants Razor ASP.NET Core.

Avertissement

Les exemples de cette section ne fonctionnent que si le serveur n’a pas activé le prérendu. Une fois le prérendu activé, une erreur est générée expliquant que les appels d’interopérabilité JavaScript ne peuvent pas être émis, car le composant est en cours de prérendu.

Désactivez le prérendu ou ajoutez du code supplémentaire pour utiliser correctement le prérendu. Pour en savoir plus sur l’écriture de code qui fonctionne avec le prérendu, consultez la section Gérer le prérendu.

Gérer l’état de chargement

Étant donné que le stockage du navigateur est accessible de manière asynchrone via une connexion réseau, il existe toujours un délai avant que les données soient chargées et disponibles pour un composant. Pour obtenir de meilleurs résultats, affichez un message pendant le chargement en cours au lieu d’afficher des données vides ou par défaut.

L’une des approches consiste à déterminer si les données sont null, ce qui signifie que les données sont toujours en cours de chargement. Dans le composant Counter par défaut, le nombre est conservé dans un int. Rendez currentCount nullable en ajoutant un point d’interrogation (?) au type (int) :

private int? currentCount;

Au lieu d’afficher inconditionnellement le nombre et le bouton Increment, affichez ces éléments uniquement si les données sont chargées en vérifiant HasValue :

@if (currentCount.HasValue)
{
    <p>Current count: <strong>@currentCount</strong></p>
    <button @onclick="IncrementCount">Increment</button>
}
else
{
    <p>Loading...</p>
}

Gérer le prérendu

Pendant le prérendu :

  • Il n’existe pas de connexion interactive au navigateur de l’utilisateur.
  • Le navigateur n’a pas encore de page dans laquelle il peut exécuter du code JavaScript.

localStorage ou sessionStorage ne sont pas disponibles lors du prérendu. Si le composant tente d’interagir avec le stockage, une erreur est générée expliquant que les appels d’interopérabilité JavaScript ne peuvent pas être émis, car le composant est en cours de prérendu.

L’un des moyens de résoudre l’erreur consiste à désactiver le prérendu. Il s’agit généralement du meilleur choix si l’application utilise intensivement le stockage basé sur un navigateur. Le prérendu ajoute de la complexité et n’est pas bénéfique pour l’application, car l’application ne peut pas prérendre du contenu utile tant que localStorage ou sessionStorage n’est pas disponible.

Pour désactiver le prérendu, indiquez le mode de rendu avec le paramètre prerender défini sur false au niveau le plus élevé dans la hiérarchie des composants de l’application qui n’est pas un composant racine.

Remarque

Rendre un composant racine interactif, comme le composant App, n’est pas pris en charge. Par conséquent, le prérendu ne peut pas être désactivé directement par le composant App.

Pour les applications basées sur le modèle de projet d’application web Blazor, le prérendu est habituellement désactivé où le composant Routes est utilisé dans le composant App (Components/App.razor) :

<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)" />

Désactivez également le pré-rendu pour le composant HeadOutlet :

<HeadOutlet @rendermode="new InteractiveServerRenderMode(prerender: false)" />

Pour plus d’informations, consultez Modes de rendu ASP.NET Core Blazor.

Pour désactiver le prérendu, ouvrez le fichier _Host.cshtml et remplacez l’attribut render-mode du Tag Helper de composant par Server :

<component type="typeof(App)" render-mode="Server" />

Lorsque le prérendu est désactivé, le prérendu du contenu <head> est désactivé.

Le prérendu peut être utile pour d’autres pages qui n’utilisent pas localStorage ou sessionStorage. Pour conserver le prérendu, reportez l’opération de chargement jusqu’à ce que le navigateur soit connecté au circuit. Voici un exemple de stockage d’une valeur de compteur :

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedLocalStorage ProtectedLocalStore

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

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isConnected = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

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

    private async Task IncrementCount()
    {
        currentCount++;
        await ProtectedLocalStore.SetAsync("count", currentCount);
    }
}
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@inject ProtectedLocalStorage ProtectedLocalStore

@if (isConnected)
{
    <p>Current count: <strong>@currentCount</strong></p>
    <button @onclick="IncrementCount">Increment</button>
}
else
{
    <p>Loading...</p>
}

@code {
    private int currentCount = 0;
    private bool isConnected = false;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isConnected = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

    private async Task LoadStateAsync()
    {
        currentCount = await ProtectedLocalStore.GetAsync<int>("count");
    }

    private async Task IncrementCount()
    {
        currentCount++;
        await ProtectedLocalStore.SetAsync("count", currentCount);
    }
}

Factoriser la conservation de l’état à un emplacement commun

Si de nombreux composants s’appuient sur un stockage basé sur le navigateur, l’implémentation du code du fournisseur d’état crée souvent une duplication de code. L’une des options permettant d’éviter la duplication du code consiste à créer un composant parent du fournisseur d’état qui encapsule la logique du fournisseur d’état. Les composants enfants peuvent fonctionner avec des données persistantes sans tenir compte du mécanisme de persistance d’état.

Dans l’exemple suivant d’un composant CounterStateProvider, les données de compteur sont conservées dans sessionStorage :

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

@if (isLoaded)
{
    <CascadingValue Value="this">
        @ChildContent
    </CascadingValue>
}
else
{
    <p>Loading...</p>
}

@code {
    private bool isLoaded;

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    public int CurrentCount { get; set; }

    protected override async Task OnInitializedAsync()
    {
        var result = await ProtectedSessionStore.GetAsync<int>("count");
        CurrentCount = result.Success ? result.Value : 0;
        isLoaded = true;
    }

    public async Task SaveChangesAsync()
    {
        await ProtectedSessionStore.SetAsync("count", CurrentCount);
    }
}
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

@if (isLoaded)
{
    <CascadingValue Value="this">
        @ChildContent
    </CascadingValue>
}
else
{
    <p>Loading...</p>
}

@code {
    private bool isLoaded;

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    public int CurrentCount { get; set; }

    protected override async Task OnInitializedAsync()
    {
        CurrentCount = await ProtectedSessionStore.GetAsync<int>("count");
        isLoaded = true;
    }

    public async Task SaveChangesAsync()
    {
        await ProtectedSessionStore.SetAsync("count", CurrentCount);
    }
}

Remarque

Pour plus d’informations sur RenderFragment, consultez Composants ASP.NET Core Razor.

Le composant CounterStateProvider gère la phase de chargement en ne rendant pas son contenu enfant tant que le chargement de l’état n’est pas terminé.

Pour rendre l’état accessible à tous les composants d’une application, enveloppez le composant CounterStateProvider autour du Router (<Router>...</Router>) dans le composant Routes avec le rendu interactif côté serveur (SSR interactif) global.

Dans le composant App (Components/App.razor) :

<Routes @rendermode="InteractiveServer" />

Dans le composant Routes (Components/Routes.razor) :

Pour utiliser le composant CounterStateProvider, encapsulez une instance du composant autour de tout autre composant qui nécessite l’accès à l’état du compteur. Pour rendre l’état accessible à tous les composants d’une application, encapsulez le composant CounterStateProvider autour du Router dans le composant App (App.razor) :

<CounterStateProvider>
    <Router ...>
        ...
    </Router>
</CounterStateProvider>

Remarque

Depuis le lancement d’ASP.NET Core 5.0.1 et pour les éventuelles versions 5.x supplémentaires, le composant Router comprend le paramètre PreferExactMatches, qui est défini sur @true. Pour plus d’informations, consultez Migrer de ASP.NET Core 3.1 vers 5.0.

Les composants encapsulés reçoivent et peuvent modifier l’état persistant du compteur. Le composant Counter suivant implémente le modèle :

@page "/counter"

<p>Current count: <strong>@CounterStateProvider?.CurrentCount</strong></p>
<button @onclick="IncrementCount">Increment</button>

@code {
    [CascadingParameter]
    private CounterStateProvider? CounterStateProvider { get; set; }

    private async Task IncrementCount()
    {
        if (CounterStateProvider is not null)
        {
            CounterStateProvider.CurrentCount++;
            await CounterStateProvider.SaveChangesAsync();
        }
    }
}

Le composant précédent n’est pas obligatoire pour interagir avec ProtectedBrowserStorage, et il ne traite pas non plus de phase de « chargement ».

Pour traiter le prérendu comme décrit précédemment, CounterStateProvider peut être modifié de sorte que tous les composants qui consomment les données du compteur fonctionnent automatiquement avec le prérendu. Pour plus d’informations, consultez la section Gérer le prérendu.

En règle générale, le modèle composant parent du fournisseur d’état est recommandé :

  • Pour consommer l’état sur de nombreux composants.
  • S’il n’y a qu’un seul objet d’état de niveau supérieur à conserver.

Pour conserver de nombreux objets d’état différents et consommer différents sous-ensembles d’objets à différents emplacements, il est préférable d’éviter la persistance de l’état à l’échelle globale.

L’état utilisateur créé dans une application Blazor WebAssembly est conservé dans la mémoire du navigateur.

Voici des exemples d’état utilisateur conservé dans la mémoire du navigateur :

  • Hiérarchie des instances de composant et leur sortie de rendu la plus récente dans l’interface utilisateur rendue.
  • Valeurs des champs et des propriétés dans les instances de composant.
  • Données conservées dans des instances de service d’injection de dépendances (DI).
  • Valeurs définies via des appels d’interopérabilité JavaScript.

Lorsqu’un utilisateur ferme et rouvre son navigateur ou recharge la page, l’état utilisateur conservé dans la mémoire du navigateur est perdu.

Remarque

Le stockage du navigateur protégé (espace de noms Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage) s’appuie sur la protection des données ASP.NET Core et est uniquement pris en charge pour les applications Blazor côté serveur.

Conserver l’état entre sessions de navigateur

En règle générale, conservez l’état sur les sessions de navigateur où les utilisateurs créent activement des données, et pas simplement en lisant des données qui existent déjà.

Pour conserver l’état entre sessions de navigateur, l’application doit conserver les données dans un autre emplacement de stockage que la mémoire du navigateur. La persistance d’état n’est pas automatique. Vous devez prendre des mesures lors du développement de l’application pour implémenter la persistance des données avec état.

La persistance des données n’est généralement requise que pour l’état à valeur élevée que les utilisateurs ont consacré des efforts à créer. Dans les exemples suivants, l’état persistant fait gagner du temps ou facilite les activités commerciales :

  • Formulaires web à plusieurs étapes : il est fastidieux pour un utilisateur de saisir à nouveau des données pour plusieurs étapes terminées d’un formulaire web à plusieurs étapes si leur état est perdu. Un utilisateur perd l’état dans ce scénario s’il quitte le formulaire et revient ultérieurement.
  • Paniers d’achat : tout composant important sur le plan commercial d’une application qui représente le chiffre d’affaires potentiel peut être conservé. Un utilisateur qui perd son état, et donc son panier d’achat, est susceptible d’acheter moins de produits ou de services lorsqu’il revient sur le site plus tard.

Une application peut uniquement conserver l’état de l’application. Les interfaces utilisateur ne peuvent pas être conservées, comme les instances de composant et leurs arborescences de rendu. Les composants et les arborescences de rendu ne sont généralement pas sérialisables. Pour conserver l’état de l’interface utilisateur, comme les nœuds développés d’un contrôle d’arborescence, l’application doit utiliser du code personnalisé pour modéliser le comportement de l’état de l’interface utilisateur comme état de l’application sérialisable.

Où conserver l’état

Il existe des emplacements courants pour conserver l’état :

Stockage côté serveur

Pour une persistance permanente des données qui s’étend sur plusieurs utilisateurs et appareils, l’application peut utiliser un stockage indépendant côté serveur accessible via une API web. Voici les options :

  • Stockage Blob
  • Stockage de clé-valeur
  • Base de données relationnelle
  • Stockage de tables

Une fois les données enregistrées, l’état de l’utilisateur est conservé et disponible dans toute nouvelle session de navigateur.

Étant donné que les applications Blazor WebAssembly s’exécutent entièrement dans le navigateur de l’utilisateur, elles nécessitent des mesures supplémentaires pour accéder à des systèmes externes sécurisés, comme les services de stockage et les bases de données. Les applications Blazor WebAssembly sont sécurisées de la même manière que les applications monopages (SPA). En règle générale, une application authentifie un utilisateur via OAuth/OpenID Connect (OIDC), puis interagit avec les services de stockage et les bases de données par le biais d’appels d’API web à une application côté serveur. L’application côté serveur gère le transfert de données entre l’application Blazor WebAssembly et le service de stockage ou la base de données. L’application Blazor WebAssembly maintient une connexion éphémère à l’application côté serveur, tandis que l’application côté serveur dispose d’une connexion permanente au stockage.

Pour en savoir plus, consultez les ressources suivantes :

Pour plus d’informations sur les options de stockage de données Azure, consultez les rubriques suivantes :

URL

Pour les données temporaires représentant l’état de navigation, modélisez les données dans le cadre de l’URL. Voici quelques exemples d’état utilisateur modélisé dans l’URL :

  • ID d’une entité consultée.
  • Numéro de page actuel dans une grille paginée.

Le contenu de la barre d’adresse du navigateur est conservé si l’utilisateur recharge manuellement la page.

Pour plus d’informations sur la définition de modèles d’URL avec la directive @page, consultez Routage et navigation avec ASP.NET Core Blazor.

Stockage du navigateur

Pour les données temporaires que l’utilisateur crée activement, les collections localStorage et sessionStorage sont un emplacement de stockage couramment utilisé :

  • localStorage est limité à la fenêtre du navigateur. Si l’utilisateur recharge la page ou ferme et rouvre le navigateur, l’état persiste. Si l’utilisateur ouvre plusieurs onglets de navigateur, l’état est partagé entre les onglets. Les données sont conservées dans localStorage jusqu’à leur effacement explicite.
  • sessionStorage est limité à l’onglet du navigateur. Si l’utilisateur recharge l’onglet, l’état persiste. Si l’utilisateur ferme l’onglet ou le navigateur, l’état est perdu. Si l’utilisateur ouvre plusieurs onglets de navigateur, chaque onglet a sa propre version indépendante des données.

Remarque

localStorage et sessionStorage peuvent être utilisés dans des applications Blazor WebAssembly, mais uniquement en écrivant du code personnalisé ou en utilisant un package tiers.

En général, sessionStorage est plus sûr à utiliser. sessionStorage évite le risque qu’un utilisateur ouvre plusieurs onglets et rencontre les problèmes suivants :

  • Bogues dans le stockage d’état entre les onglets.
  • Comportement confus lorsqu’un onglet remplace l’état d’autres onglets.

localStorage est le meilleur choix si l’application doit conserver l’état pendant la fermeture et la réouverture du navigateur.

Avertissement

Les utilisateurs peuvent afficher ou falsifier les données stockées dans localStorage et sessionStorage.

Service de conteneur d’état en mémoire

Les composants imbriqués lient généralement des données à l’aide d’une liaison chaînée, comme décrit dans Liaison de données ASP.NET Core Blazor. Les composants imbriqués et non imbriqués peuvent partager l’accès aux données à l’aide d’un conteneur d’état en mémoire inscrit. Une classe de conteneur d’état personnalisé peut utiliser un Action assignable pour notifier les composants de différentes parties de l’application des changements d’état. Dans l’exemple suivant :

  • Une paire de composants utilise un conteneur d’état pour suivre une propriété.
  • Un composant dans l’exemple suivant est imbriqué dans l’autre composant, mais l’imbrication n’est pas nécessaire pour que cette approche fonctionne.

Important

L’exemple de cette section montre comment créer un service de conteneur d’état en mémoire, inscrire le service et utiliser le service dans les composants. L’exemple ne conserve pas les données sans développement ultérieur. Pour le stockage persistant des données, le conteneur d’état doit adopter un mécanisme de stockage sous-jacent qui survit lorsque la mémoire du navigateur est effacée. Cela peut être réalisé avec localStorage/sessionStorage ou une autre technologie.

StateContainer.cs:

public class StateContainer
{
    private string? savedString;

    public string Property
    {
        get => savedString ?? string.Empty;
        set
        {
            savedString = value;
            NotifyStateChanged();
        }
    }

    public event Action? OnChange;

    private void NotifyStateChanged() => OnChange?.Invoke();
}

Applications côté client (fichier Program) :

builder.Services.AddSingleton<StateContainer>();

Applications côté serveur (fichier Program, ASP.NET Core dans .NET 6 ou une version ultérieure) :

builder.Services.AddScoped<StateContainer>();

Applications côté serveur (Startup.ConfigureServices deStartup.cs, ASP.NET Core antérieur à la version 6.0) :

services.AddScoped<StateContainer>();

Shared/Nested.razor:

@implements IDisposable
@inject StateContainer StateContainer

<h2>Nested component</h2>

<p>Nested component Property: <b>@StateContainer.Property</b></p>

<p>
    <button @onclick="ChangePropertyValue">
        Change the Property from the Nested component
    </button>
</p>

@code {
    protected override void OnInitialized()
    {
        StateContainer.OnChange += StateHasChanged;
    }

    private void ChangePropertyValue()
    {
        StateContainer.Property = 
            $"New value set in the Nested component: {DateTime.Now}";
    }

    public void Dispose()
    {
        StateContainer.OnChange -= StateHasChanged;
    }
}

StateContainerExample.razor:

@page "/state-container-example"
@implements IDisposable
@inject StateContainer StateContainer

<h1>State Container Example component</h1>

<p>State Container component Property: <b>@StateContainer.Property</b></p>

<p>
    <button @onclick="ChangePropertyValue">
        Change the Property from the State Container Example component
    </button>
</p>

<Nested />

@code {
    protected override void OnInitialized()
    {
        StateContainer.OnChange += StateHasChanged;
    }

    private void ChangePropertyValue()
    {
        StateContainer.Property = "New value set in the State " +
            $"Container Example component: {DateTime.Now}";
    }

    public void Dispose()
    {
        StateContainer.OnChange -= StateHasChanged;
    }
}

Les composants précédents implémentent IDisposable, et les délégués OnChange sont désinscrits dans les méthodes Dispose, qui sont appelées par le framework lorsque les composants sont supprimés. Pour plus d’informations, consultez le cycle de vie des composants Razor ASP.NET Core.

Autres approches

Lors de l’implémentation d’un stockage d’état personnalisé, une approche utile consiste à adopter des valeurs et des paramètres en cascade :

  • Pour consommer l’état sur de nombreux composants.
  • S’il n’y a qu’un seul objet d’état de niveau supérieur à conserver.

Résolution des problèmes

Dans un service de gestion d’état personnalisé, un rappel appelé en dehors du contexte de synchronisation de Blazor doit envelopper la logique du rappel dans ComponentBase.InvokeAsync pour la déplacer dans le contexte de synchronisation du renderer.

Lorsque le service de gestion d’état n’appelle pas StateHasChanged dans le contexte de synchronisation de Blazor, l’erreur suivante est levée :

System.InvalidOperationException : « Le thread actuel n’est pas associé au répartiteur. Utilisez InvokeAsync() pour basculer l’exécution vers le répartiteur lors du déclenchement de l’état du rendu ou du composant. »

Pour obtenir plus d’informations et un exemple de résolution de cette erreur, consultez Rendu des composants Razor ASP.NET Core.

Ressources supplémentaires