ASP.NET ciclo di vita dei componenti principali Razor

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Questo articolo illustra il ciclo di vita dei componenti di Razor base ASP.NET e come usare gli eventi del ciclo di vita.

Eventi del ciclo di vita

Il Razor componente elabora Razor gli eventi del ciclo di vita dei componenti in un set di metodi del ciclo di vita sincroni e asincroni. È possibile eseguire l'override dei metodi del ciclo di vita per eseguire operazioni aggiuntive nei componenti durante l'inizializzazione e il rendering dei componenti.

Questo articolo semplifica l'elaborazione degli eventi del ciclo di vita dei componenti per chiarire la logica complessa del framework e non copre tutte le modifiche apportate negli anni. Potrebbe essere necessario accedere all'origine di riferimento per integrare l'elaborazione ComponentBase di eventi personalizzati con Blazorl'elaborazione degli eventi del ciclo di vita. I commenti di codice nell'origine di riferimento includono osservazioni aggiuntive sull'elaborazione degli eventi del ciclo di vita che non vengono visualizzati in questo articolo o nella documentazione dell'API.

Nota

I collegamenti della documentazione all'origine del riferimento .NET in genere caricano il ramo predefinito del repository, che rappresenta lo sviluppo corrente per la versione successiva di .NET. Per selezionare un tag per una versione specifica, usare l'elenco a discesa Switch branches or tags. Per altre informazioni, vedere How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205) (Come selezionare un tag di versione del codice sorgente di ASP.NET - dotnet/AspNetCore.Docs #26205).

I diagrammi semplificati seguenti illustrano Razor l'elaborazione degli eventi del ciclo di vita dei componenti. I metodi C# associati agli eventi del ciclo di vita sono definiti con esempi nelle sezioni seguenti di questo articolo.

Eventi del ciclo di vita dei componenti:

  1. Se il rendering del componente viene eseguito per la prima volta in una richiesta:
    • Creare l'istanza del componente.
    • Eseguire l'inserimento di proprietà.
    • Chiamare OnInitialized{Async}. Se viene restituito un elemento incompleto Task , viene Task atteso e quindi il componente viene ririsolto. Il metodo sincrono viene chiamato prima del metodo asincrono.
  2. Chiamare OnParametersSet{Async}. Se viene restituito un elemento incompleto Task , viene Task atteso e quindi il componente viene ririsolto. Il metodo sincrono viene chiamato prima del metodo asincrono.
  3. Eseguire il rendering per tutte le operazioni sincrone e complete Task.

Nota

Le azioni asincrone eseguite negli eventi del ciclo di vita potrebbero non essere state completate prima del rendering di un componente. Per altre informazioni, vedere la sezione Gestire azioni asincrone incomplete al rendering più avanti in questo articolo.

Viene eseguito il rendering di un componente padre prima dei relativi componenti figlio perché il rendering determina quali elementi figlio sono presenti. Se viene utilizzata l'inizializzazione sincrona del componente padre, l'inizializzazione padre viene garantita per prima. Se viene utilizzata l'inizializzazione asincrona dei componenti padre, l'ordine di completamento dell'inizializzazione dei componenti padre e figlio non può essere determinato perché dipende dal codice di inizializzazione in esecuzione.

Eventi del ciclo di vita dei componenti di un Razor componente in Blazor

Elaborazione di eventi DOM:

  1. Viene eseguito il gestore eventi.
  2. Se viene restituito un elemento incompleto Task , viene Task atteso e quindi il componente viene ririsolto.
  3. Eseguire il rendering per tutte le operazioni sincrone e complete Task.

Elaborazione di eventi DOM

Ciclo Render di vita:

  1. Evitare ulteriori operazioni di rendering sul componente quando vengono soddisfatte entrambe le condizioni seguenti:
    • Non è il primo rendering.
    • ShouldRender restituisce false.
  2. Compilare il diff dell'albero di rendering (differenza) ed eseguire il rendering del componente.
  3. Attendere il DOM da aggiornare.
  4. Chiamare OnAfterRender{Async}. Il metodo sincrono viene chiamato prima del metodo asincrono.

Ciclo di vita del rendering

Le chiamate degli sviluppatori per StateHasChanged ottenere un rendering. Per altre informazioni, vedere Rendering dei componenti di ASP.NET CoreRazor.

Quando i parametri sono impostati (SetParametersAsync)

SetParametersAsync imposta i parametri forniti dall'elemento padre del componente nell'albero di rendering o dai parametri di route.

Il parametro del ParameterView metodo contiene il set di valori dei parametri del componente per il componente ogni volta SetParametersAsync che viene chiamato. Eseguendo l'override del metodo, il SetParametersAsync codice dello sviluppatore può interagire direttamente con ParameterViewi parametri di .

L'implementazione predefinita di SetParametersAsync imposta il valore di ogni proprietà con l'attributo [Parameter] o [CascadingParameter] con un valore corrispondente in ParameterView. I parametri che non hanno un valore corrispondente in ParameterView vengono lasciati invariati.

Se ComponentBase.SetParametersAsync non viene richiamato con base.SetParametersAsync();, il codice dello sviluppatore può interpretare i valori dei parametri in ingresso in qualsiasi modo necessario. Ad esempio, non è necessario assegnare i parametri in ingresso alle proprietà della classe .

Se si sceglie di non chiamare il metodo della classe base, è necessario chiamare i metodi di inizializzazione dei componenti (OnInitializedAsyncOnInitialized/). In caso contrario, non verranno chiamati perché la chiamata ComponentBase.SetParametersAsync è ciò che li richiama. StateHasChanged deve essere chiamato anche dopo l'inizializzazione. Fare riferimento all'origine ComponentBase di riferimento quando si struttura il codice se non si prevede di chiamare ComponentBase.SetParametersAsync.

Nota

I collegamenti della documentazione all'origine del riferimento .NET in genere caricano il ramo predefinito del repository, che rappresenta lo sviluppo corrente per la versione successiva di .NET. Per selezionare un tag per una versione specifica, usare l'elenco a discesa Switch branches or tags. Per altre informazioni, vedere How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205) (Come selezionare un tag di versione del codice sorgente di ASP.NET - dotnet/AspNetCore.Docs #26205).

Se si vuole fare affidamento sull'inizializzazione e sul rendering dell'API di ComponentBase.SetParametersAsync ma non elaborare i parametri in ingresso, è possibile passare un oggetto vuoto ParameterView al metodo della classe base:

base.SetParametersAsync(ParameterView.Empty);

Se i gestori eventi vengono forniti nel codice dello sviluppatore, annullare ilhook all'eliminazione. Per altre informazioni, vedere la sezione Eliminazione dei componenti conIAsyncDisposableIDisposable .

Nell'esempio seguente, ParameterView.TryGetValue assegna il Param valore del parametro a value se l'analisi di un parametro di route per Param ha esito positivo. Quando value non nullè , il valore viene visualizzato dal componente.

Anche se la corrispondenza dei parametri di route non fa distinzione tra maiuscole e minuscole, TryGetValue corrisponde solo ai nomi dei parametri con distinzione tra maiuscole e minuscole nel modello di route. L'esempio seguente richiede l'uso di nel modello di /{Param?} route per ottenere il valore con TryGetValue, non /{param?}. Se /{param?} viene usato in questo scenario, TryGetValue restituisce false e message non è impostato su una message stringa.

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);
    }
}

