rendering dei componenti di Razor base ASP.NET

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 Razor rendering dei componenti nelle app core ASP.NET Blazor , tra cui quando chiamare StateHasChanged per attivare manualmente un componente per il rendering.

Convenzioni di rendering per ComponentBase

I componenti devono eseguire il rendering quando vengono aggiunti per la prima volta alla gerarchia dei componenti da un componente padre. Questa è l'unica volta che un componente deve eseguire il rendering. I componenti possono eseguire il rendering in altri momenti in base alla logica e alle convenzioni.

Per impostazione predefinita, Razor i componenti ereditano dalla ComponentBase classe base, che contiene la logica per attivare il rerendering nei momenti seguenti:

I componenti ereditati da ComponentBase ignorano i rerender a causa degli aggiornamenti dei parametri se uno dei seguenti è vero:

  • Tutti i parametri provengono da un set di tipi noti† o da qualsiasi tipo primitivo che non è stato modificato dopo l'impostazione del set precedente di parametri.

    †Il Blazor framework usa un set di regole predefinite e controlli espliciti del tipo di parametro per il rilevamento delle modifiche. Queste regole e i tipi sono soggetti a modifiche in qualsiasi momento. Per altre informazioni, vedere l'API nell'origine ChangeDetectiondi riferimento ASP.NET Core.

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

  • L'override del metodo del ShouldRender componente restituisce false (l'implementazione predefinita ComponentBase restituisce truesempre ).

Controllare il flusso di rendering

Nella maggior parte dei casi, ComponentBase le convenzioni generano il subset corretto dei rerender dei componenti dopo che si verifica un evento. Gli sviluppatori in genere non devono fornire logica manuale per indicare al framework quali componenti eseguire il rerender e quando eseguirne il render. L'effetto complessivo delle convenzioni del framework è che il componente che riceve un riesendere un evento, che attiva in modo ricorsivo il rerendering dei componenti discendenti i cui valori dei parametri possono essere stati modificati.

Per altre informazioni sulle implicazioni sulle prestazioni delle convenzioni del framework e su come ottimizzare la gerarchia dei componenti di un'app per il rendering, vedere ASP.NET Procedure consigliate per le prestazioni principaliBlazor.

Rendering in streaming

Usare il rendering in streaming con rendering statico lato server (SSR statico) o pre-eseguire il provisioning per trasmettere gli aggiornamenti del contenuto nel flusso di risposta e migliorare l'esperienza utente per i componenti che eseguono attività asincrone a esecuzione prolungata per il rendering completo.

Si consideri, ad esempio, un componente che esegue una query di database a esecuzione prolungata o una chiamata API Web per eseguire il rendering dei dati quando la pagina viene caricata. In genere, le attività asincrone eseguite come parte del rendering di un componente lato server devono essere completate prima dell'invio della risposta sottoposta a rendering, che può ritardare il caricamento della pagina. Qualsiasi ritardo significativo nel rendering della pagina danneggia l'esperienza utente. Per migliorare l'esperienza utente, il rendering del flusso esegue inizialmente il rendering dell'intera pagina rapidamente con contenuto segnaposto durante l'esecuzione delle operazioni asincrone. Al termine delle operazioni, il contenuto aggiornato viene inviato al client nella stessa connessione di risposta e sottoposto a patch nel DOM.

Il rendering del flusso richiede al server di evitare il buffering dell'output. I dati di risposta devono essere trasmessi al client durante la generazione dei dati. Per gli host che applicano il buffering, il rendering del flusso degrada normalmente e la pagina viene caricata senza il rendering del flusso.

Per trasmettere gli aggiornamenti del contenuto quando si usa il rendering statico lato server (SSR statico) o il prerendering, applicare l'attributo [StreamRendering(true)] al componente. Il rendering in streaming deve essere abilitato in modo esplicito perché gli aggiornamenti trasmessi possono causare lo spostamento del contenuto nella pagina. I componenti senza l'attributo adottano automaticamente il rendering del flusso se il componente padre usa la funzionalità . Passare false all'attributo in un componente figlio per disabilitare la funzionalità in quel punto e più in basso nel sottoalbero del componente. L'attributo è funzionale quando viene applicato ai componenti forniti da una libreria di Razor classi.

