Condividi tramite


ASP.NET Core ciclo di vita dei componenti Razor

Nota

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

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 9 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 9 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 alla fonte di riferimento ComponentBase per integrare l'elaborazione di eventi personalizzati con l'elaborazione degli eventi del ciclo di vita di Blazor. 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 alla sorgente di riferimento .NET di solito 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, usa il menu 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 componente viene reso per la prima volta su una richiesta:
    • Creare l'istanza del componente.
    • Eseguire l'inserimento delle proprietà.
    • Chiamare OnInitialized{Async}. Se viene restituito un elemento incompleto Task, si attende Task e quindi il componente viene renderizzato nuovamente. Il metodo sincrono viene chiamato prima del metodo asincrono.
  2. Chiamare OnParametersSet{Async}. Se viene restituito un elemento incompleto Task, si attende Task e quindi il componente viene renderizzato nuovamente. Il metodo sincrono viene chiamato prima del metodo asincrono.
  3. Esegui il rendering per tutte le operazioni sincrone e completa Task.

Nota

Le azioni asincrone eseguite negli eventi del ciclo di vita potrebbero ritardare il rendering dei componenti o il display dei dati. Per altre informazioni, vedere la sezione Gestisci azioni asincrone incomplete durante il rendering più avanti in questo articolo.

Viene eseguito il rendering di un componente padre prima dei componenti figlio perché il rendering determina quali componenti figlio sono presenti. Se viene utilizzata l'inizializzazione sincrona del componente principale, è garantito che l'inizializzazione del componente principale venga completata 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 di eventi.
  2. Se viene restituito un elemento incompleto Task, si attende Task e quindi il componente viene renderizzato nuovamente.
  3. Esegui il rendering per tutte le operazioni sincrone e completa Task.

Elaborazione di eventi DOM

Il ciclo di vita Render:

  1. Evitare ulteriori operazioni di rendering sul componente quando vengono soddisfatte entrambe le condizioni seguenti:
    • Non è il primo rendering.
    • ShouldRender restituisce false.
  2. Creare la differenza dell'albero di rendering e renderizzare il componente.
  3. Attendere che il DOM si aggiorni.
  4. Chiamare OnAfterRender{Async}. Il metodo sincrono viene chiamato prima del metodo asincrono.

Ciclo di vita del rendering

Le chiamate degli sviluppatori a StateHasChanged comportano un rerender. Per altre informazioni, vedere Rendering dei componenti di ASP.NET CoreRazor.

Quiescenza durante l'esecuzione preliminare

Nelle app Blazor lato server, la prerendering attende l'inattività , il che significa che un componente non viene eseguito fino a quando tutti i componenti dell'albero di rendering non hanno completato il rendering. La quiescenza può causare ritardi notevoli nel rendering quando un componente esegue attività a esecuzione prolungata durante l'inizializzazione e altri metodi del ciclo di vita, causando un'esperienza utente scarsa. Per altre informazioni, vedere la sezione Gestisci azioni asincrone incomplete durante il rendering più avanti in questo articolo.

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 che SetParametersAsync viene chiamato. Eseguendo l'override del metodo SetParametersAsync, il codice dello sviluppatore può interagire direttamente con i parametri di ParameterView.

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

In genere, il codice deve chiamare il metodo della classe base (await base.SetParametersAsync(parameters);) quando si esegue l'override di SetParametersAsync. Negli scenari avanzati, il codice dello sviluppatore può interpretare i valori dei parametri in ingresso in qualsiasi modo richiesto non richiamando il metodo della classe base. Ad esempio, non è necessario assegnare i parametri in ingresso alle proprietà della classe . Tuttavia, è necessario fare riferimento alla fonte di riferimento quando si struttura il codice senza chiamare il metodo della classe base, perché esso chiama altri metodi del ciclo di vita e attiva il rendering in modo complesso.

Nota

I collegamenti della documentazione alla sorgente di riferimento .NET di solito 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, usa il menu 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 basarsi sulla logica di inizializzazione e rendering di ComponentBase.SetParametersAsync ma non elaborare i parametri in ingresso, è possibile passare un oggetto vuoto ParameterView al metodo della classe base:

await base.SetParametersAsync(ParameterView.Empty);

Se i gestori di eventi vengono forniti nel codice dello sviluppatore, scollegarli all'eliminazione. Per ulteriori informazioni, vedere eliminazione dei componenti di ASP.NET Core Razor.

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 componente visualizza il valore.

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 /{Param?} nel modello di route per ottenere il valore con TryGetValue e non con /{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?}"

<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, è garantito che l'inizializzazione del padre venga completata 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 di 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"

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

