rendering dei componenti Razor di ASP.NET Core
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 rendering dei componenti nelle app ASP.NET Core, inclusi i casi in cui chiamare StateHasChanged per attivare manualmente il rendering di un componente.
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.
Razor i componenti ereditano dalla ComponentBase classe di base, che contiene la logica per attivare il rerendering nei momenti seguenti:
- Dopo aver applicato un set aggiornato di parametri da un componente padre.
- Dopo aver applicato un valore aggiornato per un parametro a catena.
- Dopo la notifica di un evento e la chiamata di uno dei relativi gestori eventi.
- Dopo una chiamata al proprio metodo
(vedere il ciclo di vita del componente ASP.NET Core). Per indicazioni su come evitare la sovrascrittura dei parametri dei componenti figlio quando StateHasChanged viene chiamato in un componente padre, vedere Evitare la sovrascrittura dei parametri in ASP.NET Core Blazor.
I componenti ereditati da ComponentBase saltano i ri-render 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 ulteriori informazioni, consulta l'API nella documentazione di riferimento di ASP.NET Core
ChangeDetection
.Nota
I collegamenti della documentazione al codice sorgente di riferimento di .NET di solito caricano il ramo predefinito del repository, che rappresenta lo sviluppo attuale per la versione successiva di .NET. Per selezionare un tag per una versione specifica, utilizzare il menu a discesa Cambia rami o tag. 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).
La sovrascrittura del metodo del componente restituisce
false
(l'implementazione predefinitaComponentBase
restituisce sempretrue
).
Controllare il flusso di rendering
Nella maggior parte dei casi, le convenzioni ComponentBase generano il subset corretto delle ri-renderizzazioni dei componenti dopo che si verifica un evento. Gli sviluppatori di solito non sono tenuti a fornire una logica manuale per indicare al framework quali componenti ri-renderizzare e quando farlo. L'effetto complessivo delle convenzioni del framework è che il componente che riceve un evento rerenderizza se stesso, il che attiva ricorsivamente la renderizzazione dei componenti discendenti i cui valori di parametro potrebbero essere cambiati.
Per ulteriori informazioni sulle implicazioni delle convenzioni del framework sulle prestazioni e su come ottimizzare la gerarchia dei componenti di un'app per il rendering, vedere
Rendering in streaming
Usare il rendering in streaming con rendering statico lato server (SSR statico) o prerendering 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 in streaming esegue rapidamente il rendering dell'intera pagina con contenuto segnaposto mentre vengono eseguite 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 in streaming 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 in streaming degrada con eleganza e la pagina viene caricata senza rendering in streaming.
Per trasmettere gli aggiornamenti del contenuto quando si usa il rendering statico lato server (SSR statico) o il prerendering, applicare l'attributo [StreamRendering]
in .NET 9 o versione successiva (usare [StreamRendering(true)]
in .NET 8) 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 in modalità streaming se il componente padre utilizza questa 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 componente Loading...
") senza attendere il completamento del ritardo asincrono. Una volta completato il ritardo asincrono e generato il contenuto dei dati meteo, il contenuto viene inviato alla risposta e integrato nella tabella delle previsioni meteorologiche.
Weather.razor
:
@page "/weather"
@attribute [StreamRendering]
...
@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. Sovrascrivere ShouldRender per gestire l'aggiornamento dell'interfaccia utente. Se l'implementazione restituisce true
, l'interfaccia utente viene aggiornata.
Anche se ShouldRender viene sovrascritto, il componente viene sempre inizialmente sottoposto a rendering.
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() => shouldRender;
private void IncrementCount() => currentCount++;
}
@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() => 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 ulteriori informazioni sulle procedure consigliate per le prestazioni relative a
StateHasChanged
Chiamando StateHasChanged accoda un rerender da eseguire quando il thread principale dell'app è libero.
I componenti vengono accodati per il rendering e non vengono accodati di nuovo se è già presente un rerender in sospeso. Se un componente chiama StateHasChanged cinque volte in una riga in un ciclo, il componente viene eseguito una sola volta. Questo comportamento viene codificato in ComponentBase, che controlla prima di tutto se ha accodato un rerender prima di accodare uno aggiuntivo.
Un componente può eseguire il rendering più volte durante lo stesso ciclo, che in genere si verifica quando un componente ha elementi figlio che interagiscono tra loro:
- Un componente padre esegue il rendering di diversi componenti figli.
- I componenti figlio eseguono il rendering e attivano un aggiornamento nell'elemento padre.
- Un componente genitore viene renderizzato di nuovo con un nuovo stato.
Questo design consente di StateHasChanged venga chiamata quando necessario senza il rischio di introdurre render di troppo. È sempre possibile assumere il controllo di questo comportamento nei singoli componenti implementando IComponent direttamente e gestire manualmente quando il componente viene renderizzato.
Si consideri il metodo seguente IncrementCount
che incrementa un conteggio, chiama StateHasChangede incrementa nuovamente il conteggio:
private void IncrementCount()
{
currentCount++;
StateHasChanged();
currentCount++;
}
Eseguendo il codice nel debugger, si potrebbe pensare che il conteggio nell'interfaccia utente si aggiorni per la prima currentCount++
esecuzione immediatamente dopo che StateHasChanged viene chiamato. Tuttavia, l'interfaccia utente non mostra un conteggio aggiornato a quel punto a causa dell'elaborazione sincrona eseguita per l'esecuzione di questo metodo. Non è possibile che il renderer possa renderizzare il componente fino al termine della gestione dell'evento. L'interfaccia utente visualizza aumenti per entrambe le esecuzioni in un'unica rappresentazione.
Se si attende qualcosa tra le currentCount++
righe, la chiamata attesa offre al renderer la possibilità di eseguire il rendering. Ciò ha portato ad alcuni sviluppatori che chiamano Delay con un ritardo di un millisecondo nei relativi componenti per consentire l'esecuzione di un rendering, ma non è consigliabile rallentare arbitrariamente un'app per accodare un rendering.
L'approccio migliore consiste nell'attendere Task.Yield, che costringe il componente a elaborare il codice in modo asincrono e a eseguire il rendering durante il batch corrente, seguito da un secondo rendering in un batch separato dopo che il task "yielded" esegue la continuazione.
Si consideri il metodo modificato IncrementCount
seguente, che aggiorna l'interfaccia utente due volte perché il rendering accodato da StateHasChanged viene eseguito quando l'attività viene restituita con la chiamata a Task.Yield:
private async Task IncrementCount()
{
currentCount++;
StateHasChanged();
await Task.Yield();
currentCount++;
}
Fai attenzione a non chiamare StateHasChanged inutilmente, che è un errore comune che comporta 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
oOnParametersSetAsync
, 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
- Ricezione di una chiamata da qualcosa di esterno al sistema di rendering e gestione degli eventi Blazor
- Per eseguire il rendering di un componente all'esterno del sottoalbero rielaborato da un determinato evento.
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 la ri-renderizzazione solo quando Task viene restituito per la prima volta e quando Task viene completato definitivamente. Il framework non è in grado di eseguire nuovamente il rendering di un componente in altri punti intermedi, come quando restituisce dati in una serie di IAsyncEnumerable<T> 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"
<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 rendering e gestione degli eventi Blazor
ComponentBase conosce solo i propri metodi del ciclo di vita e gli eventi attivati da Blazor. 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 Blazor flusso di rendering gestito o notifica di eventi. Pertanto,OnTimerCallback
deve chiamare StateHasChanged perché Blazor non riconosce le modifiche apportate acurrentCount
nel callback. - Il componente implementa IDisposable, dove Timer viene eliminato quando il framework chiama il
Dispose
metodo . Per ulteriori informazioni, vedere eliminazione dei componenti di ASP.NET Core Razor.
Poiché il callback viene richiamato al di fuori del contesto di sincronizzazione di Blazor, il componente deve incapsulare la logica di OnTimerCallback
in ComponentBase.InvokeAsync per trasferirlo nel contesto di sincronizzazione del renderer. Questo equivale a invocare il thread dell'interfaccia utente in altri framework di 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 l'esecuzione al Dispatcher quando si attiva il rendering o lo 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
<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 al di fuori del sottoalbero ricaricato da un determinato evento.
L'interfaccia utente potrebbe comportare:
- Invio di un evento a un componente.
- Modificando uno stato.
- Nuovo rendering di un componente completamente diverso che non sia 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 dependency injection (DI), da iniettare 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:
- Eseguire il binding tra più di due componenti usando i data binding.
- Passare i dati in una gerarchia di componenti usando valori e parametri a catena.
- Sezione del servizio contenitore di stato in memoria lato server (equivalente lato client) dell'articolo Gestione dello stato.
Per l'approccio di gestione dello stato, gli eventi C# si trovano all'esterno della pipeline di rendering di Blazor. Chiamare StateHasChanged su altri componenti che si desidera eseguire il rerender in risposta agli eventi del gestore dello stato.
L'approccio del gestore di 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. È necessario chiamare InvokeAsync solo se la logica esce dal contesto di sincronizzazione, ad esempio chiamando ContinueWith su un Task o aspettando un Task con ConfigureAwait(false)
. Per ulteriori informazioni, consultare la sezione
Indicatore di stato del caricamento di WebAssembly per Blazor Web Apps
Un'app creata dal modello di progetto Blazor Web App non ha un indicatore di avanzamento del caricamento. È 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 ulteriori informazioni, vedere ASP.NET Core Blazor startup.