Partage via


Conseils pour ASP.NET Core BlazorSignalR

Remarque

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

Avertissement

Cette version d’ASP.NET Core n’est plus prise en charge. Pour plus d’informations, consultez la Stratégie de prise en charge de .NET et .NET Core. 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 explique comment configurer et gérer les connexions SignalR dans les applications Blazor.

Pour obtenir des conseils généraux sur la configuration d’ASP.NET Core SignalR, consultez les rubriques de la zone Vue d’ensemble d’ASP.NET Core SignalR de la documentation, en particulier Configuration d’ASP.NET Core SignalR.

Les applications côté serveur utilisent ASP.NET Core SignalR pour communiquer avec le navigateur. Les conditions d’hébergement et de mise à l’échelle de SignalR s’appliquent aux applications côté serveur.

Blazor fonctionne le mieux lors de l’utilisation de WebSockets en tant que transport SignalRen raison d’une latence plus faible, d’une meilleure fiabilité et d’une sécurité. L’interrogation longue est utilisée par SignalR lorsque WebSockets n’est pas disponible ou lorsque l’application est explicitement configurée pour utiliser l’interrogation longue.

Azure SignalR Service avec reconnexion avec état

La reconnexion avec état (WithStatefulReconnect) a été mise en production avec .NET 8, mais n’est actuellement pas prise en charge pour Azure SignalR Service. Pour plus d’informations, consultez l’article Prise en charge de la reconnexion avec état ? (Azure/azure-signalr #1878).

Compression WebSocket pour des composants de serveur interactif

Par défaut, les composants de serveur interactif :

  • Activez la compression des connexions WebSocket. ConfigureWebsocketOptions contrôle la compression WebSocket.

  • Adoptez une directive de stratégie de sécurité du contenu (CSP) frame-ancestors définie sur 'self', qui autorise uniquement l’incorporation de l’application dans un <iframe> de l’origine à partir de laquelle l’application est servie lorsque la compression est activée ou lorsqu’une configuration est fournie pour le contexte WebSocket. ContentSecurityFrameAncestorPolicy contrôle le CSP frame-ancestors.

Le CSP frame-ancestors peut être supprimé manuellement en définissant la valeur de ConfigureWebSocketOptions sur null, car vous souhaitez peut-être configurer le CSP de manière centralisée. Lorsque le CSP frame-ancestors est managé de manière centralisée, vous devez veiller à appliquer une stratégie chaque fois que le premier document est rendu. Nous déconseillons de supprimer complètement la stratégie, car cela peut rendre l’application vulnérable aux attaques.

Exemples d'utilisation :

Désactiver la compression en définissant ConfigureWebSocketOptions sur null, ce qui réduit la vulnérabilité de l’application aux attaques, mais peut entraîner une réduction des performances :

builder.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode(o => o.ConfigureWebSocketOptions = null)

Lorsque la compression est activée, configurez un CSP frame-ancestors plus strict avec une valeur de 'none' (guillemets simples obligatoires), qui autorise la compression WebSocket mais empêche les navigateurs d’incorporer l’application dans n’importe quel <iframe> :

builder.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = "'none'")

Lorsque la compression est activée, supprimez le CSP frame-ancestors en définissant ContentSecurityFrameAncestorsPolicy sur null. Ce scénario est recommandé uniquement pour les applications qui définissent le CSP de manière centralisée :

builder.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = null)

Important

Les navigateurs appliquent des directives CSP à partir de plusieurs en-têtes CSP à l’aide de la valeur de directive de stratégie la plus stricte. Par conséquent, un développeur ne peut pas ajouter une stratégie frame-ancestors plus faible que 'self' volontairement ou par erreur.

Les guillemets simples sont obligatoires sur la valeur de chaîne passée à ContentSecurityFrameAncestorsPolicy :

Valeurs non prises en charge : none, self

Valeurs prises en charge : 'none', 'self'

Des options supplémentaires incluent la spécification d’une ou de plusieurs sources d’hôte et de sources de schéma.

Pour plus d’informations sur la sécurité, consultez Aide sur l’atténuation des menaces pour le rendu interactif côté serveur ASP.NET Core Blazor. Pour plus d’informations sur la directive frame-ancestors, consultez CSP : frame-ancestors (documentation MDN).

Désactiver la compression des réponses pour le rechargement à chaud

Lorsque vous utilisez le Rechargement à chaud, désactivez l’intergiciel de compression de réponse dans l’environnement Development. Que le code par défaut d’un modèle de projet soit utilisé ou non, appelez toujours UseResponseCompression en premier dans le pipeline de traitement des requêtes.

Dans le fichier Program :

if (!app.Environment.IsDevelopment())
{
    app.UseResponseCompression();
}

Négociation cross-origin SignalR côté client pour l’authentification

Cette section explique comment configurer le client sous-jacent de SignalR pour qu’il envoie des informations d’identification, comme des cookies ou des en-têtes d’authentification HTTP.

Utilisez SetBrowserRequestCredentials pour définir Include sur les requêtes fetch cross-origin.

IncludeRequestCredentialsMessageHandler.cs :

using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Http;

public class IncludeRequestCredentialsMessageHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
        return base.SendAsync(request, cancellationToken);
    }
}

Quand une connexion hub est générée, affectez HttpMessageHandler à l’option HttpMessageHandlerFactory :

private HubConnectionBuilder? hubConnection;

...

hubConnection = new HubConnectionBuilder()
    .WithUrl(new Uri(Navigation.ToAbsoluteUri("/chathub")), options =>
    {
        options.HttpMessageHandlerFactory = innerHandler => 
            new IncludeRequestCredentialsMessageHandler { InnerHandler = innerHandler };
    }).Build();

L’exemple précédent montre comment configure l’URL de connexion du hub à l’adresse d’URI absolu à /chathub. L’URI peut également être défini via une chaîne, par exemple https://signalr.example.com, ou via une configuration. Navigation est un NavigationManager injecté.

Pour plus d’informations, consultez Configuration d’ASP.NET Core SignalR.

Rendu côté client

Si le prérendu est configuré, il se produit avant l’établissement de la connexion du client au serveur. Pour plus d’informations, consultez Prévisualiser les composants ASP.NET Core Razor.

Si le prérendu est configuré, il se produit avant l’établissement de la connexion du client au serveur. Pour plus d’informations, consultez les articles suivants :

Taille d’état prérendu et limite de taille du message SignalR

Une grande taille d’état prédéfini peut dépasser la limite de taille du message du circuit SignalR, ce qui entraîne les résultats suivants :

  • Le circuit SignalR ne parvient pas à s’initialiser avec une erreur sur le client : Circuit host not initialized.
  • L’interface utilisateur de reconnexion sur le client s’affiche lors de l’échec du circuit. La récupération n’est pas possible.

Pour résoudre le problème, utilisez l’une des approches suivantes :

  • Réduisez la quantité de données que vous placez dans l’état prérendu.
  • Augmentez la limite de taille des messages SignalR. AVERTISSEMENT : l’augmentation de la limite peut augmenter le risque d’attaques par déni de service (DoS).

Ressources côté client supplémentaires

Utiliser l’affinité de session (sessions persistantes) pour l’hébergement de batteries de serveurs côté serveur