L'esempio seguente si basa sul Weather componente in un'app creata dal modello di Blazor progetto app Web. La chiamata a Task.Delay simula il recupero asincrono dei dati meteo. Il componente esegue inizialmente il rendering del contenuto segnaposto ("Loading...") senza attendere il completamento del ritardo asincrono. Quando il ritardo asincrono viene completato e viene generato il contenuto dei dati meteo, il contenuto viene trasmesso alla risposta e sottoposto a patch nella tabella delle previsioni meteo.

Weather.razor:

@page "/weather"
@attribute [StreamRendering(true)]

...

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        ...
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    ...

    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        await Task.Delay(500);

        ...

        forecasts = ...
    }
}

Eliminare l'aggiornamento dell'interfaccia utente (ShouldRender)

ShouldRender viene chiamato ogni volta che viene eseguito il rendering di un componente. Eseguire l'override ShouldRender per gestire l'aggiornamento dell'interfaccia utente. Se l'implementazione restituisce true, l'interfaccia utente viene aggiornata.

Anche se ShouldRender viene sottoposto a override, il componente viene sempre sottoposto a rendering iniziale.

ControlRender.razor:

@page "/control-render"

<PageTitle>Control Render</PageTitle>

<h1>Control Render Example</h1>

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}

Per altre informazioni sulle procedure consigliate per le prestazioni relative a ShouldRender, vedere ASP.NET Procedure consigliate per le prestazioni principaliBlazor.

Quando chiamare StateHasChanged

La chiamata StateHasChanged consente di attivare un rendering in qualsiasi momento. Tuttavia, prestare attenzione a non chiamare StateHasChanged inutilmente, che è un errore comune che impone costi di rendering non necessari.

Il codice non deve chiamare StateHasChanged quando:

  • Gestione di routine degli eventi, sia in modo sincrono che asincrono, poiché ComponentBase attiva un rendering per la maggior parte dei gestori eventi di routine.
  • Implementazione della logica tipica del ciclo di vita, ad esempio OnInitialized o OnParametersSetAsync, sia in modo sincrono che asincrono, poiché ComponentBase attiva un rendering per gli eventi tipici del ciclo di vita.

Tuttavia, potrebbe essere opportuno chiamare StateHasChanged nei casi descritti nelle sezioni seguenti di questo articolo:

Un gestore asincrono prevede più fasi asincrone

A causa del modo in cui le attività vengono definite in .NET, un ricevitore di un Task può osservare solo il completamento finale, non gli stati asincroni intermedi. Pertanto, ComponentBase può attivare il rerendering solo quando Task viene restituito per la prima volta e al termine dell'ultima Task operazione. Il framework non è in grado di eseguire il rerendere un componente in altri punti intermedi, ad esempio quando un restituisce IAsyncEnumerable<T>dati in una serie di elementi intermediTask. Se si vuole eseguire il rerender in punti intermedi, chiamare StateHasChanged in questi punti.

Si consideri il componente seguente CounterState1 , che aggiorna il conteggio quattro volte ogni volta che viene eseguito il IncrementCount metodo:

  • I rendering automatici vengono eseguiti dopo il primo e l'ultimo incremento di currentCount.
  • I rendering manuali vengono attivati dalle chiamate a StateHasChanged quando il framework non attiva automaticamente i rerender nei punti di elaborazione intermedi in cui currentCount viene incrementato.

CounterState1.razor:

@page "/counter-state-1"

<PageTitle>Counter State 1</PageTitle>

<h1>Counter State Example 1</h1>

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}

Ricezione di una chiamata da un elemento esterno al sistema di gestione degli eventi e rendering Blazor