Inizializzazione dei componenti (OnInitialized{Async})

OnInitialized e OnInitializedAsync vengono utilizzati esclusivamente per inizializzare un componente per l'intera durata dell'istanza del componente. I valori dei parametri e le modifiche al valore dei parametri non devono influire sull'inizializzazione eseguita in questi metodi. Ad esempio, il caricamento di opzioni statiche in un elenco a discesa che non cambia per la durata del componente e che non dipende dai valori dei parametri viene eseguito in uno di questi metodi del ciclo di vita. Se i valori dei parametri o le modifiche apportate ai valori dei parametri influiscono sullo stato del componente, usare OnParametersSet{Async} invece .

Questi metodi vengono richiamati quando il componente viene inizializzato dopo aver ricevuto i parametri iniziali in SetParametersAsync. Il metodo sincrono viene chiamato prima del metodo asincrono.

Se viene utilizzata l'inizializzazione sincrona dei componenti padre, l'inizializzazione padre viene garantita prima dell'inizializzazione del componente figlio. Se viene utilizzata l'inizializzazione asincrona dei componenti padre, l'ordine di completamento dell'inizializzazione dei componenti padre e figlio non può essere determinato perché dipende dal codice di inizializzazione in esecuzione.

Per un'operazione sincrona, eseguire l'override OnInitializeddi :

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}";
    }
}

Per eseguire un'operazione asincrona, eseguire l'override OnInitializedAsync e usare l'operatore await :

protected override async Task OnInitializedAsync()
{
    await ...
}

Se viene usata una classe base personalizzata con la logica di inizializzazione personalizzata, chiamare OnInitializedAsync sulla classe base:

protected override async Task OnInitializedAsync()
{
    await ...

    await base.OnInitializedAsync();
}