Lorsque plusieurs serveurs back-end sont en cours d’utilisation, l’application doit implémenter l’affinité de session, aussi appelée sessions persistantes. L’affinité de session garantit que le circuit d’un client se reconnecte au même serveur si la connexion est interrompue, ce qui est important, car l’état du client est conservé uniquement dans la mémoire du serveur d’origine qui a établi le circuit du client.

L’erreur suivante est levée par une application qui n’a pas activé l’affinité de session dans une batterie de serveurs :

Uncaught (in promise) Error: Invocation canceled due to the underlying connection being closed.

Pour plus d’informations sur l’affinité de session avec l’hébergement Azure App Service, consultez Héberger et déployer des applications Blazor ASP.NET Core côté serveur.

Service Azure SignalR

Le Azure SignalR Service fonctionne conjointement avec le hub SignalR de l’application pour effectuer le scale-out d’une application côté serveur vers un grand nombre de connexions simultanées. De plus, la portée générale du service et les centres de données hautes performances contribuent de manière significative à réduire la latence due aux emplacements géographiques.

Le service n’est pas nécessaire pour les applications Blazor hébergées dans Azure App Service ou Azure Container Apps, mais peut être utile dans d’autres environnements d’hébergement :

  • Pour faciliter le scale-out de la connexion.
  • Gérer la distribution globale

Pour plus d’informations, consultez Héberger et déployer des applications ASP.NET Core Blazor côté serveur.

Options de gestionnaire de circuit côté serveur

Configurez le circuit avec CircuitOptions. Consultez les valeurs par défaut dans la source de référence.

Remarque

Les liens de documentation vers la source de référence .NET chargent généralement la branche par défaut du référentiel, qui représente le développement actuel pour la prochaine version de .NET. Pour sélectionner une balise pour une version spécifique, utilisez la liste déroulante Échanger les branches ou les balises. Pour plus d’informations, consultez Comment sélectionner une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Parcourez ou définissez les options dans le fichier Program avec un délégué d’options sur AddInteractiveServerComponents. L’espace réservé {OPTION} représente l’option et l’espace réservé {VALUE} est la valeur.

Dans le fichier Program :

builder.Services.AddRazorComponents().AddInteractiveServerComponents(options =>
{
    options.{OPTION} = {VALUE};
});

Parcourez ou définissez les options dans le fichier Program avec un délégué d’options sur AddServerSideBlazor. L’espace réservé {OPTION} représente l’option et l’espace réservé {VALUE} est la valeur.

Dans le fichier Program :

builder.Services.AddServerSideBlazor(options =>
{
    options.{OPTION} = {VALUE};
});

Parcourez ou définissez les options dans Startup.ConfigureServices avec un délégué d’options sur AddServerSideBlazor. L’espace réservé {OPTION} représente l’option et l’espace réservé {VALUE} est la valeur.

Dans Startup.ConfigureServices de Startup.cs :

services.AddServerSideBlazor(options =>
{
    options.{OPTION} = {VALUE};
});

Pour configurer le HubConnectionContext, utilisez HubConnectionContextOptions avec AddHubOptions. Consultez les valeurs par défaut pour les options de contexte de connexion hub dans source de référence. Pour obtenir des descriptions dans la documentation SignalR, consultez Configuration SignalR ASP.NET Core. L’espace réservé {OPTION} représente l’option et l’espace réservé {VALUE} est la valeur.

Remarque

Les liens de documentation vers la source de référence .NET chargent généralement la branche par défaut du référentiel, qui représente le développement actuel pour la prochaine version de .NET. Pour sélectionner une balise pour une version spécifique, utilisez la liste déroulante Échanger les branches ou les balises. Pour plus d’informations, consultez Comment sélectionner une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Dans le fichier Program :

builder.Services.AddRazorComponents().AddInteractiveServerComponents().AddHubOptions(options =>
{
    options.{OPTION} = {VALUE};
});

Dans le fichier Program :

builder.Services.AddServerSideBlazor().AddHubOptions(options =>
{
    options.{OPTION} = {VALUE};
});

Dans Startup.ConfigureServices de Startup.cs :

services.AddServerSideBlazor().AddHubOptions(options =>
{
    options.{OPTION} = {VALUE};
});

Warning

La valeur par défaut de MaximumReceiveMessageSize est 32 Ko. L’augmentation de la valeur peut augmenter le risque d’attaques par déni de service (DoS).