Un componente deve assicurarsi che sia in uno stato valido per il rendering quando OnInitializedAsync attende un Taskpotenzialmente incompleto. Se il metodo restituisce un Taskincompleto , la parte del metodo che completa in modo sincrono deve lasciare il componente in uno stato valido per il rendering. Per altre informazioni, vedere le osservazioni introduttive di ASP.NET Core Blazor contesto di sincronizzazione e ASP.NET Core smaltimento dei componenti Razor.

le app che prerenderizzano il contenuto sul server chiamano due volte:

  • Quando il componente viene inizialmente renderizzato staticamente come parte della pagina.
  • Seconda volta che il browser esegue il rendering del componente.

Per impedire che il codice dello sviluppatore in OnInitializedAsync venga eseguito due volte durante il prerendering, consultare la sezione Riconnessione con stato dopo il prerendering. Il contenuto della sezione è incentrato su Blazor Web App e sulla SignalRriconnessione con stato. Per preservare lo stato durante l'esecuzione del codice di inizializzazione durante il prerendering, vedere Componenti Prerender di ASP.NET Core.

Per impedire che il codice dello sviluppatore in OnInitializedAsync venga eseguito due volte durante il prerendering, consultare la sezione Riconnessione con stato dopo il prerendering. Anche se il contenuto della sezione è incentrato su Blazor Server e sulla SignalRriconnessione con stato, lo scenario per la prerenderizzazione nelle soluzioni ospitate Blazor WebAssembly (WebAssemblyPrerendered) prevede condizioni e approcci simili per impedire di eseguire due volte il codice dello sviluppatore. Per mantenere lo stato durante l'esecuzione del codice di inizializzazione durante il pre-rendering, vedere Integrare i componenti di ASP.NET Core Razor con MVC o le Razor Pagine.

Mentre un'app Blazor esegue la pre-renderizzazione, alcune azioni, come la chiamata a funzioni JavaScript (JS interop), non sono possibili. Potrebbe essere necessario eseguire il rendering dei componenti in modo diverso quando sono prerenderizzati. Per ulteriori informazioni, vedere la sezione Prerendering con l'interazione JavaScript.

Se i gestori di eventi vengono forniti nel codice dello sviluppatore, scollegarli all'eliminazione. Per ulteriori informazioni, vedere eliminazione dei componenti di ASP.NET Core Razor.

Usare il rendering in streaming insieme al rendering statico lato server (SSR statico) o prerendering per migliorare l'esperienza utente in caso di componenti che eseguono attività asincrone a lunga durata in OnInitializedAsync per completare il rendering. Per ulteriori informazioni, vedi le seguenti risorse:

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 ricalcola e fornisce:

    • Tipi non modificabili noti o primitivi quando almeno un parametro è stato modificato.
    • Parametri tipizzati complessi. 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 ulteriori informazioni sulle convenzioni di rendering, vedere Razor.

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 fornita 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 sia vincolare un DateTime parametro con il vincolo datetime di route sia 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}"

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

Un componente deve assicurarsi che sia in uno stato valido per il rendering quando OnParametersSetAsync attende un Taskpotenzialmente incompleto. Se il metodo restituisce un Taskincompleto , la parte del metodo che completa in modo sincrono deve lasciare il componente in uno stato valido per il rendering. Per altre informazioni, vedere le osservazioni introduttive di ASP.NET Core Blazor contesto di sincronizzazione e ASP.NET Core smaltimento dei componenti Razor.

Se i gestori di eventi vengono forniti nel codice dello sviluppatore, scollegarli all'eliminazione. Per ulteriori informazioni, vedere eliminazione dei componenti di ASP.NET Core Razor.

Se un componente eliminabile non usa un CancellationToken, OnParametersSet e OnParametersSetAsync devono verificare se il componente è stato eliminato. Se OnParametersSetAsync restituisce un Taskincompleto, il componente deve assicurarsi che la parte del metodo che completa in modo sincrono lasci il componente in uno stato valido per il rendering. Per altre informazioni, vedere le osservazioni introduttive di ASP.NET Core Blazor contesto di sincronizzazione e ASP.NET Core smaltimento dei componenti Razor.