Non è necessario chiamare ComponentBase.OnInitializedAsync a meno che non venga usata una classe di base personalizzata con logica personalizzata. Per altre informazioni, vedere la sezione Metodi del ciclo di vita della classe base.

Blazor app che prerendere il contenuto sul server chiamano OnInitializedAsyncdue volte:

  • Una volta quando il rendering del componente viene inizialmente eseguito in modo statico come parte della pagina.
  • Seconda volta che il browser esegue il rendering del componente.

Per impedire l'esecuzione del codice OnInitializedAsync dello sviluppatore due volte durante la pre-esecuzione, vedere la sezione Riconnessione con stato dopo il prerendering . Il contenuto della sezione è incentrato su Blazor App Web e riconnessione con SignalRstato. Per mantenere lo stato durante l'esecuzione del codice di inizializzazione durante la pre-esecuzione, vedere Prerender ASP.NET Componenti di baseRazor.

Per impedire l'esecuzione del codice OnInitializedAsync dello sviluppatore due volte durante la pre-esecuzione, vedere la sezione Riconnessione con stato dopo il prerendering . Anche se il contenuto della sezione è incentrato sulla Blazor Server riconnessione con statoSignalR, lo scenario per la pre-esecuzione della pre-esecuzione nelle soluzioni ospitate Blazor WebAssembly (WebAssemblyPrerendered) prevede condizioni e approcci simili per impedire l'esecuzione di codice dello sviluppatore due volte. Per mantenere lo stato durante l'esecuzione del codice di inizializzazione durante la pre-esecuzione, vedere Prerender and integrate ASP.NET Core components (Prerender and integrate ASP.NET Core Razor components).

Mentre un'app Blazor esegue la pre-gestione, alcune azioni, ad esempio la chiamata a JavaScript (JS interoperabilità), non sono possibili. Potrebbe essere necessario eseguire il rendering dei componenti in modo diverso quando viene eseguito il pre-riavvio. Per altre informazioni, vedere la sezione Prerendering with JavaScript interop (Prerendering with JavaScript interop ).

Se i gestori eventi vengono forniti nel codice dello sviluppatore, annullare ilhook all'eliminazione. Per altre informazioni, vedere la sezione Eliminazione dei componenti conIAsyncDisposableIDisposable .

Usare il rendering in streaming con rendering statico lato server (SSR statico) o prerendering per migliorare l'esperienza utente per i componenti che eseguono attività asincrone a esecuzione prolungata in OnInitializedAsync per il rendering completo. Per altre informazioni, vedere Rendering dei componenti di ASP.NET CoreRazor.

Dopo aver impostato i parametri (OnParametersSet{Async})

OnParametersSet o OnParametersSetAsync vengono chiamati:

  • Dopo l'inizializzazione del componente in OnInitialized o OnInitializedAsync.

  • Quando il componente padre esegue il rerender e fornisce:

    • Tipi non modificabili noti o primitivi quando almeno un parametro è stato modificato.
    • Parametri tipizzato complesso. Il framework non è in grado di stabilire se i valori di un parametro tipizzato complesso sono stati modificati internamente, quindi il framework considera sempre il set di parametri come modificato quando sono presenti uno o più parametri tipizzati complessi.

    Per altre informazioni sulle convenzioni di rendering, vedere rendering dei componenti di Razor base ASP.NET.

Il metodo sincrono viene chiamato prima del metodo asincrono.

I metodi possono essere richiamati anche se i valori dei parametri non sono stati modificati. Questo comportamento sottolinea la necessità per gli sviluppatori di implementare logica aggiuntiva all'interno dei metodi per verificare se i valori dei parametri sono stati effettivamente modificati prima di inizializzare nuovamente i dati o lo stato dipendenti da tali parametri.

Per il componente di esempio seguente, passare alla pagina del componente in un URL:

  • Con una data di inizio ricevuta da StartDate: /on-parameters-set/2021-03-19
  • Senza una data di inizio, dove StartDate viene assegnato un valore dell'ora locale corrente: /on-parameters-set

Nota

In una route del componente non è possibile vincolare entrambi un DateTime parametro con il vincolo datetime di route e rendere il parametro facoltativo. Di conseguenza, il componente seguente OnParamsSet usa due @page direttive per gestire il routing con e senza un segmento di data specificato nell'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}).";
        }
    }
}

Il lavoro asincrono quando si applicano parametri e valori di proprietà deve verificarsi durante l'evento del OnParametersSetAsync ciclo di vita:

protected override async Task OnParametersSetAsync()
{
    await ...
}

Se viene usata una classe base personalizzata con la logica di inizializzazione personalizzata, chiamare OnParametersSetAsync sulla classe base:

protected override async Task OnParametersSetAsync()
{
    await ...

    await base.OnParametersSetAsync();
}

Non è necessario chiamare ComponentBase.OnParametersSetAsync a meno che non venga usata una classe di base personalizzata con logica personalizzata. Per altre informazioni, vedere la sezione Metodi del ciclo di vita della classe base.

Se i gestori eventi vengono forniti nel codice dello sviluppatore, annullare ilhook all'eliminazione. Per altre informazioni, vedere la sezione Eliminazione dei componenti conIAsyncDisposableIDisposable .

Per altre informazioni sui parametri e i vincoli di route, vedere ASP.NET routing e navigazione coreBlazor.

Per un esempio di implementazione SetParametersAsync manuale per migliorare le prestazioni in alcuni scenari, vedere ASP.NET Procedure consigliate per le prestazioni principaliBlazor.

Dopo il rendering del componente (OnAfterRender{Async})

OnAfterRender e OnAfterRenderAsync vengono richiamati dopo il rendering interattivo di un componente e l'interfaccia utente ha terminato l'aggiornamento(ad esempio, dopo l'aggiunta di elementi al DOM del browser). I riferimenti a elementi e componenti vengono popolati a questo punto. Usare questa fase per eseguire passaggi di inizializzazione aggiuntivi con il contenuto sottoposto a rendering, ad esempio JS le chiamate di interoperabilità che interagiscono con gli elementi DOM sottoposti a rendering. Il metodo sincrono viene chiamato prima del metodo asincrono.

Questi metodi non vengono richiamati durante il prerendering o il rendering statico lato server (SSR statico) nel server perché tali processi non sono collegati a un DOM del browser attivo e sono già stati completati prima dell'aggiornamento del DOM.

Per OnAfterRenderAsync, il componente non esegue automaticamente il rerender dopo il completamento di qualsiasi operazione restituita Task per evitare un ciclo di rendering infinito.

OnAfterRender e OnAfterRenderAsync vengono chiamati dopo il completamento del rendering di un componente. I riferimenti a elementi e componenti vengono popolati a questo punto. Usare questa fase per eseguire passaggi di inizializzazione aggiuntivi con il contenuto sottoposto a rendering, ad esempio JS le chiamate di interoperabilità che interagiscono con gli elementi DOM sottoposti a rendering. Il metodo sincrono viene chiamato prima del metodo asincrono.

Questi metodi non vengono richiamati durante il prerendering perché il prerendering non è collegato a un DOM del browser live ed è già stato completato prima dell'aggiornamento del DOM.

Per OnAfterRenderAsync, il componente non esegue automaticamente il rerender dopo il completamento di qualsiasi operazione restituita Task per evitare un ciclo di rendering infinito.

Parametro firstRender per OnAfterRender e OnAfterRenderAsync:

  • È impostato sulla true prima volta che viene eseguito il rendering dell'istanza del componente.
  • Può essere usato per garantire che il lavoro di inizializzazione venga eseguito una sola volta.

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'esempio genera l'output AfterRender.razor seguente nella console quando viene caricata la pagina e il pulsante è selezionato:

OnAfterRender: firstRender = True
HandleClick called
OnAfterRender: firstRender = False

Il lavoro asincrono subito dopo il rendering deve verificarsi durante l'evento del OnAfterRenderAsync ciclo di vita:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    ...
}

Se viene usata una classe base personalizzata con la logica di inizializzazione personalizzata, chiamare OnAfterRenderAsync sulla classe base:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    ...

    await base.OnAfterRenderAsync(firstRender);
}

Non è necessario chiamare ComponentBase.OnAfterRenderAsync a meno che non venga usata una classe di base personalizzata con logica personalizzata. Per altre informazioni, vedere la sezione Metodi del ciclo di vita della classe base.

Anche se si restituisce un oggetto Task da OnAfterRenderAsync, il framework non pianifica un ulteriore ciclo di rendering per il componente al termine dell'attività. Ciò consente di evitare un ciclo di rendering infinito. Questo è diverso dagli altri metodi del ciclo di vita, che pianificano un ciclo di rendering successivo al completamento di un oggetto restituito Task .

OnAfterRender e OnAfterRenderAsyncnon vengono chiamati durante il processo di pre-esecuzione nel server. I metodi vengono chiamati quando il rendering del componente viene eseguito in modo interattivo dopo il prerendering. Quando l'app esegue il prerenders:

  1. Il componente viene eseguito sul server per produrre un markup HTML statico nella risposta HTTP. Durante questa fase, OnAfterRender e OnAfterRenderAsync non vengono chiamati.
  2. Quando lo Blazor script (blazor.{server|webassembly|web}.js) viene avviato nel browser, il componente viene riavviato in modalità di rendering interattivo. Dopo il riavvio OnAfterRender di un componente e OnAfterRenderAsyncviene chiamato perché l'app non è più nella fase di pre-esecuzione.