ComponentBase conosce solo i propri metodi del ciclo di vita e Blazorgli eventi attivati. ComponentBase non conosce altri eventi che possono verificarsi nel codice. Ad esempio, tutti gli eventi C# generati da un archivio dati personalizzato sono sconosciuti a Blazor. Affinché tali eventi attivino il rerendering per visualizzare i valori aggiornati nell'interfaccia utente, chiamare StateHasChanged.

Si consideri il componente seguente CounterState2 che usa System.Timers.Timer per aggiornare un conteggio a intervalli regolari e le chiamate StateHasChanged per aggiornare l'interfaccia utente:

  • OnTimerCallback viene eseguito all'esterno di qualsiasi Blazorflusso di rendering gestito o notifica degli eventi. Pertanto, OnTimerCallback deve chiamare StateHasChanged perché Blazor non riconosce le modifiche apportate a currentCount nel callback.
  • Il componente implementa IDisposable, dove Timer viene eliminato quando il framework chiama il Dispose metodo . Per altre informazioni, vedere Ciclo di vita dei componenti di ASP.NET Core Razor.

Poiché il callback viene richiamato all'esterno del contesto di Blazorsincronizzazione, il componente deve eseguire il wrapping della logica di OnTimerCallback in ComponentBase.InvokeAsync per spostarlo nel contesto di sincronizzazione del renderer. Equivale al marshalling al thread dell'interfaccia utente in altri framework dell'interfaccia utente. StateHasChanged può essere chiamato solo dal contesto di sincronizzazione del renderer e genera un'eccezione in caso contrario:

System.InvalidOperationException: 'Il thread corrente non è associato al Dispatcher. Usare InvokeAsync() per passare all'esecuzione del Dispatcher durante l'attivazione del rendering o dello stato del componente".

CounterState2.razor:

@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<PageTitle>Counter State 2</PageTitle>

<h1>Counter State Example 2</h1>

<p>
    This counter demonstrates <code>Timer</code> disposal.
</p>

<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();
}
@page "/counter-state-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 = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-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 = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-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 = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-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 = 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();
}

Per eseguire il rendering di un componente all'esterno del sottoalbero rerendered da un determinato evento

L'interfaccia utente potrebbe comportare:

  1. Invio di un evento a un componente.
  2. Modifica di uno stato.
  3. Rerendering di un componente completamente diverso che non è un discendente del componente che riceve l'evento.

Un modo per gestire questo scenario consiste nel fornire una classe di gestione dello stato, spesso come servizio di inserimento delle dipendenze ,inserito in più componenti. Quando un componente chiama un metodo sul gestore dello stato, il gestore dello stato genera un evento C# che viene quindi ricevuto da un componente indipendente.

Per gli approcci alla gestione dello stato, vedere le risorse seguenti:

Per l'approccio di gestione dello stato, gli eventi C# si trovano all'esterno della pipeline di Blazor rendering. Chiamare StateHasChanged su altri componenti che si desidera eseguire il rerender in risposta agli eventi del gestore dello stato.

L'approccio di gestione dello stato è simile al caso precedente con System.Timers.Timer nella sezione precedente. Poiché lo stack di chiamate di esecuzione rimane in genere nel contesto di sincronizzazione del renderer, la chiamata InvokeAsync non è normalmente necessaria. La chiamata InvokeAsync è necessaria solo se la logica esegue l'escape del contesto di sincronizzazione, ad esempio la chiamata ContinueWith a un Task oggetto o l'attesa di con TaskConfigureAwait(false). Per altre informazioni, vedere la sezione Ricezione di una chiamata da un elemento esterno al sistema di gestione degli eventi e renderingBlazor.

Indicatore di stato del caricamento di WebAssembly per Blazor App Web

Un indicatore di stato di caricamento non è presente in un'app creata dal Blazor modello di progetto app Web. È prevista una nuova funzionalità indicatore di stato di caricamento per una versione futura di .NET. Nel frattempo, un'app può adottare codice personalizzato per creare un indicatore di stato di caricamento. Per altre informazioni, vedere ASP.NET avvio di CoreBlazor.