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.

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 :

  1. 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.
  2. 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.
  3. Effectuez le rendu de tous les travaux synchrones et terminez les Tasks.

Remarque

Les actions asynchrones effectuées dans les événements de cycle de vie ne se sont peut-être pas 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.

Évènements du cycle de vie des composants d’un composant Razor dans Blazor

Traitement d’un événement DOM :

  1. Le gestionnaire d’événements est exécuté.
  2. Si une Task incomplète est retournée, la Task est attendue, puis le composant est renvoyé.
  3. Effectuez le rendu de tous les travaux synchrones et terminez les Tasks.

Traitement d’un évènement DOM

Cycle de vie du Render :

  1. É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 retourne false.
  2. Générez l’arborescence de rendu diff (différence) et affichez le composant.
  3. Attendez la mise à jour du DOM.
  4. Appelez OnAfterRender{Async}. La méthode synchrone est appelée avant la méthode asynchrone.

Cycle de vie de l’affichage

Les appels de développeur à StateHasChanged entraînent un 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.

Si ComponentBase.SetParametersAsync n’est pas appelée avec base.SetParametersAsync();, le code du développeur peut interpréter les valeurs des paramètres entrants de quelque manière nécessaire que ce soit. Par exemple, il n’est pas nécessaire d’affecter les paramètres entrants aux propriétés de la classe.

Si vous choisissez de ne pas appeler la méthode de classe de base, vous devez appeler vos propres méthodes d’initialisation de composant (OnInitialized/OnInitializedAsync). Sinon, elles ne seront pas appelées, car l’appel de ComponentBase.SetParametersAsync est ce qui les appelle. StateHasChanged doit également être appelé après l’initialisation. Reportez-vous à la source de référence ComponentBase lorsque vous structurez votre code si vous ne prévoyez pas d’appeler ComponentBase.SetParametersAsync.

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 l’API 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 :

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 IDisposableIAsyncDisposable.

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 OnInitializedAsyncdeux 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 web Apps Blazor et la SignalRreconnexion 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 reconnexionSignalR 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 IDisposableIAsyncDisposable.

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 IDisposableIAsyncDisposable.

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("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"
@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 OnAfterRenderAsyncne 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 :

  1. 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.
  2. 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 OnAfterRenderAsyncsont 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 IDisposableIAsyncDisposable.

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 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é. Le cas échéant, l’appel de StateHasChanged entraîne la remise à l’état du composant.

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 &mdash; @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 web Apps Blazor et la SignalRreconnexionavec é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 reconnexionSignalR 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 web Blazor 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.

Lorsqu’une application est en cours de prérendu, certaines actions, comme l’appel à JavaScript (JS), ne sont pas possibles.

Dans l’exemple suivant, la fonction setElementText1 est appelée avec JSRuntimeExtensions.InvokeVoidAsync et ne retourne pas de valeur.

Remarque

Pour obtenir une aide générale sur l’emplacement deJS et nos recommandations pour les applications de production, consultez Emplacement de JavaScript dans les applications Blazor ASP.NET Core.

<script>
  window.setElementText1 = (element, text) => element.innerText = text;
</script>

Avertissement

L’exemple précédent modifie le modèle DOM directement à des fins de démonstration uniquement. La modification directe du DOM avec JS n’est pas recommandée dans la plupart des scénarios, car JS peut interférer avec le suivi des modifications de Blazor. Pour plus d’informations, consultez Interopérabilité JavaScript et ASP.NET Core Blazor (interopérabilité JS).

L’événement de cycle de vie OnAfterRender{Async} n’est pas appelé pendant le processus de prérendu sur le serveur. Remplacez la méthode OnAfterRender{Async} pour retarder les appels d’interopérabilité JS jusqu’à ce que le composant soit rendu et interactif sur le client après le prérendu.

PrerenderedInterop1.razor :

@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JS

<PageTitle>Prerendered Interop 1</PageTitle>

<h1>Prerendered Interop Example 1</h1>

<div @ref="divElement">Text during render</div>

@code {
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync(
                "setElementText1", divElement, "Text after render");
        }
    }
}

Remarque

L’exemple précédent pollue le client avec des fonctions globales. Pour une meilleure approche dans les applications de production, consultez Isolation JavaScript dans les modules JavaScript.

Exemple :

export setElementText1 = (element, text) => element.innerText = text;

Le composant 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. Le composant indique qu’il est possible de déclencher une mise à jour de rendu à partir de OnAfterRenderAsync. Le développeur doit veiller à éviter de créer une boucle infinie dans ce scénario.

Dans l’exemple suivant, la fonction setElementText2 est appelée avec IJSRuntime.InvokeAsync et retourne une valeur.

Remarque

Pour obtenir une aide générale sur l’emplacement deJS et nos recommandations pour les applications de production, consultez Emplacement de JavaScript dans les applications Blazor ASP.NET Core.

<script>
  window.setElementText2 = (element, text) => {
    element.innerText = text;
    return text;
  };
</script>

Avertissement

L’exemple précédent modifie le modèle DOM directement à des fins de démonstration uniquement. La modification directe du DOM avec JS n’est pas recommandée dans la plupart des scénarios, car JS peut interférer avec le suivi des modifications de Blazor. Pour plus d’informations, consultez Interopérabilité JavaScript et ASP.NET Core Blazor (interopérabilité JS).

Là où JSRuntime.InvokeAsync est appelé, ElementReference est utilisé 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 affiché.

StateHasChanged est appelé pour renvoyer le composant avec le nouvel état obtenu à partir de l’appel d’interopérabilité JS (pour plus d’informations, consultez Rendu de composants ASP.NET Core Razor). Le code ne crée pas de boucle infinie, car StateHasChanged est appelé uniquement lorsque data est null.

PrerenderedInterop2.razor :

@page "/prerendered-interop-2"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<PageTitle>Prerendered Interop 2</PageTitle>

<h1>Prerendered Interop Example 2</h1>

<p>
    Get value via JS interop call:
    <strong id="val-get-by-interop">@(infoFromJs ?? "No value yet")</strong>
</p>

<p>
    Set value via JS interop call: 
    <strong id="val-set-by-interop" @ref="divElement"></strong>
</p>



@code {
    private string? infoFromJs;
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && infoFromJs == null)
        {
            infoFromJs = await JS.InvokeAsync<string>(
                "setElementText2", divElement, "Hello from interop call!");

            StateHasChanged();
        }
    }
}

Remarque

L’exemple précédent pollue le client avec des fonctions globales. Pour une meilleure approche dans les applications de production, consultez Isolation JavaScript dans les modules JavaScript.

Exemple :

export setElementText2 = (element, text) => {
  element.innerText = text;
  return text;
};

Suppression des composants avec IDisposable et IAsyncDisposable

Si un composant implémente IDisposable, IAsyncDisposable, ou les deux, l’infrastructure appelle à la mise à disposition des ressources lorsque le composant est supprimé de l’interface utilisateur. La suppression peut se produire à tout moment, y compris lors de l’initialisation des composants.

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 :

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 @implementsRazor.
  • 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 @implementsRazor.
  • 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 :

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 le Resource 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