Se i gestori eventi vengono forniti nel codice dello sviluppatore, annullare ilhook all'eliminazione. Per altre informazioni, vedere la sezione Eliminazione dei componenti conIAsyncDisposableIDisposable .

Metodi del ciclo di vita della classe base

Quando si esegue l'override Blazordei metodi del ciclo di vita di , non è necessario chiamare i metodi del ciclo di vita della classe base per ComponentBase. Tuttavia, un componente deve chiamare un metodo del ciclo di vita della classe base sottoposto a override se il metodo della classe di base contiene la logica che deve essere eseguita. I consumer di librerie chiamano in genere metodi del ciclo di vita della classe base quando ereditano una classe base perché le classi di base della libreria hanno spesso una logica del ciclo di vita personalizzata da eseguire. Se l'app usa una classe base da una libreria, consultare la documentazione della libreria per indicazioni.

Nell'esempio base.OnInitialized(); seguente viene chiamato per assicurarsi che venga eseguito il metodo della classe di OnInitialized base. Senza la chiamata, BlazorRocksBase2.OnInitialized non viene eseguita.

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!");
    }
}

Modifiche dello stato (StateHasChanged)

StateHasChanged notifica al componente che il relativo stato è stato modificato. Se applicabile, la chiamata StateHasChanged fa sì che il componente venga riabilito.

StateHasChanged viene chiamato automaticamente per EventCallback i metodi. Per altre informazioni sui callback degli eventi, vedere ASP.NET Gestione degli eventi coreBlazor.

Per altre informazioni sul rendering dei componenti e su quando chiamare StateHasChanged, incluso quando richiamarlo con ComponentBase.InvokeAsync, vedere ASP.NET rendering dei componenti principaliRazor.

Gestire azioni asincrone incomplete al rendering

Le azioni asincrone eseguite negli eventi del ciclo di vita potrebbero non essere state completate prima del rendering del componente. Gli oggetti potrebbero essere null o popolati in modo incompleto con dati durante l'esecuzione del metodo del ciclo di vita. Fornire la logica di rendering per verificare che gli oggetti siano inizializzati. Eseguire il rendering degli elementi dell'interfaccia utente segnaposto ,ad esempio un messaggio di caricamento, mentre gli oggetti sono null.

Nel componente seguente viene OnInitializedAsync eseguito l'override per fornire in modo asincrono i dati di classificazione dei film (movies). Quando movies è null, viene visualizzato un messaggio di caricamento all'utente. Al termine dell'oggetto Task restituito da OnInitializedAsync , il componente viene riabilito con lo stato aggiornato.

<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);
    }
}

Gestione degli errori

Per informazioni sulla gestione degli errori durante l'esecuzione del metodo del ciclo di vita, vedere Gestire gli errori nelle app ASP.NET CoreBlazor.

Riconnessione con stato dopo il prerendering

Quando si esegue la pre-distribuzione nel server, viene inizialmente eseguito il rendering statico di un componente come parte della pagina. Dopo che il browser stabilisce una SignalR connessione al server, il rendering del componente viene eseguito di nuovo e interattivo. Se il metodo del ciclo di vita per l'inizializzazione OnInitialized{Async} del componente è presente, il metodo viene eseguito due volte:

  • Quando il componente viene prerenderato in modo statico.
  • Dopo aver stabilito la connessione al server.

Ciò può comportare una modifica notevole dei dati visualizzati nell'interfaccia utente quando il rendering del componente viene infine eseguito. Per evitare questo comportamento, passare un identificatore per memorizzare nella cache lo stato durante la prerendering e recuperare lo stato dopo la pre-esecuzione del prerendering.

Il codice seguente illustra un WeatherForecastService oggetto che evita la modifica nella visualizzazione dei dati a causa del prerendering. Il valore atteso Delay (await Task.Delay(...)) simula un breve ritardo prima di restituire i dati dal GetForecastAsync metodo .

Aggiungere IMemoryCache servizi con AddMemoryCache nella raccolta di servizi nel file dell'app Program :

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();
        });
    }
}

Per altre informazioni su , vedere ASP.NET Linee guida di baseBlazorSignalR.RenderMode

Il contenuto di questa sezione è incentrato su Blazor App Web e riconnessione con SignalRstato. Per mantenere lo stato durante l'esecuzione del codice di inizializzazione durante la pre-esecuzione, vedere Prerender ASP.NET Componenti di baseRazor.