Per ulteriori informazioni sui parametri e i vincoli di route, vedere ASP.NET Core Blazor routing e navigazione.

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

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 rerendering dopo il completamento di qualsiasi Task restituita 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 rerendering dopo il completamento di qualsiasi Task restituita 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("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("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 di ciclo di vita OnAfterRenderAsync:

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 una volta che un Task restituito è completato.

OnAfterRender e OnAfterRenderAsyncnon vengono chiamati durante il processo di pre-esecuzione nel server. I metodi vengono chiamati quando il componente viene reso interattivamente dopo il prerendering. Quando l'app esegue il prerendering:

  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 di un componente, OnAfterRender e OnAfterRenderAsyncsono chiamati perché l'app non è più nella fase di prerendering.

Se i gestori di eventi vengono forniti nel codice dello sviluppatore, scollegarli all'eliminazione. Per ulteriori informazioni, vedere eliminazione dei componenti di ASP.NET Core Razor.

Metodi del ciclo di vita della classe base

Quando si esegue l'override dei metodi del ciclo di vita di Blazor, 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 che è stato sottoposto a override nelle situazioni seguenti:

  • Quando viene eseguito l'override di ComponentBase.SetParametersAsync, await base.SetParametersAsync(parameters); viene generalmente richiamato perché il metodo della classe base chiama altri metodi del ciclo di vita e attiva il rendering in modo complesso. Per altre informazioni, vedere la sezione Quando i parametri sono impostati (SetParametersAsync).
  • Se il metodo della classe 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"
@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;

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 accoda un rerender che avviene quando il thread principale dell'app è libero.

StateHasChanged viene chiamato automaticamente per i metodi EventCallback. Per ulteriori informazioni sui callback degli eventi, vedere ASP.NET Core Blazor gestione degli eventi.

Per ulteriori informazioni sul rendering dei componenti di ASP.NET Core e su quando chiamare StateHasChanged, incluso quando invocarlo con ComponentBase.InvokeAsync, vedere il rendering dei componenti ASP.NET CoreRazor.

Gestire azioni asincrone incomplete durante il 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. Visualizzare gli elementi dell'interfaccia utente segnaposto, ad esempio un messaggio di caricamento, mentre gli oggetti sono null.

Nel seguente componente Slow, OnInitializedAsync viene sovrascritto per eseguire asincronamente un'attività a lungo termine. Mentre isLoading è true, viene visualizzato un messaggio di caricamento per l'utente. Al termine del Task restituito da OnInitializedAsync, il componente viene re-renderizzato con lo stato aggiornato, mostrando il messaggio "Finished!".

Slow.razor:

@page "/slow"

<h2>Slow Component</h2>

@if (isLoading)
{
    <div><em>Loading...</em></div>
}
else
{
    <div>Finished!</div>
}

@code {
    private bool isLoading = true;

    protected override async Task OnInitializedAsync()
    {
        await LoadDataAsync();
        isLoading = false;
    }

    private Task LoadDataAsync()
    {
        return Task.Delay(10000);
    }
}

Il componente precedente usa una variabile isLoading per visualizzare il messaggio di caricamento. Un approccio simile viene usato per un componente che carica i dati in una raccolta e verifica se la raccolta è null per presentare il messaggio di caricamento. Nell'esempio seguente viene controllata la raccolta movies per null, al fine di mostrare il messaggio di caricamento o la raccolta di film.

@if (movies == null)
{
    <p><em>Loading...</em></p>
}
else
{
    @* display movies *@
}

@code {
    private Movies[]? movies;

    protected override async Task OnInitializedAsync()
    {
        movies = await GetMovies();
    }
}

Il prerendering attende la quiescenza , ciò significa che un componente non viene renderizzato finché tutti i componenti dell'albero di rendering non hanno completato il rendering. Ciò significa che un messaggio di caricamento non viene visualizzato mentre il metodo OnInitializedAsync di un componente figlio esegue un'attività a lungo termine durante il prerendering. Per illustrare questo comportamento, posizionare il componente Slow precedente nel componente Home di un'app di test:

@page "/"

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<Slow />

Nota

Anche se gli esempi in questa sezione illustrano il metodo del ciclo di vita OnInitializedAsync, altri metodi del ciclo di vita eseguiti durante la prerendering possono ritardare il rendering finale di un componente. Solo OnAfterRender{Async} non viene eseguito nel corso del prerendering e non è soggetto a ritardi dovuti alla quiescenza.

Durante la pre-esecuzione, il componente Home non esegue il rendering finché non viene eseguito il rendering del componente Slow, che richiede dieci secondi. L'interfaccia utente è vuota durante questo periodo di dieci secondi e non è presente alcun messaggio di caricamento. Dopo il prerendering, viene eseguito il rendering del componente Home e viene visualizzato il messaggio di caricamento del componente Slow. Dopo dieci secondi, il componente Slow visualizza infine il messaggio completato.

Come illustrato nella dimostrazione precedente, la quiescenza durante il prerendering comporta un'esperienza utente scarsa. Per migliorare l'esperienza dell'utente, iniziare implementando rendering di streaming per evitare l'attesa del completamento dell'attività asincrona durante il prerendering.

Aggiungere l'attributo [StreamRendering] al componente Slow (usare [StreamRendering(true)] in .NET 8):

@attribute [StreamRendering]

Quando il componente Home esegue il prerendering, il componente Slow viene rapidamente visualizzato insieme al suo messaggio di caricamento. Il componente Home non attende dieci secondi affinché il componente Slow completi il rendering. Tuttavia, il messaggio di caricamento sostituisce il messaggio completato alla fine del prerendering mentre il componente viene reso definitivamente, il che provoca un altro ritardo di dieci secondi. Ciò si verifica perché il componente Slow esegue il rendering due volte, insieme a LoadDataAsync che viene eseguito due volte. Quando un componente accede a risorse, ad esempio servizi e database, l'esecuzione doppia delle chiamate al servizio e delle query di database crea un carico indesiderato sulle risorse dell'app.

Per risolvere il doppio rendering del messaggio di caricamento e la riesecuzione delle chiamate al servizio e al database, conservare lo stato prerenderato con PersistentComponentState per il rendering finale del componente, come illustrato negli aggiornamenti seguenti al componente Slow.

@page "/slow"
@attribute [StreamRendering]

<h2>Slow Component</h2>

@if (Data is null)
{
    <div><em>Loading...</em></div>
}
else
{
    <div>@Data</div>
}

@code {
    [SupplyParameterFromPersistentComponentState]
    public string? Data { get; set; }

    protected override async Task OnInitializedAsync()
    {
        Data ??= await LoadDataAsync();
    }

    private async Task<string> LoadDataAsync()
    {
        await Task.Delay(5000);
        return "Finished!";
    }
}
@page "/slow"
@attribute [StreamRendering]
@implements IDisposable
@inject PersistentComponentState ApplicationState

<h2>Slow Component</h2>

@if (data is null)
{
    <div><em>Loading...</em></div>
}
else
{
    <div>@data</div>
}

@code {
    private string? data;
    private PersistingComponentStateSubscription persistingSubscription;

    protected override async Task OnInitializedAsync()
    {
        if (!ApplicationState.TryTakeFromJson<string>(nameof(data), out var restored))
        {
            data = await LoadDataAsync();
        }
        else
        {
            data = restored!;
        }

        // Call at the end to avoid a potential race condition at app shutdown
        persistingSubscription = ApplicationState.RegisterOnPersisting(PersistData);
    }

    private Task PersistData()
    {
        ApplicationState.PersistAsJson(nameof(data), data);

        return Task.CompletedTask;
    }

    private async Task<string> LoadDataAsync()
    {
        await Task.Delay(5000);
        return "Finished!";
    }

    void IDisposable.Dispose()
    {
        persistingSubscription.Dispose();
    }
}

Combinando il rendering in streaming con lo stato persistente del componente:

  • I servizi e i database richiedono solo una singola chiamata per l'inizializzazione dei componenti.
  • I componenti eseguono rapidamente il rendering delle interfacce utente caricando i messaggi durante le attività a esecuzione prolungata per un'esperienza utente ottimale.

Per ulteriori informazioni, vedi le seguenti risorse:

:::moniker-end

La quiescenza durante la prerenderizzazione comporta una scarsa esperienza utente. Il ritardo può essere risolto nelle app destinate a .NET 8 o versioni successive con una funzionalità denominata rendering in streaming, in genere combinata con persistenza dello stato del componente durante il prerendering per evitare di attendere il completamento dell'attività asincrona. Nelle versioni di .NET precedenti a .NET 8, l'esecuzione di un'attività in background a esecuzione prolungata che carica i dati dopo il rendering finale può risolvere un lungo ritardo di rendering a causa della quiescenza.

Gestione degli errori

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

Riconnessione dello stato dopo il prerendering

Quando si esegue il prerendering sul server, un componente viene inizialmente renderizzato in modo statico come parte della pagina. Dopo che il browser stabilisce una SignalR connessione al server, il componente viene reso di nuovo visibile 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, utilizzare un identificatore per memorizzare nello stato della cache durante il prerendering e recuperare lo stato dopo il prerendering.

Il codice seguente dimostra un WeatherForecastService che evita la modifica della visualizzazione dei dati dovuta al prerendering. Il valore atteso Delay (await Task.Delay(...)) simula un breve ritardo prima di restituire i dati dal GetForecastAsync metodo .

Aggiungere IMemoryCache servizi utilizzando AddMemoryCache nella raccolta di servizi del 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;

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 ulteriori informazioni su RenderMode, vedere la guida di ASP.NET CoreBlazorSignalR.

Il contenuto di questa sezione è incentrato sui Blazor Web App e sulla riconnessione con statoSignalR. Per preservare lo stato durante l'esecuzione del codice di inizializzazione durante il prerendering, vedere Componenti Prerender di ASP.NET Core.

Anche se il contenuto di questa sezione è incentrato su Blazor Server e sulla SignalR riconnessione con stato, lo scenario per la prerenderizzazione 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 il pre-rendering, vedere Integrare i componenti di ASP.NET Core Razor con MVC o le Razor Pagine.

Pre-renderizzazione tramite interoperabilità JavaScript

Questa sezione si applica alle app lato server che prerenderizzano i componenti Razor. La prerenderizzazione è trattata in Prerender ASP.NET Core Razor components.

Nota

Lo spostamento interno per routing interattivo in Blazor Web App non comporta la richiesta al server di un nuovo contenuto della pagina. Di conseguenza, il prerendering non si verifica per le richieste di pagina interne. Se l'app adotta il routing interattivo, eseguire un ricaricamento completo della pagina per esempi di componenti che illustrano il comportamento di prerenderizzazione. Per ulteriori informazioni, vedere Componenti di ASP.NET Core di base.

Questa sezione si applica alle app lato server e alle app portale Blazor WebAssembly che prerenderizzano Razor i componenti. La prerenderizzazione è descritta in Integrare i componenti ASP.NET Core Razor con MVC o Razor pagine.

Durante la pre-esecuzione, la chiamata a JavaScript (JS) non è possibile. Nell'esempio seguente viene dimostrato come utilizzare l'interoperabilità JS come parte della logica di inizializzazione di un componente in un modo compatibile con la pre-renderizzazione.

La funzione seguente scrollElementIntoView :

window.scrollElementIntoView = (element) => {
  element.scrollIntoView();
  return element.getBoundingClientRect().top;
}

Dove IJSRuntime.InvokeAsync chiama la JS funzione nel codice del componente, ElementReference viene 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.

StateHasChanged (origine di riferimento) viene chiamato per accodare il rerendering del componente con il nuovo stato ottenuto dalla JS chiamata interop (per ulteriori informazioni, vedere ASP.NET Core Razor rendering dei componenti). Non viene creato un ciclo infinito perché StateHasChanged viene chiamato solo quando scrollPosition è null.

PrerenderedInterop.razor:

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

<PageTitle>Prerendered Interop</PageTitle>

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}

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

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:

  • È stata avviata un'attività in background con dati di input errati o parametri di elaborazione sbagliati.
  • L'insieme corrente di attività di lavoro in background in esecuzione deve essere sostituito con un nuovo insieme di attività di lavoro.
  • La priorità delle attività attualmente in esecuzione deve essere modificata.
  • L'app deve essere arrestata per il riposizionamento 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 :

  • Il lavoro asincrono in background a esecuzione prolungata è rappresentato da await Task.Delay(10000, cts.Token);.
  • 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"