Blazor s’appuie sur MaximumParallelInvocationsPerClient défini sur 1, qui est la valeur par défaut. Pour plus d’informations, consultez MaximumParallelInvocationsPerClient > 1 interrompt le chargement de fichiers en mode Blazor Server (dotnet/aspnetcore #53951).

Pour plus d’informations sur la gestion de la mémoire, consultez Héberger et déployer des applications ASP.NET Core Blazor côté serveur.

Options hub Blazor

Configurez les options MapBlazorHub pour contrôler HttpConnectionDispatcherOptions du hub Blazor. Consultez les valeurs par défaut pour les options de répartiteur de la connexion de hub dans source de référence. L’espace réservé {OPTION} représente l’option et l’espace réservé {VALUE} est la valeur.

Remarque

Les liens de documentation vers la source de référence .NET chargent généralement la branche par défaut du référentiel, qui représente le développement actuel pour la prochaine version de .NET. Pour sélectionner une balise pour une version spécifique, utilisez la liste déroulante Échanger les branches ou les balises. Pour plus d’informations, consultez Comment sélectionner une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Placez l’appel à app.MapBlazorHub après l’appel à app.MapRazorComponents dans le fichier Program de l’application :

app.MapBlazorHub(options =>
{
    options.{OPTION} = {VALUE};
});

La configuration du hub utilisé par AddInteractiveServerRenderMode avec MapBlazorHub échoue et lève une exception AmbiguousMatchException :

Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints.

Pour contourner le problème pour les applications ciblant .NET 8, accordez une priorité plus élevée au hub personnalisé Blazor configuré à l’aide de la méthode WithOrder :

app.MapBlazorHub(options =>
{
    options.CloseOnAuthenticationExpiration = true;
}).WithOrder(-1);

Pour plus d’informations, consultez les ressources suivantes :

Fournissez les options à app.MapBlazorHub dans le fichier Program de l’application :

app.MapBlazorHub(options =>
{
    options.{OPTION} = {VALUE};
});

Fournissez les options à app.MapBlazorHub dans la configuration du routage des points de terminaison :

app.UseEndpoints(endpoints =>
{
    endpoints.MapBlazorHub(options =>
    {
        options.{OPTION} = {VALUE};
    });
    ...
});

Taille maximale du message de réception

Cette section s’applique uniquement aux projets qui implémentent SignalR.

La taille maximale des messages SignalR entrants autorisée pour les méthodes hub est limitée par HubOptions.MaximumReceiveMessageSize (valeur par défaut : 32 Ko). Les messages SignalR de taille supérieure à MaximumReceiveMessageSize génèrent une erreur. Le framework n’impose pas de limite à la taille d’un message SignalR du hub vers un client.

Lorsque la journalisation de SignalR n’est pas définie sur Débogage ou Trace, une erreur de taille de message s’affiche uniquement dans la console des outils de développement du navigateur :

Erreur : Connexion déconnectée avec l’erreur « Erreur : Le serveur a renvoyé une erreur à la fermeture : connexion fermée avec une erreur. ».

Lorsque la journalisation côté serveur SignalR est définie sur Débogage ou Trace, la journalisation côté serveur affiche InvalidDataException pour une erreur de taille de message.

appsettings.Development.json :

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      ...
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

Erreur :

System.IO.InvalidDataException : la taille maximale du message de 32768 octets a été dépassée. La taille du message peut être configurée dans AddHubOptions.

Une approche consiste à augmenter la limite en définissant MaximumReceiveMessageSize dans le fichier Program. L’exemple suivant définit la taille maximale des messages de réception à 64 Ko :

builder.Services.AddRazorComponents().AddInteractiveServerComponents()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

L’augmentation de la taille des messages entrants SignalR est limitée par le coût d’exiger davantage de ressources serveur et augmente le risque d’attaques par déni de service (DoS). En outre, la lecture d’une grande quantité de contenu dans la mémoire sous forme de chaînes ou de tableaux d’octets peut également entraîner des allocations qui fonctionnent mal avec le récupérateur de mémoire, ce qui entraîne des pénalités de performances supplémentaires.

Une meilleure option de lecture de charges utiles volumineuses consiste à envoyer le contenu en blocs plus petits et à traiter la charge utile en tant que Stream. Cela peut être utilisé lors de la lecture de charges utiles JSON d’interopérabilité JavaScript (JS) volumineuses ou si les données d’interopérabilité JS sont disponibles sous forme d’octets bruts. Pour obtenir un exemple illustrant l’envoi de charges utiles binaires volumineuses dans des applications côté serveur à l’aide de techniques similaires au composant InputFile, consultez l’exemple d’application Envoi binaire et l’exemple de composant BlazorInputLargeTextArea.

Remarque

Les liens de documentation vers la source de référence .NET chargent généralement la branche par défaut du référentiel, qui représente le développement actuel pour la prochaine version de .NET. Pour sélectionner une balise pour une version spécifique, utilisez la liste déroulante Échanger les branches ou les balises. Pour plus d’informations, consultez Comment sélectionner une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Les formulaires qui traitent des charges utiles volumineuses sur SignalR peuvent également utiliser l’interopérabilité JS de diffusion en continu directement. Pour plus d’informations, consultez Appeler des fonctions JavaScript à partir de méthodes .NET dans ASP.NET Core Blazor. Pour obtenir un exemple de formulaire qui diffuse des <textarea> données vers le serveur, reportez-vous aux formulairesBlazor Résoudre les problèmes ASP.NET Core.

Une approche consiste à augmenter la limite en définissant MaximumReceiveMessageSize dans le fichier Program. L’exemple suivant définit la taille maximale des messages de réception à 64 Ko :

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

L’augmentation de la taille des messages entrants SignalR est limitée par le coût d’exiger davantage de ressources serveur et augmente le risque d’attaques par déni de service (DoS). En outre, la lecture d’une grande quantité de contenu dans la mémoire sous forme de chaînes ou de tableaux d’octets peut également entraîner des allocations qui fonctionnent mal avec le récupérateur de mémoire, ce qui entraîne des pénalités de performances supplémentaires.

Une meilleure option de lecture de charges utiles volumineuses consiste à envoyer le contenu en blocs plus petits et à traiter la charge utile en tant que Stream. Cela peut être utilisé lors de la lecture de charges utiles JSON d’interopérabilité JavaScript (JS) volumineuses ou si les données d’interopérabilité JS sont disponibles sous forme d’octets bruts. Pour obtenir un exemple illustrant l’envoi de charges utiles binaires volumineuses dans Blazor Server à l’aide de techniques similaires au composant InputFile, consultez l’exemple d’application Envoi binaire et l’exemple de composant BlazorInputLargeTextArea.

Remarque

Les liens de documentation vers la source de référence .NET chargent généralement la branche par défaut du référentiel, qui représente le développement actuel pour la prochaine version de .NET. Pour sélectionner une balise pour une version spécifique, utilisez la liste déroulante Échanger les branches ou les balises. Pour plus d’informations, consultez Comment sélectionner une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Les formulaires qui traitent des charges utiles volumineuses sur SignalR peuvent également utiliser l’interopérabilité JS de diffusion en continu directement. Pour plus d’informations, consultez Appeler des fonctions JavaScript à partir de méthodes .NET dans ASP.NET Core Blazor. Pour obtenir un exemple de formulaire qui diffuse des <textarea> données dans une Blazor Server application, reportez-vous aux Blazorformulaires Résoudre les problèmes ASP.NET formulaires Core.

Augmentez la limite en définissant MaximumReceiveMessageSize dans Startup.ConfigureServices :

services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

L’augmentation de la taille des messages entrants SignalR est limitée par le coût d’exiger davantage de ressources serveur et augmente le risque d’attaques par déni de service (DoS). En outre, la lecture d’une grande quantité de contenu dans la mémoire sous forme de chaînes ou de tableaux d’octets peut également entraîner des allocations qui fonctionnent mal avec le récupérateur de mémoire, ce qui entraîne des pénalités de performances supplémentaires.

Tenez compte des conseils suivants lors du développement de code qui transfère une grande quantité de données :

  • Tirez parti de la prise en charge de l’interopérabilité JS de diffusion en continu native pour transférer des données de taille supérieure à la limite de taille de message entrant SignalR :
  • Conseils généraux :
    • N’allouez pas d’objets volumineux dans JS et le code C#.
    • Libérez la mémoire consommée lorsque le processus est terminé ou annulé.
    • Appliquez les exigences supplémentaires suivantes à des fins de sécurité :
      • Déclarez la taille de fichier ou de données maximale qui peut être transmise.
      • Déclarez le taux de chargement minimal du client vers le serveur.
    • Une fois les données reçues par le serveur, les données peuvent être :
      • Stockées temporairement dans une mémoire tampon jusqu’à ce que tous les segments soient collectés.
      • Consommées immédiatement. Par exemple, les données peuvent être stockées immédiatement dans une base de données ou écrites sur le disque à mesure que chaque segment est reçu.
  • Découpez les données en morceaux plus petits et envoyez les segments de données séquentiellement jusqu’à ce que toutes les données soient reçues par le serveur.
  • N’allouez pas d’objets volumineux dans JS et le code C#.
  • Ne bloquez pas le thread d’interface utilisateur main pendant de longues périodes lors de l’envoi ou de la réception de données.
  • Libérez la mémoire consommée lorsque le processus est terminé ou annulé.
  • Appliquez les exigences supplémentaires suivantes à des fins de sécurité :
    • Déclarez la taille de fichier ou de données maximale qui peut être transmise.
    • Déclarez le taux de chargement minimal du client vers le serveur.
  • Une fois les données reçues par le serveur, les données peuvent être :
    • Stockées temporairement dans une mémoire tampon jusqu’à ce que tous les segments soient collectés.
    • Consommées immédiatement. Par exemple, les données peuvent être stockées immédiatement dans une base de données ou écrites sur le disque à mesure que chaque segment est reçu.

Configuration de l’itinéraire du point de terminaison hub côté serveur Blazor

Dans le fichier Program, appelez MapBlazorHub pour mapper le BlazorHub au chemin d’accès par défaut de l’application. Le script Blazor (blazor.*.js) pointe automatiquement vers le point de terminaison créé par MapBlazorHub.

Refléter l’état de connexion côté serveur dans l’interface utilisateur

Lorsque le client détecte que la connexion a été perdue, une interface utilisateur par défaut s’affiche à l’utilisateur pendant que le client tente de se reconnecter. Si la reconnexion échoue, l’utilisateur a la possibilité de réessayer.

Pour personnaliser l’interface utilisateur, définissez un élément unique avec un id de components-reconnect-modal. L’exemple suivant place l’élément dans le composant App.

App.razor :

Pour personnaliser l’interface utilisateur, définissez un élément unique avec un id de components-reconnect-modal. L’exemple suivant place l’élément dans la page hôte.

Pages/_Host.cshtml :

Pour personnaliser l’interface utilisateur, définissez un élément unique avec un id de components-reconnect-modal. L’exemple suivant place l’élément dans la page de disposition.

Pages/_Layout.cshtml :

Pour personnaliser l’interface utilisateur, définissez un élément unique avec un id de components-reconnect-modal. L’exemple suivant place l’élément dans la page hôte.

Pages/_Host.cshtml :

<div id="components-reconnect-modal">
    There was a problem with the connection!
</div>

Remarque

Si plusieurs éléments avec un id de components-reconnect-modal sont rendus par l’application, seul le premier élément rendu reçoit des modifications de classe CSS pour afficher ou masquer l’élément.

Ajoutez les styles CSS suivants à la feuille de style du site.

wwwroot/app.css :

wwwroot/css/site.css :

#components-reconnect-modal {
    display: none;
}