Anche se il contenuto di questa sezione è incentrato SignalRsulla Blazor Server riconnessione con stato e sulla riconnessione con stato, lo scenario per la prerendering nelle soluzioni ospitate Blazor WebAssembly (WebAssemblyPrerendered) prevede condizioni e approcci simili per impedire l'esecuzione di codice dello sviluppatore due volte. Per mantenere lo stato durante l'esecuzione del codice di inizializzazione durante la pre-esecuzione, vedere Prerender and integrate ASP.NET Core components (Prerender and integrate ASP.NET Core Razor components).

Prerendering con interoperabilità JavaScript

Questa sezione si applica alle app lato server che prerendere Razor i componenti. La prerendering è descritta nei componenti Prerender ASP.NET CoreRazor.

Nota

Lo spostamento interno per il routing interattivo in Blazor App Web non comporta la richiesta di nuovo contenuto della pagina dal server. Di conseguenza, il prerendering non si verifica per le richieste di pagina interne. Se l'app adotta il routing interattivo, eseguire un ricaricamento a pagina completa per esempi di componenti che illustrano il comportamento di pre-esecuzione. Per altre informazioni, vedere Prerender ASP.NET Componenti di baseRazor.

Questa sezione si applica alle app lato server e alle app ospitate Blazor WebAssembly che prerendere Razor i componenti. La prerendering è descritta in Prerender e integra ASP.NET componenti coreRazor.

Mentre un'app esegue la pre-gestione, alcune azioni, ad esempio la chiamata a JavaScript (JS), non sono possibili.

Per l'esempio seguente, la setElementText1 funzione viene chiamata con JSRuntimeExtensions.InvokeVoidAsync e non restituisce un valore.

Nota

Per indicazioni generali sulla JS posizione e i suggerimenti per le app di produzione, vedere Posizione JavaScript nelle app ASP.NET CoreBlazor.

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

Avviso

L'esempio precedente modifica direttamente il DOM solo a scopo dimostrativo. La modifica diretta del DOM con JS non è consigliata nella maggior parte degli scenari perché JS può interferire con Blazoril rilevamento delle modifiche. Per altre informazioni, vedere ASP.NET Core JavaScript interoperabilità (interoperabilità).For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

L'evento OnAfterRender{Async} del ciclo di vita non viene chiamato durante il processo di pre-esecuzione del processo nel server. Eseguire l'override del OnAfterRender{Async} metodo per ritardare JS le chiamate di interoperabilità fino a quando il rendering del componente viene eseguito e interattivo nel client dopo la pre-esecuzione del pre-riavvio.

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");
        }
    }
}

Nota

L'esempio precedente inquina il client con funzioni globali. Per un approccio migliore nelle app di produzione, vedere Isolamento JavaScript nei moduli JavaScript.

Esempio:

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

Il componente seguente illustra come usare JS l'interoperabilità come parte della logica di inizializzazione di un componente in modo compatibile con la prerendering. Il componente mostra che è possibile attivare un aggiornamento del rendering dall'interno OnAfterRenderAsyncdi . Lo sviluppatore deve prestare attenzione per evitare di creare un ciclo infinito in questo scenario.

Per l'esempio seguente, la setElementText2 funzione viene chiamata con IJSRuntime.InvokeAsync e restituisce un valore.

Nota

Per indicazioni generali sulla JS posizione e i suggerimenti per le app di produzione, vedere Posizione JavaScript nelle app ASP.NET CoreBlazor.

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

Avviso

L'esempio precedente modifica direttamente il DOM solo a scopo dimostrativo. La modifica diretta del DOM con JS non è consigliata nella maggior parte degli scenari perché JS può interferire con Blazoril rilevamento delle modifiche. Per altre informazioni, vedere ASP.NET Core JavaScript interoperabilità (interoperabilità).For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

Dove JSRuntime.InvokeAsync viene chiamato , viene ElementReference usato solo in OnAfterRenderAsync e non in alcun metodo del ciclo di vita precedente perché non è presente alcun elemento DOM HTML fino a quando non viene eseguito il rendering del componente.

StateHasChangedviene chiamato per ripristinare il componente con il nuovo stato ottenuto dalla JS chiamata di interoperabilità . Per altre informazioni, vedere rendering dei componenti di Razor base ASP.NET. Il codice non crea un ciclo infinito perché StateHasChanged viene chiamato solo quando data è 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();
        }
    }
}

Nota

L'esempio precedente inquina il client con funzioni globali. Per un approccio migliore nelle app di produzione, vedere Isolamento JavaScript nei moduli JavaScript.