@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;
        }
    }
}

Per visualizzare un indicatore di caricamento mentre è in corso il lavoro in background, usare l'approccio seguente.

Creare un componente indicatore di caricamento con un Loading parametro in grado di visualizzare il contenuto figlio in un oggetto RenderFragment. Per il Loading parametro :

ContentLoading.razor:

@if (Loading)
{
    <progress id="loadingIndicator" aria-label="Content loading…"></progress>
}
else
{
    @ChildContent
}

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    [Parameter]
    public bool Loading { get; set; }
}

Per caricare gli stili CSS per l'indicatore, aggiungere al contenuto <head> gli stili con il componente HeadContent. Per ulteriori informazioni, vedere Blazor.

@if (Loading)
{
    <!-- OPTIONAL ...
    <HeadContent>
        <style>
            ...
        </style>
    </HeadContent>
    -->
    <progress id="loadingIndicator" aria-label="Content loading…"></progress>
}
else
{
    @ChildContent
}

...

Avvolgere il markup del componente con il componente Razor e passare un valore a un campo C# al parametro ContentLoading quando il lavoro di inizializzazione viene eseguito dal componente.

<ContentLoading Loading="@loading">
    ...
</ContentLoading>

@code {
    private bool loading = true;
    ...

    protected override async Task OnInitializedAsync()
    {
        await LongRunningWork().ContinueWith(_ => loading = false);
    }

    ...
}

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 dei circuiti, vedere le linee guida di ASP.NET Core BlazorSignalR.

Risorse aggiuntive

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