#components-reconnect-modal.components-reconnect-show, 
#components-reconnect-modal.components-reconnect-failed, 
#components-reconnect-modal.components-reconnect-rejected {
    display: block;
}

Le tableau suivant décrit les classes CSS appliquées à l’élément components-reconnect-modal par le framework Blazor.

Classe CSS Indique...
components-reconnect-show Une connexion perdue. Le client tente de se reconnecter. Affiche le modal.
components-reconnect-hide Une connexion active est rétablie avec le serveur. Masque le modal.
components-reconnect-failed Échec de la reconnexion, probablement en raison d’une défaillance réseau. Pour tenter une reconnexion, appelez window.Blazor.reconnect() en JavaScript.
components-reconnect-rejected La reconnexion a été rejetée. Le serveur a été atteint, mais a refusé la connexion, et l’état de l’utilisateur sur le serveur est perdu. Pour recharger l’application, appelez location.reload() en JavaScript. Cet état de connexion peut se produire lorsque :
  • Un incident dans le circuit côté serveur se produit.
  • Le client est déconnecté suffisamment longtemps pour que le serveur supprime l’état de l’utilisateur. Les instances des composants de l’utilisateur sont supprimées.
  • Le serveur est redémarré ou le processus de travail de l’application est recyclé.

Personnalisez le délai avant l’affichage de l’interface utilisateur de reconnexion en définissant la propriété transition-delay dans le CSS du site pour l’élément modal. L’exemple suivant modifie le délai de transition de 500 ms (par défaut) à 1 000 ms (1 seconde).

wwwroot/app.css :

wwwroot/css/site.css :

#components-reconnect-modal {
    transition: visibility 0s linear 1000ms;
}

Pour afficher la tentative de reconnexion actuelle, définissez un élément avec un id de components-reconnect-current-attempt. Pour afficher le nombre maximal de nouvelles tentatives de reconnexion, définissez un élément avec un id de components-reconnect-max-retries. L’exemple suivant place ces éléments à l’intérieur d’un élément modal de tentative de reconnexion à la suite de l’exemple précédent.

<div id="components-reconnect-modal">
    There was a problem with the connection!
    (Current reconnect attempt: 
    <span id="components-reconnect-current-attempt"></span> /
    <span id="components-reconnect-max-retries"></span>)
</div>

Lorsque la fenêtre modale de reconnexion personnalisée s’affiche, elle affiche un contenu similaire à ce qui suit en fonction du code précédent :

There was a problem with the connection! (Current reconnect attempt: 3 / 8)

Rendu côté serveur

Par défaut, les composants sont prérendus sur le serveur avant l’établissement de la connexion cliente au serveur. Pour plus d’informations, consultez Prévisualiser les composants ASP.NET Core Razor.

Par défaut, les composants sont prérendus sur le serveur avant l’établissement de la connexion cliente au serveur. Pour plus d’informations, consultez assistance des balises de composant dans ASP.NET Core.

Surveiller l’activité du circuit côté serveur

Surveillez l’activité du circuit entrant à l’aide de la méthode CreateInboundActivityHandler sur CircuitHandler. L’activité du circuit entrant est toute activité envoyée du navigateur au serveur, comme les événements d’interface utilisateur ou les appels d’interopérabilité JavaScript-to-.NET.

Par exemple, vous pouvez utiliser un gestionnaire d’activités de circuit pour détecter si le client est inactif et journaliser son ID de circuit (Circuit.Id) :

using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.Options;
using Timer = System.Timers.Timer;

public sealed class IdleCircuitHandler : CircuitHandler, IDisposable
{
    private Circuit? currentCircuit;
    private readonly ILogger logger;
    private readonly Timer timer;

    public IdleCircuitHandler(ILogger<IdleCircuitHandler> logger, 
        IOptions<IdleCircuitOptions> options)
    {
        timer = new Timer
        {
            Interval = options.Value.IdleTimeout.TotalMilliseconds,
            AutoReset = false
        };

        timer.Elapsed += CircuitIdle;
        this.logger = logger;
    }

    private void CircuitIdle(object? sender, System.Timers.ElapsedEventArgs e)
    {
        logger.LogInformation("{CircuitId} is idle", currentCircuit?.Id);
    }

    public override Task OnCircuitOpenedAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        currentCircuit = circuit;

        return Task.CompletedTask;
    }

    public override Func<CircuitInboundActivityContext, Task> CreateInboundActivityHandler(
        Func<CircuitInboundActivityContext, Task> next)
    {
        return context =>
        {
            timer.Stop();
            timer.Start();

            return next(context);
        };
    }

    public void Dispose() => timer.Dispose();
}

public class IdleCircuitOptions
{
    public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(5);
}

public static class IdleCircuitHandlerServiceCollectionExtensions
{
    public static IServiceCollection AddIdleCircuitHandler(
        this IServiceCollection services, 
        Action<IdleCircuitOptions> configureOptions)
    {
        services.Configure(configureOptions);
        services.AddIdleCircuitHandler();

        return services;
    }

    public static IServiceCollection AddIdleCircuitHandler(
        this IServiceCollection services)
    {
        services.AddScoped<CircuitHandler, IdleCircuitHandler>();

        return services;
    }
}

Inscrivez le service dans le fichier Program. L’exemple suivant configure le délai d’inactivité par défaut de cinq minutes à cinq secondes pour tester l’implémentation de IdleCircuitHandler précédente :

builder.Services.AddIdleCircuitHandler(options => 
    options.IdleTimeout = TimeSpan.FromSeconds(5));