Esempio:

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

Eliminazione dei componenti con IDisposable e IAsyncDisposable

Se un componente implementa IDisposable, IAsyncDisposableo entrambi, il framework chiama l'eliminazione delle risorse quando il componente viene rimosso dall'interfaccia utente. Lo smaltimento può verificarsi in qualsiasi momento, incluso durante l'inizializzazione dei componenti.

I componenti non devono implementare IDisposable e IAsyncDisposable contemporaneamente. Se entrambi sono implementati, il framework esegue solo l'overload asincrono.

Il codice dello sviluppatore deve garantire che IAsyncDisposable il completamento delle implementazioni non richiede molto tempo.

Eliminazione dei riferimenti agli oggetti di interoperabilità JavaScript

Esempi in tutti gli articoli di interoperabilità JavaScript (JS) illustrano i modelli di eliminazione di oggetti tipici:

JS I riferimenti agli oggetti di interoperabilità vengono implementati come mappa con chiave da un identificatore sul lato della JS chiamata di interoperabilità che crea il riferimento. Quando l'eliminazione di oggetti viene avviata da .NET o JS lato, Blazor rimuove la voce dalla mappa e l'oggetto può essere sottoposto a Garbage Collection purché non sia presente alcun altro riferimento sicuro all'oggetto.

Eliminare sempre gli oggetti creati sul lato .NET per evitare perdite di memoria gestita .NET.

Attività di pulizia DOM durante l'eliminazione dei componenti

Per altre informazioni, vedere ASP.NET Core JavaScript interoperabilità (interoperabilità).For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

Per indicazioni su quando un circuito è disconnesso, vedere ASP.NET'interoperabilità JavaScript core ( interoperabilità).For guidance on JSDisconnectedException when a circuit is disconnected, see ASP.NET Core Blazor JavaScript interoperability (JS interop). Per indicazioni generali sulla gestione degli errori di interoperabilità JavaScript, vedere la sezione Interoperabilità JavaScript in Gestire gli errori nelle app ASP.NET CoreBlazor.

Sincrono IDisposable

Per le attività di eliminazione sincrone, usare IDisposable.Dispose.

Componente seguente:

  • Implementa IDisposable con la @implementsRazor direttiva .
  • Elimina , objovvero un tipo che implementa IDisposable.
  • Viene eseguito un controllo Null perché obj viene creato in un metodo del ciclo di vita (non visualizzato).
@implements IDisposable

...

@code {
    ...

    public void Dispose()
    {
        obj?.Dispose();
    }
}

Se un singolo oggetto richiede l'eliminazione, è possibile usare un'espressione lambda per eliminare l'oggetto quando Dispose viene chiamato . L'esempio seguente viene visualizzato nell'articolo sul rendering dei componenti di Razor base ASP.NET e illustra l'uso di un'espressione lambda per l'eliminazione di un oggetto 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();
}

Nota

Nell'esempio precedente, la chiamata a viene sottoposta a StateHasChanged wrapping da una chiamata a ComponentBase.InvokeAsync perché il callback viene richiamato all'esterno del contesto di Blazorsincronizzazione. Per altre informazioni, vedere Rendering dei componenti di ASP.NET CoreRazor.

Se l'oggetto viene creato in un metodo del ciclo di vita, ad esempio OnInitialized{Async}, verificare la presenza null di prima di chiamare 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();
}

Per altre informazioni, vedi:

Asincrona IAsyncDisposable

Per le attività di eliminazione asincrone, usare IAsyncDisposable.DisposeAsync.

Componente seguente:

  • Implementa IAsyncDisposable con la @implementsRazor direttiva .
  • Elimina , objovvero un tipo non gestito che implementa IAsyncDisposable.
  • Viene eseguito un controllo Null perché obj viene creato in un metodo del ciclo di vita (non visualizzato).
@implements IAsyncDisposable

...

@code {
    ...

    public async ValueTask DisposeAsync()
    {
        if (obj is not null)
        {
            await obj.DisposeAsync();
        }
    }
}

Per altre informazioni, vedi:

Assegnazione di null a oggetti eliminati

In genere, non è necessario assegnare null agli oggetti eliminati dopo aver chiamato/DisposeDisposeAsync . I rari casi per l'assegnazione null includono quanto segue:

  • Se il tipo dell'oggetto non è implementato correttamente e non tollera le chiamate ripetute a Dispose/DisposeAsync, assegnare null dopo l'eliminazione per ignorare normalmente altre chiamate a .Dispose/DisposeAsync
  • Se un processo di lunga durata continua a contenere un riferimento a un oggetto eliminato, l'assegnazione null consente al Garbage Collector di liberare l'oggetto nonostante il processo di lunga durata che contiene un riferimento.

