Interactivité Blazor dans .NET 9 et .NET 10 : un guide complet
Si vous avez créé des applications Web avec Blazor au cours des dernières années, vous savez que le framework a parcouru un long chemin. Ce qui a commencé comme un choix entre Blazor Server et Blazor WebAssembly a évolué vers un modèle de rendu unifié et flexible qui vous permet de choisir la bonne stratégie d’interactivité pour chaque composant de votre application.
Avec .NET 8, nous avons obtenu un changement fondamental : l’introduction des modes de rendu et du rendu statique côté serveur (SSR) par défaut. Désormais, .NET 9 et .NET 10 s’appuient sur cette base avec des améliorations qui rendent l’expérience du développeur plus fluide et celle de l’utilisateur final plus rapide.
Dans cet article, je souhaite vous présenter l’image complète de l’interactivité de Blazor telle qu’elle existe aujourd’hui : comment fonctionnent les modes de rendu, ce que le streaming SSR apporte, comment la navigation améliorée et la gestion des formulaires changent le jeu, et les nouveautés des dernières versions. Si vous planifiez un nouveau projet Blazor ou envisagez une mise à niveau, c’est le guide que j’aurais aimé avoir lorsque j’ai commencé à approfondir tout cela.
Un rapide retour en arrière : comment nous en sommes arrivés là
Avant .NET 8, vous deviez vous engager dans un modèle d’hébergement au niveau du projet. Blazor Server signifiait que tout fonctionnait sur le serveur via une connexion SignalR. Blazor WebAssembly signifiait que tout fonctionnait dans le navigateur. Chacun avait des compromis, et les mélanger était douloureux.
.NET 8 a changé la donne en introduisant un modèle de projet unique – la Blazor Web App – qui unifie les deux modèles. Le concept clé est les modes de rendu : vous décidez par composant comment il doit être rendu et où l’interactivité se produit. La valeur par défaut est devenue SSR statique, ce qui signifie que les composants s’affichent sur le serveur et envoient du HTML brut au navigateur - pas de SignalR, pas de WebAssembly, juste du HTML rapide.
.NET 9 a peaufiné ces concepts, amélioré l’expérience des développeurs et optimisé les performances. .NET 10 va plus loin avec une meilleure gestion des reconnexions, un état persistant des composants dans tous les modes de rendu et des améliorations dans la façon dont le script Blazor lui-même est fourni.
Décomposons tout cela.
Modes de rendu dans .NET 9
Au cœur du Blazor moderne se trouve le concept de modes de rendu. Il existe quatre modes que vous devez connaître :
1. SSR statique (par défaut)
Lorsque vous créez une nouvelle application Web Blazor, les composants s’affichent par défaut de manière statique sur le serveur. Le serveur traite le composant Razor, génère du HTML et l’envoie au navigateur. Il n’y a pas de connexion persistante, pas de runtime WebAssembly - juste une requête/réponse traditionnelle.
C’est parfait pour les pages riches en contenu, les tableaux de bord qui affichent principalement des données ou toute page sur laquelle vous n’avez pas besoin d’interaction en temps réel.
@page "/products"
<h1>Our Products</h1>
@foreach (var product in products)
{
<div class="product-card">
<h3>@product.Name</h3>
<p>@product.Description</p>
<span class="price">@product.Price.ToString("C")</span>
</div>
}
@code {
private List<Product> products = new();
protected override async Task OnInitializedAsync()
{
products = await ProductService.GetAllAsync();
}
}
Ce composant s’affiche sur le serveur, produit du HTML, et c’est tout. Aucune connexion continue. Chargement initial rapide, idéal pour le référencement, utilisation minimale des ressources du serveur.
2. Serveur interactifLorsque vous avez besoin d’interactivité en temps réel (gestion des clics sur les boutons, traitement des entrées utilisateur, mise à jour dynamique de l’interface utilisateur), vous pouvez opter pour le mode Serveur interactif. Cela établit une connexion SignalR entre le navigateur et le serveur, et les mises à jour de l’interface utilisateur s’effectuent via cette connexion.
@page "/counter"
@rendermode InteractiveServer
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
La directive @rendermode InteractiveServer suffit. Le composant s’affiche initialement sur le serveur, puis une connexion SignalR est établie pour gérer les interactions ultérieures. Vos gestionnaires d’événements C# s’exécutent sur le serveur et les différences d’interface utilisateur sont envoyées au navigateur.
Quand l’utiliser : Lorsque vous avez besoin d’interactivité, vos composants doivent accéder aux ressources côté serveur (bases de données, API, systèmes de fichiers) et vous souhaitez un chargement initial rapide sans attendre le téléchargement de WebAssembly.
3. WebAssembly interactif
Si vous souhaitez une interactivité sans maintenir une connexion serveur, Interactive WebAssembly exécute la logique de votre composant directement dans le navigateur à l’aide du runtime .NET WebAssembly.
@page "/search"
@rendermode InteractiveWebAssembly
<h1>Product Search</h1>
<input @bind="searchTerm" @bind:event="oninput" placeholder="Search products..." />
@if (filteredProducts.Any())
{
<ul>
@foreach (var product in filteredProducts)
{
<li>@product.Name — @product.Price.ToString("C")</li>
}
</ul>
}
@code {
private string searchTerm = string.Empty;
private List<Product> allProducts = new();
private IEnumerable<Product> filteredProducts => allProducts
.Where(p => p.Name.Contains(searchTerm, StringComparison.OrdinalIgnoreCase));
protected override async Task OnInitializedAsync()
{
allProducts = await Http.GetFromJsonAsync<List<Product>>("api/products") ?? new();
}
}
Le compromis : il existe un coût de téléchargement initial pour le runtime .NET et vos assemblys. Mais une fois chargé, le composant s’exécute entièrement dans le navigateur, sans allers-retours entre le serveur et les interactions avec l’interface utilisateur.
Quand l’utiliser : Pour les composants hautement interactifs où la latence est importante (pensez : éditeurs de texte enrichi, outils de dessin, filtrage en temps réel), lorsque vous souhaitez réduire la charge du serveur ou lorsque vous créez une application Web progressive (PWA).
4. Automatique interactif
C’est le juste milieu pragmatique et l’une de mes fonctionnalités préférées. Interactive Auto commence par le rendu côté serveur via SignalR pour le premier chargement, puis télécharge silencieusement le runtime WebAssembly en arrière-plan. Lors des visites ultérieures, le composant s’exécute sur WebAssembly.
@page "/dashboard"
@rendermode InteractiveAuto
<h1>Dashboard</h1>
<DashboardWidget Title="Sales" Value="@salesTotal" />
<DashboardWidget Title="Users" Value="@activeUsers" />
<button @onclick="RefreshData">Refresh</button>
@code {
private decimal salesTotal;
private int activeUsers;
protected override async Task OnInitializedAsync()
{
await RefreshData();
}
private async Task RefreshData()
{
var data = await DashboardService.GetSummaryAsync();
salesTotal = data.SalesTotal;
activeUsers = data.ActiveUsers;
}
}
Cela vous offre le meilleur des deux mondes : un premier rendu rapide (pas d’attente pour le téléchargement WASM) et finalement le composant s’exécute côté client. L’utilisateur ne remarque pas la transition.
Quand l’utiliser : Lorsque vous souhaitez à la fois des chargements initiaux rapides et une éventuelle exécution côté client. C’est un excellent choix par défaut pour de nombreux composants interactifs.
Streaming SSR : le meilleur des deux mondes
Le streaming SSR est l’une de ces fonctionnalités qui semblent simples mais qui font une énorme différence dans les performances perçues. Voici l’idée : au lieu d’attendre que toutes vos données soient chargées avant d’envoyer du code HTML, le serveur envoie immédiatement le shell de la page, puis diffuse les mises à jour du contenu à mesure que les données deviennent disponibles.
@page "/reports"
@attribute [StreamRendering]
<h1>Monthly Reports</h1>
@if (reports is null)
{
<div class="loading-spinner">
<p>Loading reports...</p>
</div>
}
else
{
<table class="table">
<thead>
<tr>
<th>Month</th>
<th>Revenue</th>
<th>Growth</th>
</tr>
</thead>
<tbody>
@foreach (var report in reports)
{
<tr>
<td>@report.Month</td>
<td>@report.Revenue.ToString("C")</td>
<td class="@(report.Growth >= 0 ? "text-success" : "text-danger")">
@report.Growth.ToString("P1")
</td>
</tr>
}
</tbody>
</table>
}
@code {
private List<MonthlyReport>? reports;
protected override async Task OnInitializedAsync()
{
// This might take a couple of seconds
reports = await ReportService.GetMonthlyReportsAsync();
}
}
Avec l’attribut [StreamRendering], voici ce qui se passe :
- Le serveur envoie immédiatement le code HTML avec la flèche de chargement.
OnInitializedAsyncexécute et récupère les données.- Lorsque les données arrivent, le serveur diffuse le code HTML mis à jour (le tableau) vers le navigateur.
- Le navigateur corrige le DOM sans recharger la page complète.
L’utilisateur voit la page instantanément avec un indicateur de chargement, puis le contenu se remplit. Aucun framework JavaScript n’est nécessaire. Aucune connexion WebSocket. Juste une utilisation intelligente du streaming HTTP.Quand l’utiliser : Toute page SSR statique qui récupère des données pendant le rendu. Pages de liste de produits, tableaux de bord, rapports — partout où le chargement initial des données peut prendre plus de quelques centaines de millisecondes.
Quand NE PAS l’utiliser : Si les données se chargent extrêmement rapidement (moins de 100 ms), la surcharge de streaming n’en vaut pas la peine. De plus, le streaming SSR ne vous offre pas une interactivité continue – pour cela, vous avez toujours besoin d’un mode de rendu interactif.
Amélioration de la navigation et de la gestion des formulaires
L’une des améliorations subtiles mais puissantes du Blazor moderne est la navigation améliorée. Par défaut, Blazor intercepte les clics sur les liens internes et les soumissions de formulaires, récupérant le contenu de la nouvelle page via fetch et corrigeant le DOM plutôt que d’effectuer une navigation complète dans le navigateur.
Cela signifie que la navigation entre les pages SSR statiques ressemble à un SPA : pas de flash de page entière, la position de défilement peut être préservée et l’expérience est douce et soyeuse.
Comment ça marche
Lorsque le script Blazor (blazor.web.js) est chargé, il intercepte automatiquement les clics sur les liens internes. Au lieu d’une navigation traditionnelle dans un navigateur, il :
- Effectue une requête
fetchà l’URL cible. - Reçoit la réponse HTML.
- Fusionne le nouveau contenu dans le DOM existant.
- Met à jour l’URL et l’historique du navigateur.
Vous n’avez rien à faire pour l’activer – c’est activé par défaut. Mais vous pouvez le contrôler :
<!-- Disable enhanced navigation for a specific link -->
<a href="/legacy-page" data-enhance-nav="false">Legacy Page</a>
<!-- Force a full page reload for external-like behavior -->
<a href="/downloads/report.pdf" data-enhance-nav="false">Download Report</a>
Gestion améliorée des formulaires
Les formulaires bénéficient du même traitement. Lorsque vous utilisez EditForm ou l’élément standard <form> avec la gestion des formulaires de Blazor, les soumissions sont interceptées et traitées via fetch :
@page "/contact"
<EditForm Model="contactModel" OnValidSubmit="HandleSubmit" FormName="contact" Enhance>
<DataAnnotationsValidator />
<div class="mb-3">
<label for="name">Name</label>
<InputText id="name" @bind-Value="contactModel.Name" class="form-control" />
<ValidationMessage For="() => contactModel.Name" />
</div>
<div class="mb-3">
<label for="email">Email</label>
<InputText id="email" @bind-Value="contactModel.Email" class="form-control" />
<ValidationMessage For="() => contactModel.Email" />
</div>
<div class="mb-3">
<label for="message">Message</label>
<InputTextArea id="message" @bind-Value="contactModel.Message" class="form-control" />
<ValidationMessage For="() => contactModel.Message" />
</div>
<button type="submit" class="btn btn-primary">Send</button>
</EditForm>
@code {
[SupplyParameterFromForm]
private ContactModel contactModel { get; set; } = new();
private async Task HandleSubmit()
{
await ContactService.SubmitAsync(contactModel);
contactModel = new ContactModel();
}
}
L’attribut Enhance sur EditForm indique à Blazor d’intercepter la soumission du formulaire. Le formulaire est publié sur le serveur, le serveur le traite et restitue la page, et le HTML mis à jour est renvoyé, le tout sans navigation sur une page complète. Cela semble interactif, mais c’est entièrement rendu par le serveur.
Notez l’attribut [SupplyParameterFromForm] : c’est ainsi que Blazor lie les données de formulaire publiées à votre modèle dans des scénarios SSR statiques. C’est le pont entre les poteaux de formulaire traditionnels et le modèle de composants de Blazor.
Interactivité par page et par composant
L’un des aspects les plus puissants du nouveau modèle est que vous pouvez mélanger les modes de rendu au sein d’une seule application. Votre page de liste de produits peut être un SSR statique, votre panier peut être un serveur interactif et votre configurateur de produit peut être un WebAssembly interactif, le tout dans la même application.
Définition des modes de rendu au niveau des composants
Vous pouvez définir le mode de rendu directement sur un composant à l’aide de la directive @rendermode :
@* This component is interactive via Server *@
@rendermode InteractiveServer
<h3>Live Chat</h3>
<!-- chat UI here -->
Ou vous pouvez le définir lors de l’utilisation d’un composant d’un parent :
@page "/product/{Id:int}"
<h1>@product?.Name</h1>
<p>@product?.Description</p>
<!-- This child component gets its own interactive render mode -->
<ProductConfigurator Product="product" @rendermode="InteractiveWebAssembly" />
<!-- This stays static -->
<ProductReviews ProductId="Id" />
@code {
[Parameter] public int Id { get; set; }
private Product? product;
protected override async Task OnInitializedAsync()
{
product = await ProductService.GetByIdAsync(Id);
}
}
Dans cet exemple, la page elle-même est un SSR statique. Le composant ProductConfigurator s’exécute sur WebAssembly pour une riche interactivité côté client. Le composant ProductReviews reste statique car il affiche simplement des données.
Définition des modes de rendu globalement
Si vous souhaitez que toutes les pages soient interactives par défaut, vous pouvez définir le mode de rendu au niveau racine dans App.razor :
<Routes @rendermode="InteractiveServer" />
```Cela rend chaque page interactive via le rendu du serveur. Les composants individuels peuvent toujours remplacer cela si nécessaire.
### Règles importantes à retenir
Il y a quelques contraintes à garder à l'esprit lors du mélange des modes de rendu :
- **Un composant enfant ne peut pas avoir un mode de rendu "plus interactif" que son parent.** Si un parent est statique, les enfants peuvent être statiques ou interactifs. Mais si un parent est Interactive Server, un enfant ne peut pas être Interactive WebAssembly (il devra être Server ou Auto).
- **Les composants interactifs ne peuvent pas utiliser directement les services étendus des parents statiques.** Si un composant s'exécute sur WebAssembly, il ne peut pas accéder directement aux instances `DbContext` côté serveur — vous aurez besoin d'une couche API.
- **L'état n'est pas automatiquement transféré entre les modes de rendu.** Si un composant effectue un pré-rendu sur le serveur puis passe à WebAssembly, vous devez gérer explicitement la persistance de l'état - plus d'informations à ce sujet dans la section .NET 10.
## Quoi de neuf dans .NET 10
.NET 10 poursuit l'approche d'amélioration incrémentielle, en se concentrant sur la fiabilité et l'expérience des développeurs. Voici les points forts de l’interactivité de Blazor :
### Expérience de reconnexion améliorée
Si vous avez utilisé le mode Serveur interactif en production, vous avez probablement rencontré la superposition de reconnexion : ce moment où la connexion SignalR est interrompue et l'utilisateur voit un message "Reconnexion...". Dans .NET 10, cette expérience s’améliore considérablement.
La logique de reconnexion est désormais plus intelligente en matière de nouvelle tentative. Au lieu d'un intervalle de nouvelle tentative fixe, il utilise une stratégie d'attente qui s'adapte à la situation. L'interface utilisateur lors de la reconnexion est également plus raffinée et vous disposez de meilleurs crochets pour personnaliser l'expérience :
```razor
<!-- In your App.razor or layout -->
<div id="components-reconnect-modal">
<div class="reconnect-visible">
<p>Connection lost. Attempting to reconnect...</p>
<div class="spinner"></div>
</div>
<div class="reconnect-failed">
<p>Could not reconnect to the server.</p>
<button onclick="location.reload()">Reload</button>
</div>
<div class="reconnect-rejected">
<p>Your session has expired.</p>
<a href="/">Return to Home</a>
</div>
</div>
Le framework tente désormais également de se reconnecter de manière plus agressive en cas de pannes transitoires et peut restaurer l’état du circuit de manière plus fiable. Cela signifie moins de moments « veuillez recharger la page » pour vos utilisateurs.
État persistant des composants dans les modes de rendu
C’est un gros problème. Dans .NET 9, lorsqu’un composant effectue un pré-rendu sur le serveur puis passe à WebAssembly (ou passe d’un mode de rendu à l’autre), l’état du composant est perdu. Le composant se réinitialise efficacement, ce qui peut entraîner des récupérations de données en double et un scintillement de l’interface utilisateur.
.NET 10 améliore le service PersistentComponentState pour fonctionner de manière plus transparente lors des transitions en mode rendu :
@page "/weather"
@rendermode InteractiveWebAssembly
@inject PersistentComponentState ApplicationState
<h1>Weather Forecast</h1>
@if (forecasts is null)
{
<p>Loading...</p>
}
else
{
@foreach (var forecast in forecasts)
{
<div class="forecast-card">
<h4>@forecast.Date.ToShortDateString()</h4>
<p>@forecast.Summary — @forecast.TemperatureC°C</p>
</div>
}
}
@code {
private List<WeatherForecast>? forecasts;
private PersistingComponentStateSubscription persistingSubscription;
protected override async Task OnInitializedAsync()
{
persistingSubscription = ApplicationState.RegisterOnPersisting(PersistData);
if (!ApplicationState.TryTakeFromJson<List<WeatherForecast>>(
"weather-forecasts", out var restored))
{
// Data wasn't persisted from prerendering — fetch it
forecasts = await Http.GetFromJsonAsync<List<WeatherForecast>>("api/weather");
}
else
{
forecasts = restored;
}
}
private Task PersistData()
{
ApplicationState.PersistAsJson("weather-forecasts", forecasts);
return Task.CompletedTask;
}
public void Dispose()
{
persistingSubscription.Dispose();
}
}
Avec les améliorations apportées à .NET 10, ce modèle fonctionne de manière plus fiable. L’état sérialisé lors du prérendu est correctement disponible lorsque le composant s’initialise dans son mode interactif, évitant ainsi les doubles récupérations de données et le bref scintillement que les utilisateurs pourraient voir.
Blazor Script en tant qu’actif Web statique
Dans les versions précédentes, le fichier JavaScript Blazor (blazor.web.js) était servi à partir du point de terminaison interne du framework. Dans .NET 10, il est fourni sous forme de ressource Web statique. Cela peut sembler un petit changement, mais il présente des avantages pratiques :- Meilleure mise en cache : Les ressources Web statiques obtiennent des en-têtes de cache appropriés et des URL avec empreintes digitales, afin que les navigateurs les mettent en cache plus efficacement.
- Compatible avec les CDN : Puisqu’il s’agit d’un fichier statique standard, les CDN peuvent le mettre en cache et le servir à partir d’emplacements périphériques.
- Compression : La compression statique des ressources Web s’applique automatiquement, réduisant ainsi la taille du script sur le fil.
Vous n’avez pas besoin de modifier la façon dont vous le référencez : le framework gère automatiquement le chemin mis à jour.
Bonnes pratiques : choisir le bon mode de rendu
Après avoir travaillé avec tous ces modes sur plusieurs projets, voici mon cadre de décision :
Commencez avec le SSR statique
Faites du SSR statique votre valeur par défaut. La plupart des pages de la plupart des applications concernent principalement l’affichage de données. Pages de produits, articles de blog, profils d’utilisateurs, pages de paramètres : ils n’ont pas besoin d’interactivité en temps réel. Le SSR statique vous offre les meilleures performances, la plus faible utilisation des ressources et le modèle mental le plus simple.
Ajoutez de l’interactivité uniquement là où cela est nécessaire
Identifiez les composants spécifiques qui doivent répondre aux interactions des utilisateurs en temps réel. Un bouton « J’aime », un widget de chat, une interface glisser-déposer : tout cela a besoin d’interactivité. Mais la page qui les entoure ne le fait probablement pas.
Utilisez Interactive Auto comme mode interactif incontournable
Lorsque vous avez besoin d’interactivité, Interactive Auto est souvent le meilleur choix par défaut. Il vous offre des chargements initiaux rapides (rendu serveur) avec une éventuelle exécution côté client (WebAssembly). L’utilisateur obtient le meilleur des deux mondes et vous écrivez votre code une seule fois.
Réserver le serveur interactif pour des cas spécifiques
Utilisez Interactive Server dans les cas suivants :
- Votre composant a besoin d’un accès direct aux ressources du serveur (bases de données, système de fichiers, API internes).
- La taille du téléchargement de WebAssembly est un problème et vous ne pouvez pas utiliser Auto.
- Vous avez besoin que le composant s’exécute toujours sur le serveur pour des raisons de sécurité (par exemple, traitement de données sensibles).
Utilisez généreusement le Streaming SSR
Si vos pages SSR statiques récupèrent des données, ajoutez [StreamRendering]. Le coût est minime et l’amélioration perçue des performances est significative. Les utilisateurs voient le contenu apparaître progressivement plutôt que de regarder une page blanche.
Gérez les transitions d’état avec soin
Si vous utilisez Interactive Auto ou mélangez le pré-rendu avec WebAssembly, utilisez toujours PersistentComponentState pour éviter les récupérations de données en double. Vos utilisateurs vous remercieront pour l’absence de contenu scintillant.
Gardez l’arborescence des composants à l’esprit
N’oubliez pas les règles de hiérarchie du mode de rendu. Planifiez votre arborescence de composants de manière à ce que les limites interactives aient un sens. Un modèle courant consiste à avoir une disposition statique avec des « îlots » interactifs intégrés là où cela est nécessaire :
@* Layout: Static SSR *@
<header>
<NavMenu />
<UserMenu @rendermode="InteractiveServer" /> @* Needs real-time auth state *@
</header>
<main>
@Body
</main>
<footer>
<ChatWidget @rendermode="InteractiveAuto" /> @* Rich interactivity *@
</footer>
Conclusion
Le modèle d’interactivité de Blazor dans .NET 9 et .NET 10 représente une approche mature et bien pensée pour créer des applications Web. La possibilité de choisir les modes de rendu par composant, la navigation améliorée et transparente, le streaming SSR et les améliorations continues en matière de reconnexion et de gestion de l’état en font un choix incontournable pour un large éventail d’applications.L’idée clé est que l’interactivité est un spectre et non un choix binaire. La plupart de votre application peut être statique. Certaines parties nécessitent une interactivité pilotée par le serveur. Quelques-uns pourraient bénéficier d’une exécution dans le navigateur. Blazor vous permet désormais de faire ce choix avec la granularité la plus fine – le composant individuel – sans combattre le framework.
Si vous démarrez un nouveau projet, mon conseil est simple : créez une application Web Blazor, commencez par un SSR statique, ajoutez [StreamRendering] aux pages riches en données et promouvez les composants individuels vers des modes interactifs uniquement lorsque vous en avez besoin. Vous obtiendrez une application rapide, efficace et bien évolutive.
Bon codage, et comme toujours, n’hésitez pas à nous contacter si vous avez des questions !