Les gestionnaires d’activités de circuit proposent également une approche pour accéder aux services Blazor délimités à partir d’autres étendues d’injection de dépendances non Blazor. Pour plus d’informations et d’exemples, consultez :

Démarrage de Blazor

Configurez le démarrage manuel du circuit SignalR d’une application Blazor dans le fichier App.razor (Blazor Web App) :

Configurez le démarrage manuel du circuit SignalR d’une application Blazor dans le fichier Pages/_Host.cshtml (Blazor Server) :

Configurez le démarrage manuel du circuit SignalR d’une application Blazor dans le fichier Pages/_Layout.cshtml (Blazor Server) :

Configurez le démarrage manuel du circuit SignalR d’une application Blazor dans le fichier Pages/_Host.cshtml (Blazor Server) :

  • Ajoutez un attribut autostart="false" à la balise <script> pour le script blazor.*.js.
  • Placez un script qui appelle Blazor.start() après le chargement du script Blazor et à l’intérieur de la balise fermante </body>.

Quand autostart est désactivé, tout aspect de l’application qui ne dépend pas du circuit fonctionne normalement. Par exemple, le routage côté client est opérationnel. Toutefois, tout aspect qui dépend du circuit n’est pas opérationnel tant que Blazor.start() n’est pas appelé. Le comportement de l’application est imprévisible sans circuit établi. Par exemple, les méthodes de composant ne parviennent pas à s’exécuter lorsque le circuit est déconnecté.

Pour plus d’informations, notamment la façon d’initialiser Blazor lorsque le document est prêt et comment chaîner à un JS Promise, consultez Démarrage ASP.NET Core Blazor.

Configurer les délais d’expiration SignalR et Keep-Alive sur le client

Configurez les valeurs suivantes pour le client :

  • withServerTimeout : configure le délai d’attente du serveur en millisecondes. Si ce délai d’attente s’écoule sans réception de messages du serveur, la connexion se termine avec une erreur. La valeur de délai d'attente par défaut est de 30 secondes. Le délai d’expiration du serveur doit être au moins le double de la valeur affectée à l’intervalle Keep-Alive (withKeepAliveInterval).
  • withKeepAliveInterval : configure l’intervalle Keep-Alive en millisecondes (intervalle par défaut auquel effectuer un test ping sur le serveur). Ce paramètre permet au serveur de détecter les déconnexions matérielles, par exemple lorsqu’un client débranche son ordinateur du réseau. Le ping se produit au maximum aussi souvent que le serveur envoie des pings. Si le serveur envoie un ping toutes les cinq secondes, l’affectation d’une valeur inférieure à 5000 (5 secondes) correspond à un test ping toutes les cinq secondes. La valeur par défaut est de 15 secondes. L’intervalle Keep-Alive doit être inférieur ou égal à la moitié de la valeur affectée au délai d’expiration du serveur (withServerTimeout).

L’exemple suivant pour le fichier App.razor (Blazor Web App) affiche l’affectation des valeurs par défaut.

Blazor Web App :

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      configureSignalR: function (builder) {
        builder.withServerTimeout(30000).withKeepAliveInterval(15000);
      }
    }
  });
</script>

L’exemple suivant pour le fichier Pages/_Host.cshtml (Blazor Server, toutes les versions sauf ASP.NET Core dans .NET 6) ou le fichier Pages/_Layout.cshtml (Blazor Server, ASP.NET Core dans .NET 6).

Blazor Server :

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      builder.withServerTimeout(30000).withKeepAliveInterval(15000);
    }
  });
</script>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de script Blazor et le nom de fichier. Pour connaître l’emplacement du script et le chemin d’accès à utiliser, consultez Structure de projet Blazor ASP.NET Core.

Lors de la création d’une connexion hub dans un composant, définissez ServerTimeout (valeur par défaut : 30 secondes) et KeepAliveInterval (valeur par défaut : 15 secondes) sur le HubConnectionBuilder. Définissez HandshakeTimeout (valeur par défaut : 15 secondes) au niveau de la HubConnection générée. L’exemple suivant montre l’affectation de valeurs par défaut :

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .WithServerTimeout(TimeSpan.FromSeconds(30))
        .WithKeepAliveInterval(TimeSpan.FromSeconds(15))
        .Build();

    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(15);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

Configurez les valeurs suivantes pour le client :

  • serverTimeoutInMilliseconds : délai d’attente du serveur en millisecondes. Si ce délai d’attente s’écoule sans réception de messages du serveur, la connexion se termine avec une erreur. La valeur de délai d'attente par défaut est de 30 secondes. Le délai d’expiration du serveur doit être au moins le double de la valeur affectée à l’intervalle Keep-Alive (keepAliveIntervalInMilliseconds).
  • keepAliveIntervalInMilliseconds : intervalle par défaut auquel effectuer un ping sur le serveur. Ce paramètre permet au serveur de détecter les déconnexions matérielles, par exemple lorsqu’un client débranche son ordinateur du réseau. Le ping se produit au maximum aussi souvent que le serveur envoie des pings. Si le serveur envoie un ping toutes les cinq secondes, l’affectation d’une valeur inférieure à 5000 (5 secondes) correspond à un test ping toutes les cinq secondes. La valeur par défaut est de 15 secondes. L’intervalle Keep-Alive doit être inférieur ou égal à la moitié de la valeur affectée au délai d’expiration du serveur (serverTimeoutInMilliseconds).

L’exemple suivant pour le fichier Pages/_Host.cshtml (Blazor Server, toutes les versions sauf ASP.NET Core dans .NET 6) ou le fichier Pages/_Layout.cshtml (Blazor Server, ASP.NET Core dans .NET 6) :

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      let c = builder.build();
      c.serverTimeoutInMilliseconds = 30000;
      c.keepAliveIntervalInMilliseconds = 15000;
      builder.build = () => {
        return c;
      };
    }
  });
</script>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de script Blazor et le nom de fichier. Pour connaître l’emplacement du script et le chemin d’accès à utiliser, consultez Structure de projet Blazor ASP.NET Core.

Lors de la création d’une connexion hub dans un composant, définissez ServerTimeout (valeur par défaut : 30 secondes), HandshakeTimeout (valeur par défaut : 15 secondes) et KeepAliveInterval (valeur par défaut : 15 secondes) sur la HubConnection générée. L’exemple suivant montre l’affectation de valeurs par défaut :

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .Build();

    hubConnection.ServerTimeout = TimeSpan.FromSeconds(30);
    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(15);
    hubConnection.KeepAliveInterval = TimeSpan.FromSeconds(15);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

Lors de la modification des valeurs du délai d’expiration du serveur (ServerTimeout) ou de l’intervalle Keep-Alive (KeepAliveInterval) :

  • Le délai d’expiration du serveur doit être au moins le double de la valeur affectée à l’intervalle Keep-Alive.
  • L’intervalle Keep-Alive doit être inférieur ou égal à la moitié de la valeur affectée au délai d’expiration du serveur.

Pour plus d’informations, consultez les sections Déploiement global et échecs de connexion des articles suivants :

Modifier le gestionnaire de reconnexion côté serveur