Si tratta di scenari insoliti. Per gli oggetti implementati correttamente e si comportano normalmente, non c'è alcun punto da assegnare null agli oggetti eliminati. Nei rari casi in cui è necessario assegnare nullun oggetto , è consigliabile documentare il motivo e cercare una soluzione che impedisca la necessità di assegnare null.

StateHasChanged

Nota

La chiamata StateHasChanged in Dispose e DisposeAsync non è supportata. StateHasChanged potrebbe essere richiamato come parte dell'disinstallazione del renderer, quindi la richiesta di aggiornamenti dell'interfaccia utente a quel punto non è supportata.

Gestori eventi

Annullare sempre la sottoscrizione dei gestori eventi dagli eventi .NET. Gli esempi di modulo seguenti Blazor illustrano come annullare la sottoscrizione di un gestore eventi nel Dispose metodo :

  • Campo privato e approccio 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;
        }
    }
    
  • Approccio al metodo privato

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

Per altre informazioni, vedere la sezione Eliminazione dei componenti con IDisposable e IAsyncDisposable .

Per altre informazioni sul EditForm componente e sui moduli, vedere ASP.NET Cenni preliminari sui moduli di base Blazor e gli altri articoli relativi ai moduli nel nodo Moduli.

Funzioni, metodi ed espressioni anonime

Quando vengono usate funzioni, metodi o espressioni anonime, non è necessario implementare e annullare IDisposable la sottoscrizione dei delegati. Tuttavia, la mancata sottoscrizione di un delegato è un problema quando l'oggetto che espone l'evento dura la durata del componente che registra il delegato. In questo caso, si verifica una perdita di memoria perché il delegato registrato mantiene attivo l'oggetto originale. Pertanto, usare gli approcci seguenti solo quando si sa che il delegato dell'evento elimina rapidamente. In caso di dubbi sulla durata degli oggetti che richiedono l'eliminazione, sottoscrivere un metodo delegato ed eliminare correttamente il delegato come illustrato negli esempi precedenti.

  • Approccio anonimo al metodo lambda (eliminazione esplicita non richiesta):

    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);
    }
    
  • Approccio anonimo per l'espressione lambda (eliminazione esplicita non necessaria):

    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'esempio completo del codice precedente con espressioni lambda anonime viene visualizzato nell'articolo ASP.NET convalida dei moduli coreBlazor.

Per altre informazioni, vedere Pulizia delle risorse non gestite e degli argomenti che seguono l'implementazione dei Dispose metodi e DisposeAsync .

Lavoro in background annullabile

I componenti spesso eseguono operazioni in background a esecuzione prolungata, ad esempio l'esecuzione di chiamate di rete (HttpClient) e l'interazione con i database. È consigliabile arrestare il lavoro in background per risparmiare risorse di sistema in diverse situazioni. Ad esempio, le operazioni asincrone in background non vengono interrotte automaticamente quando un utente si allontana da un componente.

Altri motivi per cui gli elementi di lavoro in background potrebbero richiedere l'annullamento includono:

  • Un'attività in background in esecuzione è stata avviata con dati di input difettosi o parametri di elaborazione.
  • Il set corrente di elementi di lavoro in background in esecuzione deve essere sostituito con un nuovo set di elementi di lavoro.
  • La priorità delle attività attualmente in esecuzione deve essere modificata.
  • L'app deve essere arrestata per la ridistribuzione del server.
  • Le risorse del server diventano limitate e richiedono la riprogrammazione degli elementi di lavoro in background.

Per implementare un modello di lavoro in background annullabile in un componente:

Nell'esempio seguente :

  • await Task.Delay(5000, cts.Token); rappresenta il lavoro in background asincrono a esecuzione prolungata.
  • BackgroundResourceMethod rappresenta un metodo in background con esecuzione prolungata che non deve iniziare se viene Resource eliminato prima che venga chiamato il metodo .

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 eventi di riconnessione

Gli eventi del ciclo di vita dei componenti trattati in questo articolo operano separatamente dai gestori eventi di riconnessione lato server. Quando la SignalR connessione al client viene persa, vengono interrotti solo gli aggiornamenti dell'interfaccia utente. Gli aggiornamenti dell'interfaccia utente vengono ripresi quando viene ristabilita la connessione. Per altre informazioni sugli eventi e sulla configurazione del gestore del circuito, vedere ASP.NET Linee guida di baseBlazorSignalR.

Risorse aggiuntive

Gestire le eccezioni rilevate al di fuori del ciclo di vita di un Razor componente