Cycle de vie des composants Razor ASP.NET Core
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 le cycle de vie des composants Razor ASP.NET Core et comment utiliser des événements de cycle de vie.
Événements de cycle de vie
Le composant Razor traite les événements de cycle de vie des composants Razor dans un ensemble de méthodes de cycle de vie synchrones et asynchrones. Les méthodes de cycle de vie peuvent être remplacées pour effectuer des opérations supplémentaires dans les composants lors de l’initialisation et du rendu des composants.
Cet article simplifie le traitement des évènements du cycle de vie des composants afin de clarifier la logique d’infrastructure complexe, sans couvrir toutes les modifications apportées au cours des années. Vous devrez peut-être accéder à la source de référence ComponentBase
pour intégrer le traitement des événements personnalisés avec le traitement des événements de cycle de vie Blazor. Les commentaires de code dans la source de référence incluent des remarques supplémentaires sur le traitement des événements de cycle de vie qui n’apparaissent pas dans cet article ou dans la documentation de l’API.
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 diagrammes simplifiés suivants illustrent le traitement des événements de cycle de vie des composants Razor. Les méthodes C# associées aux événements de cycle de vie sont définies avec des exemples dans les sections suivantes de cet article.
Événements de cycle de vie des composants :
- Si le composant effectue un rendu pour la première fois sur une requête :
- Créez l’instance du composant.
- Effectuez l’injection de propriétés.
- Appelez
OnInitialized{Async}
. Si une Task incomplète est retournée, la Task est attendue, puis le composant est renvoyé. La méthode synchrone est appelée avant la méthode asynchrone.
- Appelez
OnParametersSet{Async}
. Si une Task incomplète est retournée, la Task est attendue, puis le composant est renvoyé. La méthode synchrone est appelée avant la méthode asynchrone. - Effectuez le rendu de tous les travaux synchrones et terminez les Tasks.
Remarque
Les actions asynchrones effectuées dans les événements du cycle de vie peuvent ne pas être terminées avant qu’un composant ne soit rendu. Pour plus d’informations, consultez la section Gérer les actions asynchrones incomplètes au rendu plus loin dans cet article.
Un composant parent effectue le rendu avant ses composants enfants, car le rendu est ce qui détermine les enfants présents. Si l’initialisation du composant parent synchrone est utilisée, l’initialisation parente est garantie pour se terminer en premier. Si l’initialisation asynchrone du composant parent est utilisée, l’ordre d’achèvement de l’initialisation des composants parent et enfant ne peut pas être déterminé, car il dépend du code d’initialisation en cours d’exécution.
Traitement d’un événement DOM :
- Le gestionnaire d’événements est exécuté.
- Si une Task incomplète est retournée, la Task est attendue, puis le composant est renvoyé.
- Effectuez le rendu de tous les travaux synchrones et terminez les Tasks.
Cycle de vie du Render
:
- Évitez les opérations de rendu supplémentaires sur le composant lorsque les deux conditions suivantes sont remplies :
- Ce n’est pas le premier rendu.
ShouldRender
retournefalse
.
- Générez l’arborescence de rendu diff (différence) et affichez le composant.
- Attendez la mise à jour du DOM.
- Appelez
OnAfterRender{Async}
. La méthode synchrone est appelée avant la méthode asynchrone.
Les appels de développeur à StateHasChanged
entraînent un nouveau rendu. Pour plus d’informations, consultez le rendu de composants Razor ASP.NET Core.
Quand les paramètres sont définis (SetParametersAsync
)
SetParametersAsync définit les paramètres fournis par le parent du composant dans l’arborescence de rendu ou à partir des paramètres de routage.
Le paramètre ParameterView de la méthode contient l’ensemble de valeurs de paramètre de composant pour le composant chaque fois que SetParametersAsync est appelée. En substituant la méthode SetParametersAsync, le code du développeur peut interagir directement avec les paramètres de ParameterView.
L’implémentation par défaut de SetParametersAsync définit la valeur de chaque propriété avec l’attribut [Parameter]
ou [CascadingParameter]
qui a une valeur correspondante dans le ParameterView. Les paramètres qui n’ont pas de valeur correspondante dans ParameterView restent inchangés.
En règle générale, votre code doit appeler la méthode de classe de base (await base.SetParametersAsync(parameters);
) en cas de substitution de SetParametersAsync. Dans les scénarios avancés, le code du développeur peut interpréter les valeurs des paramètres entrants de toute manière requise en n’appelant pas la méthode de classe de base. Par exemple, il n’est pas nécessaire d’affecter les paramètres entrants aux propriétés de la classe. Toutefois, vous devez faire référence à la source de référence ComponentBase
lors de la structuration de votre code sans appeler la méthode de classe de base, car elle appelle d’autres méthodes de cycle de vie et déclenche le rendu de manière complexe.
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).
Si vous souhaitez vous appuyer sur la logique d’initialisation et de rendu de ComponentBase.SetParametersAsync, mais ne pas traiter les paramètres entrants, vous avez la possibilité de transmettre un ParameterView vide à la méthode de classe de base :
await base.SetParametersAsync(ParameterView.Empty);
Si des gestionnaires d’événements sont fournis dans le code du développeur, décochez-les lors de la suppression. Pour plus d’informations, consultez la section Suppression des composants avec IDisposable
etIAsyncDisposable
.
Dans l’exemple suivant, ParameterView.TryGetValue affecte la valeur du paramètre Param
à value
si l’analyse d’un paramètre de route pour Param
est réussie. Lorsque value
n’est pas null
, la valeur est affichée par le composant.
Bien que la correspondance des paramètres de route ne respecte pas la casse, TryGetValue ne correspond qu’aux noms de paramètres respectant la casse dans le modèle d’itinéraire. L’exemple suivant nécessite l’utilisation de dans le modèle d’itinéraire /{Param?}
pour obtenir la valeur avec TryGetValue, et non /{param?}
. Si /{param?}
est utilisé dans ce scénario, TryGetValue retourne false
et message
n’est défini sur aucune des message
chaînes.
SetParamsAsync.razor
:
@page "/set-params-async/{Param?}"
<PageTitle>Set Parameters Async</PageTitle>
<h1>Set Parameters Async Example</h1>
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string? Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async/{Param?}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string? Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async/{Param?}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string? Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async/{Param?}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async"
@page "/set-params-async/{Param}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
Initialisation des composants (OnInitialized{Async}
)
OnInitialized et OnInitializedAsync sont utilisés exclusivement pour initialiser un composant pendant toute la durée de vie de l’instance du composant. Les valeurs de paramètre et les modifications de celles-ci ne doivent pas affecter l’initialisation effectuée dans ces méthodes. Par exemple, le chargement d’options statiques dans une liste déroulante qui ne change pas pour la durée de vie du composant et qui ne dépend pas des valeurs de paramètre est effectué dans l’une de ces méthodes de cycle de vie. Si les valeurs de paramètre ou les modifications apportées à celles-ci affectent l’état du composant, utilisez OnParametersSet{Async}
à la place.
Ces méthodes sont appelées lorsque le composant est initialisé après avoir reçu ses paramètres initiaux dans SetParametersAsync. La méthode synchrone est appelée avant la méthode asynchrone.
Si l’initialisation de composant parent synchrone est utilisée, l’initialisation parente est garantie avant l’initialisation du composant enfant. Si l’initialisation asynchrone du composant parent est utilisée, l’ordre d’achèvement de l’initialisation des composants parent et enfant ne peut pas être déterminé, car il dépend du code d’initialisation en cours d’exécution.
Pour une opération synchrone, remplacez OnInitialized :
OnInit.razor
:
@page "/on-init"
<PageTitle>On Initialized</PageTitle>
<h1>On Initialized Example</h1>
<p>@message</p>
@code {
private string? message;
protected override void OnInitialized() =>
message = $"Initialized at {DateTime.Now}";
}
@page "/on-init"
<p>@message</p>
@code {
private string? message;
protected override void OnInitialized()
{
message = $"Initialized at {DateTime.Now}";
}
}
@page "/on-init"
<p>@message</p>
@code {
private string? message;
protected override void OnInitialized()
{
message = $"Initialized at {DateTime.Now}";
}
}
@page "/on-init"
<p>@message</p>
@code {
private string message;
protected override void OnInitialized()
{
message = $"Initialized at {DateTime.Now}";
}
}
@page "/on-init"
<p>@message</p>
@code {
private string message;
protected override void OnInitialized()
{
message = $"Initialized at {DateTime.Now}";
}
}
Pour effectuer une opération asynchrone, remplacez OnInitializedAsync et utilisez l’opérateur await
:
protected override async Task OnInitializedAsync()
{
await ...
}
Si une classe de base personnalisée est utilisée avec une logique d’initialisation personnalisée, appelez OnInitializedAsync sur la classe de base :
protected override async Task OnInitializedAsync()
{
await ...
await base.OnInitializedAsync();
}
Il n’est pas nécessaire d’appeler ComponentBase.OnInitializedAsync, sauf si une classe de base personnalisée est utilisée avec une logique personnalisée. Pour plus d’informations, consultez la section Méthodes de cycle de vie de classe de base.
Les applications Blazor qui prérendent leur contenu sur le serveur appellent OnInitializedAsync deux fois :
- Une fois lorsque le composant est initialement rendu statiquement dans le cadre de la page.
- Une deuxième fois lorsque le navigateur affiche le composant.
Pour empêcher le code du développeur dans OnInitializedAsync de s’exécuter deux fois lors du prerendering, consultez la section Reconnexion avec état après le prerendering. Le contenu de la section se concentre sur les Blazor Web App et la reconnexion SignalR avec état. Pour conserver l’état pendant l’exécution du code d’initialisation lors du prérendu, consultez Prérendu des composants ASP.NET Core Razor.
Pour empêcher le code du développeur dans OnInitializedAsync de s’exécuter deux fois lors du prerendering, consultez la section Reconnexion avec état après le prerendering. Bien que le contenu de la section se concentre sur Blazor Server et la reconnexion SignalR avec état, le scénario de prérendu dans les solutions Blazor WebAssembly hébergées (WebAssemblyPrerendered) implique des conditions et approches similaires pour empêcher l’exécution du code du développeur deux fois. Pour conserver l’état pendant l’exécution du code d’initialisation lors du prérendu, consultez Prérendu et intégration des composants ASP.NET Core Razor.
Bien qu’une application Blazor soit en prerendering, certaines actions, telles que l’appel à JavaScript (JS interop), ne sont pas possibles. Les composants peuvent avoir besoin de s’afficher différemment lorsqu’ils sont prérendus. Pour plus d’informations, consultez la section Prerendering avec interopérabilité JavaScript.
Si des gestionnaires d’événements sont fournis dans le code du développeur, décochez-les lors de la suppression. Pour plus d’informations, consultez la section Suppression des composants avec IDisposable
IAsyncDisposable
.
Utilisez le rendu en streaming avec le rendu côté serveur statique (SSR statique) ou le prérendu afin d’améliorer l’expérience utilisateur pour les composants qui effectuent des tâches asynchrones de longue durée dans OnInitializedAsync pour effectuer un rendu complet. Pour plus d’informations, consultez le rendu de composants Razor ASP.NET Core.
Une fois les paramètres définis (OnParametersSet{Async}
)
OnParametersSet ou OnParametersSetAsync sont appelés :
Une fois le composant initialisé dans OnInitialized ou OnInitializedAsync.
Lorsque le composant parent est remis à l'état initial et fourni :
- Des types immuables connus ou primitifs quand au moins un paramètre a changé.
- Paramètres de type complexe. L’infrastructure ne peut pas savoir si les valeurs d’un paramètre de type complexe ont muté en interne, de sorte que l’infrastructure traite toujours le jeu de paramètres comme modifié lorsqu’un ou plusieurs paramètres de type complexe sont présents.
Pour plus d’informations sur les conventions de rendu, consultez le rendu de composants Razor ASP.NET Core.
La méthode synchrone est appelée avant la méthode asynchrone.
Les méthodes peuvent être appelées même si les valeurs de paramètre n’ont pas changé. Ce comportement souligne la nécessité pour les développeurs d’implémenter une logique supplémentaire dans les méthodes pour vérifier si les valeurs des paramètres ont effectivement changé avant de réinitialiser les données ou l’état en fonction de ces paramètres.
Pour l’exemple de composant suivant, accédez à la page du composant à une URL :
- Avec une date de début reçue par
StartDate
:/on-parameters-set/2021-03-19
- Sans date de début, où
StartDate
est attribuée une valeur de l’heure locale actuelle :/on-parameters-set
Remarque
Dans un itinéraire de composant, il n’est pas possible de limiter à la fois un paramètre DateTime avec la contrainte de routage datetime
et rendre le paramètre facultatif. Par conséquent, le composant OnParamsSet
suivant utilise deux directives @page
pour gérer le routage avec et sans segment de date fourni dans l’URL.
OnParamsSet.razor
:
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<PageTitle>On Parameters Set</PageTitle>
<h1>On Parameters Set Example</h1>
<p>
Pass a datetime in the URI of the browser's address bar.
For example, add <code>/1-1-2024</code> to the address.
</p>
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied " +
$"(StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used " +
$"(StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
Le travail asynchrone lors de l’application de paramètres et de valeurs de propriété doit se produire pendant l’événement de cycle de vie OnParametersSetAsync :
protected override async Task OnParametersSetAsync()
{
await ...
}
Si une classe de base personnalisée est utilisée avec une logique d’initialisation personnalisée, appelez OnParametersSetAsync sur la classe de base :
protected override async Task OnParametersSetAsync()
{
await ...
await base.OnParametersSetAsync();
}
Il n’est pas nécessaire d’appeler ComponentBase.OnParametersSetAsync, sauf si une classe de base personnalisée est utilisée avec une logique personnalisée. Pour plus d’informations, consultez la section Méthodes de cycle de vie de classe de base.
Si des gestionnaires d’événements sont fournis dans le code du développeur, décochez-les lors de la suppression. Pour plus d’informations, consultez la section Suppression des composants avec IDisposable
IAsyncDisposable
.
Pour plus d’informations sur les contraintes et paramètres de routage, consultez ASP.NET Core Blazor routage et navigation.
Pour obtenir un exemple d’implémentation manuelle de SetParametersAsync
pour améliorer les performances dans certains scénarios, consultez ASP.NET Core Blazor meilleures pratiques en matière de performances.
Après le rendu du composant (OnAfterRender{Async}
)
OnAfterRender et OnAfterRenderAsync sont appelés une fois qu’un composant a produit un rendu de manière interactive et que l’interface utilisateur a terminé la mise à jour (par exemple, une fois les éléments ajoutés au DOM du navigateur). Les références d’élément et de composant sont renseignées à ce stade. Utilisez cette étape pour effectuer des étapes d’initialisation supplémentaires avec le contenu rendu, comme les appels d’interopérabilité JS qui interagissent avec les éléments DOM rendus. La méthode synchrone est appelée avant la méthode asynchrone.
Ces méthodes ne sont pas sollicitées pendant le prérendu ou le rendu côté serveur statique (SSR statique), car ces processus ne sont pas attachés à un DOM de navigateur en direct et sont déjà terminés avant la mise à jour du DOM.
Pour OnAfterRenderAsync, le composant ne produit pas automatiquement un rendu après l’achèvement de tout Task
retourné pour éviter une boucle de rendu infinie.
OnAfterRender et OnAfterRenderAsync sont appelées une fois que le rendu d’un composant est terminé. Les références d’élément et de composant sont renseignées à ce stade. Utilisez cette étape pour effectuer des étapes d’initialisation supplémentaires avec le contenu rendu, comme les appels d’interopérabilité JS qui interagissent avec les éléments DOM rendus. La méthode synchrone est appelée avant la méthode asynchrone.
Ces méthodes ne sont pas sollicitées pendant le prérendu, car le prérendu n’est pas attaché à un DOM de navigateur en direct et est déjà terminé avant la mise à jour du DOM.
Pour OnAfterRenderAsync, le composant ne produit pas automatiquement un rendu après l’achèvement de tout Task
retourné pour éviter une boucle de rendu infinie.
Paramètre firstRender
pour OnAfterRender et OnAfterRenderAsync :
- Est défini sur
true
la première fois que l’instance du composant est rendue. - Peut être utilisé pour garantir que le travail d’initialisation n’est effectué qu’une seule fois.
AfterRender.razor
:
@page "/after-render"
@inject ILogger<AfterRender> Logger
<PageTitle>After Render</PageTitle>
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender) =>
Logger.LogInformation("firstRender = {FirstRender}", firstRender);
private void HandleClick() => Logger.LogInformation("HandleClick called");
}
@page "/after-render"
@inject ILogger<AfterRender> Logger
<PageTitle>After Render</PageTitle>
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
{
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
}
private void HandleClick()
{
Logger.LogInformation("HandleClick called");
}
}
@page "/after-render"
@inject ILogger<AfterRender> Logger
<PageTitle>After Render</PageTitle>
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
{
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
}
private void HandleClick()
{
Logger.LogInformation("HandleClick called");
}
}
@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
{
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
}
private void HandleClick()
{
Logger.LogInformation("HandleClick called");
}
}
@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger
<h1>After Render Example</h1>
<p>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
{
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
}
private void HandleClick()
{
Logger.LogInformation("HandleClick called");
}
}
L’exemple AfterRender.razor
produit la sortie suivante dans la console lorsque la page est chargée et que le bouton est sélectionné :
OnAfterRender: firstRender = True
HandleClick called
OnAfterRender: firstRender = False
Le travail asynchrone immédiatement après le rendu doit se produire pendant l’événement de cycle de vie OnAfterRenderAsync :
protected override async Task OnAfterRenderAsync(bool firstRender)
{
...
}
Si une classe de base personnalisée est utilisée avec une logique d’initialisation personnalisée, appelez OnAfterRenderAsync sur la classe de base :
protected override async Task OnAfterRenderAsync(bool firstRender)
{
...
await base.OnAfterRenderAsync(firstRender);
}
Il n’est pas nécessaire d’appeler ComponentBase.OnAfterRenderAsync, sauf si une classe de base personnalisée est utilisée avec une logique personnalisée. Pour plus d’informations, consultez la section Méthodes de cycle de vie de classe de base.
Même si vous retournez un Task à partir de OnAfterRenderAsync, l’infrastructure ne planifie pas de cycle de rendu supplémentaire pour votre composant une fois cette tâche terminée. Cela permet d’éviter une boucle de rendu infinie. Cela diffère des autres méthodes de cycle de vie, qui planifient un autre cycle de rendu une fois qu’une Task retournée est terminée.
OnAfterRender et OnAfterRenderAsync ne sont pas appelés pendant le processus de prerendering sur le serveur. Les méthodes sont appelées lorsque le composant est rendu de manière interactive après le prerendering. Lorsque l'application se prépare :
- Le composant s’exécute sur le serveur pour produire un balisage HTML statique dans la réponse HTTP. Pendant cette phase, OnAfterRender et OnAfterRenderAsync ne sont pas appelées.
- Lorsque le Blazor script (
blazor.{server|webassembly|web}.js
) démarre dans le navigateur, le composant est redémarré en mode de rendu interactif. Une fois qu’un composant est redémarré, OnAfterRender et OnAfterRenderAsync sont appelées, car l’application n’est plus dans la phase de prerendering.
Si des gestionnaires d’événements sont fournis dans le code du développeur, décochez-les lors de la suppression. Pour plus d’informations, consultez la section Suppression des composants avec IDisposable
IAsyncDisposable
.
Méthodes de cycle de vie de classe de base
En cas de substitution des méthodes de cycle de vie de Blazor, il n’est pas nécessaire d’appeler des méthodes de cycle de vie de classe de base pour ComponentBase. Toutefois, un composant doit appeler une méthode de cycle de vie de classe de base substituée dans les situations suivantes :
- En cas de substitution de ComponentBase.SetParametersAsync,
await base.SetParametersAsync(parameters);
est généralement appelée, car la méthode de classe de base appelle d’autres méthodes de cycle de vie et déclenche le rendu de manière complexe. Pour plus d’informations, consultez la section Lorsque les paramètres sont définis (SetParametersAsync
). - Si la méthode de classe de base contient une logique qui doit être exécutée. Les consommateurs de bibliothèque appellent généralement des méthodes de cycle de vie de classe de base lors de l’héritage d’une classe de base, car les classes de base de bibliothèque ont souvent une logique de cycle de vie personnalisée à exécuter. Si l’application utilise une classe de base à partir d’une bibliothèque, consultez la documentation de la bibliothèque pour obtenir des conseils.
Dans l’exemple suivant, base.OnInitialized();
est appelé pour s’assurer que la méthode OnInitialized
de la classe de base est exécutée. Sans l’appel, BlazorRocksBase2.OnInitialized
ne s’exécute pas.
BlazorRocks2.razor
:
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
BlazorRocksBase2.cs
:
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } = "Blazor rocks the browser!";
protected override void OnInitialized() =>
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
Changements d’état (StateHasChanged
)
StateHasChanged avertit le composant que son état a changé. Lorsque c’est applicable, appeler StateHasChanged empile un nouveau rendu qui se produit lorsque le fil de discussion principal de l’application est libre.
StateHasChanged est appelée automatiquement pour les méthodes EventCallback. Pour plus d’informations sur les rappels d’événements, consultez ASP.NET Core Blazor gestion des événements.
Pour plus d’informations sur le rendu des composants et le moment d’appeler StateHasChanged, notamment quand l’appeler avec ComponentBase.InvokeAsync, consultez ASP.NET Core Razor rendu de composant.
Gérer les actions asynchrones incomplètes au rendu
Les actions asynchrones effectuées dans les événements de cycle de vie ne se sont peut-être pas terminées avant que le composant ne soit rendu. Les objets peuvent être null
ou être renseignés de manière incomplète avec des données pendant l’exécution de la méthode de cycle de vie. Fournissez une logique de rendu pour confirmer que les objets sont initialisés. Affiche les éléments d’interface utilisateur d’espace réservé (par exemple, un message de chargement) alors que les objets sont null
.
Dans le composant suivant, OnInitializedAsync est remplacé pour fournir de manière asynchrone les données d’évaluation des films (movies
). Lorsque movies
est null
, un message de chargement s’affiche à l’utilisateur. Une fois le Task
retourné par OnInitializedAsync terminé, le composant est remangé avec l’état mis à jour.
<h1>Sci-Fi Movie Ratings</h1>
@if (movies == null)
{
<p><em>Loading...</em></p>
}
else
{
<ul>
@foreach (var movie in movies)
{
<li>@movie.Title — @movie.Rating</li>
}
</ul>
}
@code {
private Movies[]? movies;
protected override async Task OnInitializedAsync()
{
movies = await GetMovieRatings(DateTime.Now);
}
}
Gérer les erreurs
Pour plus d’informations sur la gestion des erreurs pendant l’exécution de la méthode de cycle de vie, consultez Gérer les erreurs dans les Blazorapplications ASP.NET Core.
Reconnexion avec état après le prerendering
Lors du prérendu sur le serveur, un composant est initialement rendu statiquement dans le cadre de la page. Une fois que le navigateur établit une connexion SignalR au serveur, le composant est rendu à nouveau et interactif. Si la méthode de cycle de vie OnInitialized{Async}
pour initialiser le composant est présente, la méthode est exécutée deux fois :
- Lorsque le composant est prérendu statiquement.
- Une fois la connexion au serveur établie.
Cela peut entraîner une modification notable des données affichées dans l’interface utilisateur lorsque le composant est finalement rendu. Pour éviter ce comportement, transmettez un identificateur pour mettre en cache l’état lors du prérendu et pour récupérer l’état après le prérendu.
Le code suivant illustre un élément WeatherForecastService
qui évite la modification de l’affichage des données en raison d’un prérendu. L’élément Delay attendu (await Task.Delay(...)
) simule un court délai avant de retourner des données à partir de la méthode GetForecastAsync
.
Ajoutez des services IMemoryCache avec AddMemoryCache sur la collection de services dans le fichier Program
de l’application :
builder.Services.AddMemoryCache();
WeatherForecastService.cs
:
using Microsoft.Extensions.Caching.Memory;
namespace BlazorSample;
public class WeatherForecastService(IMemoryCache memoryCache)
{
private static readonly string[] summaries =
[
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
];
public IMemoryCache MemoryCache { get; } = memoryCache;
public Task<WeatherForecast[]?> GetForecastAsync(DateOnly startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
});
}
}
using Microsoft.Extensions.Caching.Memory;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]?> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
});
}
}
using Microsoft.Extensions.Caching.Memory;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
});
}
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
var rng = new Random();
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = summaries[rng.Next(summaries.Length)]
}).ToArray();
});
}
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
var rng = new Random();
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = summaries[rng.Next(summaries.Length)]
}).ToArray();
});
}
}
Pour plus d’informations sur le RenderMode, consultez conseils BlazorSignalR ASP.NET Core.
Le contenu de cette section se concentre sur les Blazor Web App et la reconnexion SignalRavec état. Pour conserver l’état pendant l’exécution du code d’initialisation lors du prérendu, consultez Prérendu des composants ASP.NET Core Razor.
Bien que le contenu de cette section se concentre sur Blazor Server et la reconnexion SignalR avec état, le scénario de prérendu dans les solutions Blazor WebAssembly hébergées (WebAssemblyPrerendered) implique des conditions et approches similaires pour empêcher l’exécution du code du développeur deux fois. Pour conserver l’état pendant l’exécution du code d’initialisation lors du prérendu, consultez Prérendu et intégration des composants ASP.NET Core Razor.
Prerendering avec l’interopérabilité JavaScript
Cette section s’applique aux applications côté serveur et hébergées qui effectuent un pré-rendu des composants Razor. Le prérendu est couvert dans Prérendu des composants ASP.NET Core Razor.
Remarque
La navigation interne pour le routage interactif dans les applications Blazor Web App n’implique pas de demander le nouveau contenu de la page auprès du serveur. Par conséquent, un prérendu n’est pas effectué pour les demandes de pages internes. Si l’application adopte le routage interactif, effectuez un rechargement de la page complète pour les exemples de composants qui illustrent le comportement de prérendu. Pour plus d’informations, consultez Prévisualiser les composants ASP.NET Core Razor.
Cette section s’applique aux applications côté serveur et Blazor WebAssembly hébergées qui effectuent un prérendu des composants Razor. Le prérendu est couvert dans Prérendu et intégration des composants ASP.NET Core Razor.
Lors du prérendu, l’appel en JavaScript (JS) n’est pas possible. L’exemple suivant montre comment utiliser l’interopérabilité JS dans le cadre de la logique d’initialisation d’un composant d’une manière compatible avec le prérendu.
La fonction scrollElementIntoView
suivante :
- fait défiler jusqu’à l’élément passé avec
scrollIntoView
; - retourne la valeur de propriété
top
de l’élément à partir de la méthodegetBoundingClientRect
.
window.scrollElementIntoView = (element) => {
element.scrollIntoView();
return element.getBoundingClientRect().top;
}
Là où IJSRuntime.InvokeAsync appelle la fonction JS dans le code du composant, la ElementReference est utilisée uniquement dans OnAfterRenderAsync et non dans une méthode de cycle de vie antérieure, car aucun élément DOM HTML n’est présent tant que le composant n’est pas rendu.
StateHasChanged
(source de référence) est appelé pour empiler le nouveau rendu du composant avec le nouvel état obtenu à partir de l’appel d’interopérabilité JS (pour plus d’informations, consultez Rendu de composants Razor ASP.NET Core). Le code ne crée pas de boucle infinie, car StateHasChanged est appelé uniquement lorsque scrollPosition
est null
.
PrerenderedInterop.razor
:
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<PageTitle>Prerendered Interop</PageTitle>
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
</div>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && scrollPosition is null)
{
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
}
}
}
L’exemple précédent pollue le client avec une fonction globale. Pour une meilleure approche dans les applications de production, consultez Isolation JavaScript dans les modules JavaScript.
Suppression des composants avec IDisposable
et IAsyncDisposable
Si un composant implémente IDisposable ou IAsyncDisposable, l’infrastructure appelle à la mise à disposition des ressources lorsque le composant est supprimé de l’interface utilisateur. Ne vous fiez pas au moment exact où ces méthodes sont exécutées. Par exemple, IAsyncDisposable peut être déclenché avant ou après l’appel ou la fin d’une Task asynchrone attendue dans OnInitalizedAsync
. En outre, le code de suppression d’objets ne doit pas supposer que les objets créés pendant l’initialisation ou d’autres méthodes de cycle de vie existent.
Les composants ne doivent pas avoir besoin d’implémenter IDisposable et IAsyncDisposable simultanément. Si les deux sont implémentés, l’infrastructure exécute uniquement la surcharge asynchrone.
Le code du développeur doit s’assurer que les implémentations IAsyncDisposable ne prennent pas beaucoup de temps.
Suppression des références d’objets d’interopérabilité JavaScript
Des exemples dans les articles d’interopérabilité JavaScript (JS) illustrent les modèles de suppression d’objets classiques :
Lors de l’appel JS à partir de .NET, comme décrit dans Appeler des fonctions JavaScript à partir de méthodes .NET dans ASP.NET Core Blazor, éliminez les fonctions créées IJSObjectReference/IJSInProcessObjectReference/JSObjectReferenceà partir de .NET ou de JS pour éviter toute fuite de JS mémoire.
Lors de l’appel de .NET à partir de JS, comme décrit dans Appeler des méthodes .NET à partir de fonctions JavaScript dans ASP.NET Core Blazor, supprimez un DotNetObjectReference créé à partir de .NET ou de JS pour éviter la fuite de mémoire .NET.
Les références d’objet d’interopérabilité JS sont implémentées en tant que carte avec pour clé un identificateur sur le côté de l’appel d’interopérabilité JS qui crée la référence. Lorsque l’élimination de l’objet est lancée du côté .NET ou JS, Blazor supprime l’entrée de la carte, et l’objet peut être récupéré en mémoire tant qu’aucune autre référence forte à l’objet n’est présente.
Au minimum, éliminez toujours les objets créés côté .NET pour éviter les fuites de mémoire managée .NET.
Tâches de nettoyage de modèle DOM lors de la suppression des composants
Pour plus d’informations, consultez Interopérabilité JavaScript et ASP.NET Core Blazor (interopérabilité JS).
Pour obtenir de l’aide sur JSDisconnectedException quand un circuit est déconnecté, consultez l’interopérabilité ASP.NET Blazor JavaScript (interopérabilité JS). Pour obtenir des conseils généraux sur la gestion des erreurs d’interopérabilité JavaScript, consultez la section JavaScript Interop dans Gérer les erreurs dans les applications Blazor ASP.NET Core.
IDisposable
synchrone
Pour les tâches de suppression synchrone, utilisez IDisposable.Dispose.
Le composant suivant :
- Implémente IDisposable avec la directive
@implements
Razor. - Suppression de
obj
, le type qui implémente IDisposable. - Une vérification null est effectuée, car
obj
est créée dans une méthode de cycle de vie (non affichée).
@implements IDisposable
...
@code {
...
public void Dispose()
{
obj?.Dispose();
}
}
Si un objet unique nécessite une suppression, une lambda peut être utilisée pour supprimer l’objet lorsque Dispose est appelé. L’exemple suivant apparaît dans l’article de rendu du composantRazor ASP.NET Core et illustre l’utilisation d’une expression lambda pour la suppression d’un Timer.
TimerDisposal1.razor
:
@page "/timer-disposal-1"
@using System.Timers
@implements IDisposable
<PageTitle>Timer Disposal 1</PageTitle>
<h1>Timer Disposal Example 1</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
CounterWithTimerDisposal1.razor
:
@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
CounterWithTimerDisposal1.razor
:
@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
CounterWithTimerDisposal1.razor
:
@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
CounterWithTimerDisposal1.razor
:
@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer = new Timer(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
Remarque
Dans l’exemple précédent, l’appel à StateHasChanged est encapsulé par un appel à ComponentBase.InvokeAsync, car le rappel est appelé en dehors du contexte de synchronisation de Blazor. Pour plus d’informations, consultez le rendu de composants Razor ASP.NET Core.
Si l’objet est créé dans une méthode de cycle de vie, telle que OnInitialized{Async}
, vérifiez null
avant d’appeler Dispose
.
TimerDisposal2.razor
:
@page "/timer-disposal-2"
@using System.Timers
@implements IDisposable
<PageTitle>Timer Disposal 2</PageTitle>
<h1>Timer Disposal Example 2</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer? timer;
protected override void OnInitialized()
{
timer = new Timer(1000);
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer?.Dispose();
}
CounterWithTimerDisposal2.razor
:
@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer? timer;
protected override void OnInitialized()
{
timer = new Timer(1000);
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer?.Dispose();
}
CounterWithTimerDisposal2.razor
:
@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer? timer;
protected override void OnInitialized()
{
timer = new Timer(1000);
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer?.Dispose();
}
CounterWithTimerDisposal2.razor
:
@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer;
protected override void OnInitialized()
{
timer = new Timer(1000);
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer?.Dispose();
}
CounterWithTimerDisposal2.razor
:
@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer;
protected override void OnInitialized()
{
timer = new Timer(1000);
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer?.Dispose();
}
Pour plus d'informations, voir :
IAsyncDisposable
asynchrone
Pour les tâches d’élimination asynchrones, utilisez IAsyncDisposable.DisposeAsync.
Le composant suivant :
- Implémente IAsyncDisposable avec la directive
@implements
Razor. - Supprime
obj
, qui est un type non managé qui implémente IAsyncDisposable. - Une vérification null est effectuée, car
obj
est créée dans une méthode de cycle de vie (non affichée).
@implements IAsyncDisposable
...
@code {
...
public async ValueTask DisposeAsync()
{
if (obj is not null)
{
await obj.DisposeAsync();
}
}
}
Pour plus d'informations, voir :
Affectation de null
aux objets supprimés
En règle générale, il n’est pas nécessaire d’affecter null
à des objets supprimés après avoir appelé Dispose/DisposeAsync. Les cas rares pour l’affectation de null
incluent les éléments suivants :
- Si le type de l’objet est mal implémenté et ne tolère pas les appels répétés à Dispose/DisposeAsync, affectez
null
après suppression pour ignorer correctement d’autres appels à Dispose/DisposeAsync. - Si un processus de longue durée continue de contenir une référence à un objet supprimé, l’affectation
null
permet au récupérateur de mémoire de libérer l’objet malgré le processus de longue durée contenant une référence à celui-ci.
Il s’agit de scénarios inhabituels. Pour les objets qui sont implémentés correctement et qui se comportent normalement, il est inutile d’affecter null
à des objets supprimés. Dans les rares cas où un objet doit être affecté null
, nous vous recommandons de documenter la raison et de rechercher une solution qui évite d’avoir à affecter null
.
StateHasChanged
Remarque
L’appel de StateHasChanged dans Dispose
et DisposeAsync
n’est pas pris en charge. StateHasChanged peut être appelée dans le cadre de la suppression du convertisseur, de sorte que la demande de mises à jour de l’interface utilisateur à ce stade n’est pas prise en charge.
Gestionnaires d’événements
Toujours annuler les gestionnaires d’événements des événements .NET. Les exemples de formulaires Blazor suivants montrent comment se désinscrire d'un gestionnaire d'événements dans la méthode Dispose
:
Champ privé et approche lambda
@implements IDisposable <EditForm ... EditContext="editContext" ...> ... <button type="submit" disabled="@formInvalid">Submit</button> </EditForm> @code { ... private EventHandler<FieldChangedEventArgs>? fieldChanged; protected override void OnInitialized() { editContext = new(model); fieldChanged = (_, __) => { ... }; editContext.OnFieldChanged += fieldChanged; } public void Dispose() { editContext.OnFieldChanged -= fieldChanged; } }
Approche de méthode privée
@implements IDisposable <EditForm ... EditContext="editContext" ...> ... <button type="submit" disabled="@formInvalid">Submit</button> </EditForm> @code { ... protected override void OnInitialized() { editContext = new(model); editContext.OnFieldChanged += HandleFieldChanged; } private void HandleFieldChanged(object sender, FieldChangedEventArgs e) { ... } public void Dispose() { editContext.OnFieldChanged -= HandleFieldChanged; } }
Pour plus d’informations, consultez la section Suppression des composants avec IDisposable
etIAsyncDisposable
.
Pour plus d’informations sur le composant EditForm et les formulaires, consultez Vue d’ensemble des formulaires Blazor ASP.NET Core et les autres articles sur les formulaires dans le nœud Formulaires.
Fonctions, méthodes et expressions anonymes
Lorsque des fonctions, des méthodes ou des expressions anonymes sont utilisées, il n’est pas nécessaire d’implémenter IDisposable et de se désabonner des délégués. Toutefois, l’échec de l’annulation d’un délégué est un problème lorsque l’objet qui expose l’événement dépasse la durée de vie du composant qui inscrit le délégué. Lorsque cela se produit, une fuite de mémoire se produit, car le délégué inscrit maintient l’objet d’origine actif. Par conséquent, utilisez uniquement les approches suivantes lorsque vous savez que le délégué d’événement se supprime rapidement. En cas de doute quant à la durée de vie des objets qui nécessitent une suppression, abonnez-vous à une méthode de délégué et éliminez correctement le délégué, comme le montrent les exemples précédents.
Approche de méthode lambda anonyme (suppression explicite non requise) :
private void HandleFieldChanged(object sender, FieldChangedEventArgs e) { formInvalid = !editContext.Validate(); StateHasChanged(); } protected override void OnInitialized() { editContext = new(starship); editContext.OnFieldChanged += (s, e) => HandleFieldChanged((editContext)s, e); }
Approche d’expression lambda anonyme (suppression explicite non requise) :
private ValidationMessageStore? messageStore; [CascadingParameter] private EditContext? CurrentEditContext { get; set; } protected override void OnInitialized() { ... messageStore = new(CurrentEditContext); CurrentEditContext.OnValidationRequested += (s, e) => messageStore.Clear(); CurrentEditContext.OnFieldChanged += (s, e) => messageStore.Clear(e.FieldIdentifier); }
L’exemple complet du code précédent avec des expressions lambda anonymes apparaît dans l’article Validation de formulaires Blazor ASP.NET Core.
Pour plus d’informations, consultez Nettoyage des ressources non managées et les rubriques qui le suivent sur l’implémentation des méthodes Dispose
et DisposeAsync
.
Travail en arrière-plan annulable
Les composants effectuent souvent un travail en arrière-plan de longue durée, comme effectuer des appels réseau (HttpClient) et interagir avec des bases de données. Il est souhaitable d’arrêter le travail en arrière-plan pour conserver les ressources système dans plusieurs situations. Par exemple, les opérations asynchrones en arrière-plan ne s’arrêtent pas automatiquement lorsqu’un utilisateur quitte un composant.
Voici d’autres raisons pour lesquelles les éléments de travail en arrière-plan peuvent nécessiter une annulation :
- Une tâche en arrière-plan d’exécution a été démarrée avec des données d’entrée ou des paramètres de traitement défectueux.
- L’ensemble actuel d’éléments de travail en arrière-plan en cours d’exécution doit être remplacé par un nouvel ensemble d’éléments de travail.
- La priorité des tâches en cours d’exécution doit être modifiée.
- L’application doit être arrêtée pour le redéploiement du serveur.
- Les ressources du serveur deviennent limitées, ce qui nécessite la replanification des éléments de travail en arrière-plan.
Pour implémenter un modèle de travail en arrière-plan annulable dans un composant :
- Utilisez un CancellationTokenSource et CancellationToken.
- Lors de l’élimination du composant et à tout moment l’annulation est souhaitée en annulant manuellement le jeton, appelez
CancellationTokenSource.Cancel
pour signaler que le travail en arrière-plan doit être annulé. - Une fois l’appel asynchrone retourné, appelez ThrowIfCancellationRequested sur le jeton.
Dans l’exemple suivant :
await Task.Delay(5000, cts.Token);
représente un travail en arrière-plan asynchrone de longue durée.BackgroundResourceMethod
représente une méthode d’arrière-plan de longue durée qui ne doit pas démarrer si leResource
est supprimé avant l’appel de la méthode.
BackgroundWork.razor
:
@page "/background-work"
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<PageTitle>Background Work</PageTitle>
<h1>Background Work Example</h1>
<p>
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
</p>
<p>Study logged messages in the console.</p>
<p>
If you trigger disposal within 10 seconds of page load, the
<code>BackgroundResourceMethod</code> isn't executed.
</p>
<p>
If disposal occurs after <code>BackgroundResourceMethod</code> is called but
before action is taken on the resource, an <code>ObjectDisposedException</code>
is thrown by <code>BackgroundResourceMethod</code>, and the resource isn't
processed.
</p>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
private IList<string> messages = [];
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(10000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
if (!cts.IsCancellationRequested)
{
cts.Cancel();
}
cts?.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose() => disposed = true;
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new Resource();
private CancellationTokenSource cts = new CancellationTokenSource();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
Blazor Server événements de reconnexion
Les événements de cycle de vie des composants abordés dans cet article fonctionnent séparément des gestionnaires d’événements de reconnexion côté serveur. En cas de perte de la connexion SignalR au client, seules les mises à jour de l’interface utilisateur sont interrompues. Les mises à jour de l’interface utilisateur reprennent lorsque la connexion est rétablie. Pour plus d’informations sur les événements et la configuration du gestionnaire de circuit, consultez les conseils ASP.NET Core BlazorSignalR.
Ressources supplémentaires
Gérer les exceptions interceptées en dehors du cycle de vie d’un composant Razor