Les événements de reconnexion de circuit du gestionnaire de reconnexion peuvent être modifiés pour des comportements personnalisés, par exemple :

  • Pour avertir l’utilisateur si la connexion est supprimée.
  • Pour effectuer la journalisation (à partir du client) lorsqu’un circuit est connecté.

Pour modifier les événements de connexion, inscrivez des rappels pour les modifications de connexion suivantes :

  • Les connexions supprimées utilisent onConnectionDown.
  • Les connexions établies/rétablies utilisent onConnectionUp.

onConnectionDown et onConnectionUp doivent tous deux être spécifiés.

Blazor Web App :

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      reconnectionHandler: {
        onConnectionDown: (options, error) => console.error(error),
        onConnectionUp: () => console.log("Up, up, and away!")
      }
    }
  });
</script>

Blazor Server :

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    reconnectionHandler: {
      onConnectionDown: (options, error) => console.error(error),
      onConnectionUp: () => console.log("Up, up, and away!")
    }
  });
</script>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de script Blazor et le nom de fichier. Pour connaître l’emplacement du script et le chemin d’accès à utiliser, consultez Structure de projet Blazor ASP.NET Core.

Actualiser automatiquement la page en cas d’échec de reconnexion côté serveur

Le comportement de reconnexion par défaut nécessite que l’utilisateur effectue une action manuelle pour actualiser la page après l’échec de la reconnexion. Toutefois, un gestionnaire de reconnexion personnalisé peut être utilisé pour actualiser automatiquement la page :

App.razor :

Pages/_Host.cshtml :

<div id="reconnect-modal" style="display: none;"></div>
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script src="boot.js"></script>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de script Blazor et le nom de fichier. Pour connaître l’emplacement du script et le chemin d’accès à utiliser, consultez Structure de projet Blazor ASP.NET Core.

Créez le fichier wwwroot/boot.js suivant.

Blazor Web App :

(() => {
  const maximumRetryCount = 3;
  const retryIntervalMilliseconds = 5000;
  const reconnectModal = document.getElementById('reconnect-modal');

  const startReconnectionProcess = () => {
    reconnectModal.style.display = 'block';

    let isCanceled = false;

    (async () => {
      for (let i = 0; i < maximumRetryCount; i++) {
        reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of ${maximumRetryCount}`;

        await new Promise(resolve => setTimeout(resolve, retryIntervalMilliseconds));

        if (isCanceled) {
          return;
        }

        try {
          const result = await Blazor.reconnect();
          if (!result) {
            // The server was reached, but the connection was rejected; reload the page.
            location.reload();
            return;
          }

          // Successfully reconnected to the server.
          return;
        } catch {
          // Didn't reach the server; try again.
        }
      }

      // Retried too many times; reload the page.
      location.reload();
    })();

    return {
      cancel: () => {
        isCanceled = true;
        reconnectModal.style.display = 'none';
      },
    };
  };

  let currentReconnectionProcess = null;

  Blazor.start({
    circuit: {
      reconnectionHandler: {
        onConnectionDown: () => currentReconnectionProcess ??= startReconnectionProcess(),
        onConnectionUp: () => {
          currentReconnectionProcess?.cancel();
          currentReconnectionProcess = null;
        }
      }
    }
  });
})();

Blazor Server :

(() => {
  const maximumRetryCount = 3;
  const retryIntervalMilliseconds = 5000;
  const reconnectModal = document.getElementById('reconnect-modal');

  const startReconnectionProcess = () => {
    reconnectModal.style.display = 'block';

    let isCanceled = false;

    (async () => {
      for (let i = 0; i < maximumRetryCount; i++) {
        reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of ${maximumRetryCount}`;

        await new Promise(resolve => setTimeout(resolve, retryIntervalMilliseconds));

        if (isCanceled) {
          return;
        }

        try {
          const result = await Blazor.reconnect();
          if (!result) {
            // The server was reached, but the connection was rejected; reload the page.
            location.reload();
            return;
          }

          // Successfully reconnected to the server.
          return;
        } catch {
          // Didn't reach the server; try again.
        }
      }

      // Retried too many times; reload the page.
      location.reload();
    })();

    return {
      cancel: () => {
        isCanceled = true;
        reconnectModal.style.display = 'none';
      },
    };
  };

  let currentReconnectionProcess = null;

  Blazor.start({
    reconnectionHandler: {
      onConnectionDown: () => currentReconnectionProcess ??= startReconnectionProcess(),
      onConnectionUp: () => {
        currentReconnectionProcess?.cancel();
        currentReconnectionProcess = null;
      }
    }
  });
})();

Pour plus d’informations sur le démarrage de Blazor, consultez Démarrage d’ASP.NET Core Blazor.

Ajuster le nombre et l’intervalle des nouvelles tentatives de reconnexion côté serveur

Pour ajuster le nombre et l’intervalle des nouvelles tentatives de reconnexion, définissez le nombre de nouvelles tentatives (maxRetries) et la période autorisée en millisecondes pour chaque nouvelle tentative (retryIntervalMilliseconds).

Blazor Web App :

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      reconnectionOptions: {
        maxRetries: 3,
        retryIntervalMilliseconds: 2000
      }
    }
  });
</script>

Blazor Server :

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    reconnectionOptions: {
      maxRetries: 3,
      retryIntervalMilliseconds: 2000
    }
  });
</script>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de script Blazor et le nom de fichier. Pour connaître l’emplacement du script et le chemin d’accès à utiliser, consultez Structure de projet Blazor ASP.NET Core.

Lorsque l’utilisateur retourne à une application avec un circuit déconnecté, la reconnexion est tentée immédiatement plutôt que d’attendre pendant la durée du prochain intervalle de reconnexion. Ce comportement s’efforce de reprendre la connexion aussi rapidement que possible pour l’utilisateur.

Le minutage de reconnexion par défaut utilise une stratégie de backoff calculée. Les premières tentatives de reconnexion se produisent rapidement avant l’introduction de délais calculés entre les tentatives. La logique par défaut pour calculer l’intervalle avant nouvelle tentative est un détail d’implémentation susceptible de changer sans préavis, mais vous trouverez la logique par défaut utilisée par l’infrastructure Blazor dans la fonction computeDefaultRetryInterval (source de référence).

Remarque

Les liens de documentation vers la source de référence .NET chargent généralement la branche par défaut du référentiel, qui représente le développement actuel pour la prochaine version de .NET. Pour sélectionner une balise pour une version spécifique, utilisez la liste déroulante Échanger les branches ou les balises. Pour plus d’informations, consultez Comment sélectionner une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Personnalisez le comportement d’intervalle avant nouvelle tentative en spécifiant une fonction de calcul d’intervalle avant nouvelle tentative. Dans l’exemple de backoff exponentiel suivant, le nombre de tentatives de reconnexion précédent est multipliée par 1 000 ms pour calculer l’intervalle avant nouvelle tentative. Lorsque le nombre de tentatives précédentes pour la reconnexion (previousAttempts) est supérieur à la limite maximale de nouvelle tentative (maxRetries), null est affecté à l’intervalle avant nouvelle tentative (retryIntervalMilliseconds) pour arrêter davantage de tentatives de reconnexion :

Blazor.start({
  circuit: {
    reconnectionOptions: {
      retryIntervalMilliseconds: (previousAttempts, maxRetries) => 
        previousAttempts >= maxRetries ? null : previousAttempts * 1000
    },
  },
});

Une solution de rechange consiste à spécifier la séquence exacte des intervalles avant nouvelles tentative. Après le dernier intervalle avant nouvelle tentative spécifié, les nouvelles tentatives s’arrêtent car la fonction retryIntervalMilliseconds renvoie undefined :

Blazor.start({
  circuit: {
    reconnectionOptions: {
      retryIntervalMilliseconds: 
        Array.prototype.at.bind([0, 1000, 2000, 5000, 10000, 15000, 30000]),
    },
  },
});

Pour plus d’informations sur le démarrage de Blazor, consultez Démarrage d’ASP.NET Core Blazor.

Contrôler quand l’interface utilisateur de reconnexion s’affiche

Contrôler quand l’interface utilisateur de reconnexion s’affiche peut être utile dans les situations suivantes :

  • Une application déployée affiche fréquemment l’interface utilisateur de reconnexion à cause des pauses de ping provoquées par une latence réseau interne ou Internet, et vous souhaitez augmenter le délai.
  • Une application doit signaler aux utilisateurs que la connexion a été supprimée plus tôt et que vous souhaitez raccourcir le délai.

Le délai de l’interface utilisateur de reconnexion est influencé par l’ajustement de l’intervalle Keep-Alive et des délais d’expiration sur le client. L’interface utilisateur de reconnexion s’affiche lorsque le délai d’expiration du serveur est atteint sur le client (withServerTimeout, section configuration du client ). Toutefois, la modification de la valeur de withServerTimeout nécessite des modifications apportées à d’autres paramètres Keep-Alive, de délai d’attente et d’établissement d’une liaison décrits dans les instructions suivantes.

En guise de recommandations générales pour les conseils suivants :

  • L’intervalle Keep-Alive doit correspondre entre les configurations client et serveur.
  • Les délais d’expiration doivent être au moins le double de la valeur affectée à l’intervalle Keep-Alive.

Configurer le serveur

Spécifiez les paramètres suivants :

  • ClientTimeoutInterval (valeur par défaut : 30 secondes) : les clients de fenêtre de temps doivent envoyer un message avant que le serveur ferme la connexion.
  • HandshakeTimeout (valeur par défaut : 15 secondes) : intervalle utilisé par le serveur pour l’expiration des demandes d’établissement de liaison entrantes par les clients.
  • KeepAliveInterval (valeur par défaut : 15 secondes) : intervalle utilisé par le serveur pour envoyer des pings Keep Alive aux clients connectés. Notez qu’il existe également un paramètre d’intervalle Keep-Alive sur le client, qui doit correspondre à la valeur du serveur.

Les intervalles ClientTimeoutInterval et HandshakeTimeout peuvent être augmentés, et KeepAliveInterval peut rester le même. L’important est que si vous modifiez les valeurs. Assurez-vous que les délais d’expiration sont au moins deux fois la valeur de l’intervalle Keep-Alive et que celui-ci correspond entre le serveur et le client. Pour plus d’informations, consultez la section Configurer les délais d’expiration SignalR et Keep-Alive sur le client.

Dans l’exemple suivant :

  • L’intervalle ClientTimeoutInterval est augmenté à 60 secondes (valeur par défaut : 30 secondes).
  • Le délai HandshakeTimeout est augmenté à 30 secondes (valeur par défaut : 15 secondes).
  • L’intervalle KeepAliveInterval n’est pas défini dans le code du développeur et utilise sa valeur par défaut de 15 secondes. La diminution de la valeur de l’intervalle Keep-Alive augmente la fréquence des pings de communication, ce qui augmente la charge sur l’application, le serveur et le réseau. Veillez à éviter d’introduire de mauvaises performances lors de l’abaissement de l’intervalle Keep-Alive.

Blazor Web App (.NET 8 ou version ultérieure) dans le fichier Program du projet de serveur :

builder.Services.AddRazorComponents().AddInteractiveServerComponents()
    .AddHubOptions(options =>
{
    options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
    options.HandshakeTimeout = TimeSpan.FromSeconds(30);
});

Blazor Server dans le fichierProgram :

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options =>
    {
        options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
        options.HandshakeTimeout = TimeSpan.FromSeconds(30);
    });

Pour plus d’informations, consultez la section Options de gestionnaire de circuit côté serveur.

Configuration de client

Spécifiez les paramètres suivants :

  • withServerTimeout (valeur par défaut : 30 secondes) : configure le délai d’expiration du serveur, indiqué en millisecondes, pour la connexion hub du circuit.
  • withKeepAliveInterval (valeur par défaut : 15 secondes) : intervalle, spécifié en millisecondes, auquel la connexion envoie des messages Keep-Alive.

Le délai d’expiration du serveur peut être augmenté et l’intervalle Keep-Alive peut rester le même. L’important est que si vous modifiez les valeurs. Assurez-vous que le délai d’expiration du serveur est au moins deux fois la valeur de l’intervalle Keep-Alive et que celle-ci correspond entre le serveur et le client. Pour plus d’informations, consultez la section Configurer les délais d’expiration SignalR et Keep-Alive sur le client.

Dans l’exemple de configuration de démarrage suivant (emplacement du script Blazor), une valeur personnalisée de 60 secondes est utilisée pour le délai d’expiration du serveur. L’intervalle Keep-Alive (withKeepAliveInterval) n’est pas défini et utilise sa valeur par défaut de 15 secondes.

Blazor Web App :

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      configureSignalR: function (builder) {
        builder.withServerTimeout(60000);
      }
    }
  });
</script>

Blazor Server :

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      builder.withServerTimeout(60000);
    }
  });
</script>

Lorsque vous créez une connexion hub dans un composant, définissez le délai d’expiration du serveur (WithServerTimeout, par défaut : 30 secondes) au niveau de HubConnectionBuilder. Définissez HandshakeTimeout (valeur par défaut : 15 secondes) au niveau de la HubConnection générée. Vérifiez que les délais d’expiration sont au moins deux fois l’intervalle Keep-Alive (WithKeepAliveInterval/KeepAliveInterval) et que la valeur Keep-Alive correspond entre le serveur et le client.

L’exemple suivant est basé sur le composant Index dans le didacticiel SignalR avec Blazor. Le délai d’expiration du serveur est augmenté à 60 secondes, et le délai d’établissement d’une liaison est augmenté à 30 secondes. L’intervalle Keep-Alive n’est pas défini et utilise sa valeur par défaut de 15 secondes.

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .WithServerTimeout(TimeSpan.FromSeconds(60))
        .Build();

    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

Spécifiez les paramètres suivants :

  • serverTimeoutInMilliseconds (valeur par défaut : 30 secondes) : configure le délai d’expiration du serveur, indiqué en millisecondes, pour la connexion hub du circuit.
  • keepAliveIntervalInMilliseconds (valeur par défaut : 15 secondes) : intervalle, spécifié en millisecondes, auquel la connexion envoie des messages Keep-Alive.

Le délai d’expiration du serveur peut être augmenté et l’intervalle Keep-Alive peut rester le même. L’important est que si vous modifiez les valeurs. Assurez-vous que le délai d’expiration du serveur est au moins deux fois la valeur de l’intervalle Keep-Alive et que celle-ci correspond entre le serveur et le client. Pour plus d’informations, consultez la section Configurer les délais d’expiration SignalR et Keep-Alive sur le client.

Dans l’exemple de configuration de démarrage suivant (emplacement du script Blazor), une valeur personnalisée de 60 secondes est utilisée pour le délai d’expiration du serveur. L’intervalle Keep-Alive (keepAliveIntervalInMilliseconds) n’est pas défini et utilise sa valeur par défaut de 15 secondes.

Dans Pages/_Host.cshtml :

<script src="_framework/blazor.server.js" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      let c = builder.build();
      c.serverTimeoutInMilliseconds = 60000;
      builder.build = () => {
        return c;
      };
    }
  });
</script>

Lors de la création d’une connexion hub dans un composant, définissez ServerTimeout (valeur par défaut : 30 secondes) et HandshakeTimeout (valeur par défaut : 15 secondes) sur le HubConnection généré. Vérifiez que les délais d’expiration sont au moins deux fois l’intervalle Keep-Alive. Vérifiez que l’intervalle Keep-Alive correspond entre le serveur et le client.

L’exemple suivant est basé sur le composant Index dans le didacticiel SignalR avec Blazor. Le délai ServerTimeout est augmenté à 60 secondes et le délai HandshakeTimeout est augmenté à 30 secondes. L’intervalle Keep-Alive (KeepAliveInterval) n’est pas défini et utilise sa valeur par défaut de 15 secondes.

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .Build();

    hubConnection.ServerTimeout = TimeSpan.FromSeconds(60);
    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

Déconnecter le circuit Blazor du client

Un circuit Blazor est déconnecté lorsque l’événement de page unload est déclenché. Pour déconnecter le circuit pour d’autres scénarios sur le client, appelez Blazor.disconnect dans le gestionnaire d’événements approprié. Dans l’exemple suivant, le circuit est déconnecté lorsque la page est masquée (événement pagehide) :

window.addEventListener('pagehide', () => {
  Blazor.disconnect();
});

Pour plus d’informations sur le démarrage de Blazor, consultez Démarrage d’ASP.NET Core Blazor.

Gestionnaire de circuit côté serveur

Vous pouvez définir un gestionnaire de circuit, ce qui permet d’exécuter du code sur les modifications apportées à l’état du circuit d’un utilisateur. Un gestionnaire de circuit est implémenté en dérivant de CircuitHandler et en inscrivant la classe dans le conteneur de service de l’application. L’exemple suivant d’un gestionnaire de circuit effectue le suivi des connexions SignalR ouvertes.

TrackingCircuitHandler.cs :

using Microsoft.AspNetCore.Components.Server.Circuits;

public class TrackingCircuitHandler : CircuitHandler
{
    private HashSet<Circuit> circuits = new();

    public override Task OnConnectionUpAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Add(circuit);

        return Task.CompletedTask;
    }

    public override Task OnConnectionDownAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Remove(circuit);

        return Task.CompletedTask;
    }

    public int ConnectedCircuits => circuits.Count;
}

Les gestionnaires de circuit sont inscrits par DI. Les instances délimitées sont créées par instance d’un circuit. À l’aide de TrackingCircuitHandler dans l’exemple précédent, un service singleton est créé, car l’état de tous les circuits doit être suivi.

Dans le fichier Program :

builder.Services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();

Dans Startup.ConfigureServices de Startup.cs :

services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();

Si les méthodes d’un gestionnaire de circuit personnalisé lèvent une exception non prise en charge, l’exception est irrécupérable pour le circuit. Pour tolérer des exceptions dans le code ou les méthodes appelées d’un gestionnaire, encapsulez le code dans une ou plusieurs instructions try-catch avec gestion des erreurs et journalisation.

Lorsqu’un circuit se termine parce qu’un utilisateur s’est déconnecté et que le framework nettoie l’état du circuit, le framework supprime l’étendue de DI du circuit. La suppression de l’étendue supprime tous les services de DI à l’étendue du circuit qui implémentent System.IDisposable. Si un service de DI lève une exception non prise en charge pendant la suppression, le framework journalise l’exception. Pour plus d’informations, consultez Injection de dépendances Blazor ASP.NET Core.

Gestionnaire de circuit côté serveur pour capturer des utilisateurs des services personnalisés

Utilisez un CircuitHandler pour capturer un utilisateur à partir du AuthenticationStateProvider et définissez cet utilisateur dans un service. Pour obtenir des informations supplémentaires et des exemples de code, consultez la section Autres scénarios de sécurité ASP.NET Core Blazor côté serveur.

Fermeture des circuits quand il ne reste plus de composants de serveur interactif

Les composants de serveur interactif gèrent les événements de l’interface utilisateur web en utilisant une connexion en temps réel avec le navigateur, appelée un circuit. Un circuit et son état associé sont créés quand un composant de serveur interactif racine est rendu. Le circuit est fermé quand il ne reste plus de composants de serveur interactif sur la page, ce qui libère des ressources du serveur.

IHttpContextAccessor/HttpContext dans les composants Razor

IHttpContextAccessor doit être évité avec le rendu interactif, car il n’existe pas de HttpContext valide disponible.

IHttpContextAccessor peut être utilisé pour les composants rendus statiquement sur le serveur. Toutefois, nous vous recommandons de l’éviter si possible.

HttpContext peut être utilisé comme paramètre en cascade uniquement dans les composants racines rendus statiquement pour les tâches générales, telles que l’inspection et la modification d’en-têtes ou d’autres propriétés dans le composant App (Components/App.razor). La valeur est toujours null pour le rendu interactif.

[CascadingParameter]
public HttpContext? HttpContext { get; set; }

Pour les scénarios où HttpContext est requis dans les composants interactifs, nous vous recommandons de transmettre les données via l’état du composant persistant à partir du serveur. Pour plus d’informations, consultez Autres scénarios de sécurité ASP.NET Core Blazor côté serveur.

N’utilisez pas IHttpContextAccessor/HttpContext directement ou indirectement dans les composants Razor des applications Blazor côté serveur. Les applications Blazor s’exécutent en dehors du contexte de pipeline ASP.NET Core. Le HttpContext n’est pas garanti d’être disponible dans le IHttpContextAccessor, et HttpContext n’est pas garanti de conserver le contexte qui a démarré l’application Blazor.

L’approche recommandée pour passer l’état de la requête à l’application Blazor consiste à utiliser les paramètres de composant racine pendant le rendu initial de l’application. L’application peut également copier les données dans un service délimité dans l’événement de cycle de vie d’initialisation du composant racine pour une utilisation dans l’application. Pour plus d’informations, consultez Autres scénarios de sécurité ASP.NET Core Blazor côté serveur.

Un aspect essentiel de la sécurité de Blazor côté serveur est que l’utilisateur attaché à un circuit donné peut être mis à jour à un moment donné après l’établissement du circuit Blazor, mais que le IHttpContextAccessor n’est pas mis à jour. Pour plus d’informations sur la résolution de cette situation avec des services personnalisés, consultez Autres scénarios de sécurité ASP.NET Core Blazor côté serveur.

Ressources côté serveur supplémentaires