ASP.NET Renderização de componentes do Core Razor
Observação
Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.
Advertência
Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.
Importante
Estas informações referem-se a um produto de pré-lançamento que pode ser substancialmente modificado antes de ser lançado comercialmente. A Microsoft não oferece garantias, expressas ou implícitas, em relação às informações fornecidas aqui.
Para a versão atual, consulte a versão .NET 9 deste artigo.
Este artigo explica Razor renderização de componentes em aplicativos ASP.NET Core Blazor, incluindo quando chamar StateHasChanged para acionar manualmente um componente para renderizar.
Convenções de renderização para ComponentBase
Os componentes devem renderizados quando são adicionados pela primeira vez à hierarquia de componentes por um componente pai. Este é o único momento que um componente deve renderizar. Os componentes podem ser renderizados em outros momentos de acordo com a sua própria lógica e convenções.
Razor componentes herdam da classe base ComponentBase, que contém lógica para disparar a rerenderização nos seguintes momentos:
- Depois de aplicar um conjunto atualizado de parâmetros de um componente pai.
- Depois de aplicar um valor em cascata atualizado para o parâmetro .
- Após a notificação de um evento e a invocação de um dos seus próprios manipuladores de eventos .
- Após uma chamada para o seu próprio método StateHasChanged (consulte ASP.NET Core do ciclo de vida do componente Razor). Para obter orientações sobre como evitar a substituição de parâmetros de componente filho aquando da chamada de StateHasChanged em um componente pai, consulte Evitar a substituição de parâmetros no ASP.NET Core Blazor.
Os componentes herdados do ComponentBase ignoram rerenderizações devido a atualizações de parâmetros se uma das seguintes opções for verdadeira:
Todos os parâmetros são de um conjunto de tipos conhecidos† ou qualquer tipo primitivo que não mudou desde que o conjunto anterior de parâmetros foi definido.
†A estrutura Blazor usa um conjunto de regras internas e verificações explícitas de tipo de parâmetro para deteção de alterações. Estas regras e os tipos estão sujeitos a alterações a qualquer momento. Para obter mais informações, consulte a API
ChangeDetection
na fonte de referência do ASP.NET Core.Observação
Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).
A substituição do método
ShouldRender
do componente retornafalse
(a implementação deComponentBase
padrão sempre retornatrue
).
Controle o fluxo de renderização
Na maioria dos casos, as ComponentBase convenções resultam no subconjunto correto de rerenderizações de componentes após a ocorrência de um evento. Os desenvolvedores geralmente não são obrigados a fornecer lógica manual para dizer à estrutura quais componentes renderizar novamente e quando rerenderizá-los. O efeito geral das convenções da estrutura é que o componente que recebe um evento rerenderiza-se, o que, por sua vez, dispara recursivamente a rerenderização de componentes descendentes cujos parâmetros podem ter sido alterados.
Para obter mais informações sobre as implicações de desempenho das convenções da estrutura e como otimizar a hierarquia de componentes de um aplicativo para renderização, consulte ASP.NET Core Blazor práticas recomendadas de desempenho.
Renderização de streaming
Use de renderização de streaming com renderização estática do lado do servidor (SSR estático) ou pré-renderização para transmitir atualizações de conteúdo no fluxo de resposta e melhorar a experiência do usuário para componentes que executam tarefas assíncronas de longa duração para renderização completa.
Por exemplo, considere um componente que faz uma consulta de banco de dados de longa execução ou uma chamada de API da Web para renderizar dados quando a página é carregada. Normalmente, as tarefas assíncronas executadas como parte da renderização de um componente do lado do servidor devem ser concluídas antes que a resposta renderizada seja enviada, o que pode atrasar o carregamento da página. Qualquer atraso significativo na renderização da página prejudica a experiência do usuário. Para melhorar a experiência de utilizador, a renderização de streaming inicialmente renderiza a página inteira rapidamente com conteúdo temporário enquanto as operações assíncronas são executadas. Após a conclusão das operações, o conteúdo atualizado é enviado para o cliente na mesma conexão de resposta e integrado no DOM.
A renderização de streaming requer que o servidor evite o armazenamento em buffer da saída. Os dados de resposta devem fluir para o cliente à medida que os dados são gerados. Para hosts que impõem buffer, a renderização de streaming se degrada normalmente e a página é carregada sem renderização de streaming.
Para transmitir atualizações de conteúdo, ao usar a renderização estática do lado do servidor (SSR estático) ou a pré-renderização, aplique o atributo [StreamRendering]
no .NET 9 ou posterior (use [StreamRendering(true)]
no .NET 8) ao componente. A renderização de streaming deve ser explicitamente habilitada porque as atualizações transmitidas podem fazer com que o conteúdo da página mude. Os componentes sem o atributo adotam automaticamente a renderização de streaming se o componente pai usar o recurso. Passe false
para o atributo em um componente filho para desativar o recurso nesse ponto e mais abaixo na subárvore do componente. O atributo é funcional quando aplicado a componentes fornecidos por uma biblioteca de classes Razor.
O exemplo a seguir é baseado no componente Weather
em um aplicativo criado a partir do modelo de projeto Blazor Web App. A chamada para Task.Delay simula a recuperação de dados meteorológicos de forma assíncrona. O componente inicialmente renderiza o conteúdo de espaço reservado ("Loading...
") sem esperar que o atraso assíncrono seja concluído. Quando o atraso assíncrono é concluído e o conteúdo dos dados meteorológicos é gerado, o conteúdo é transmitido para a resposta e integrado na tabela de previsão do tempo.
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 = ...
}
}
Suprimir a atualização da interface de utilizador (ShouldRender
)
ShouldRender é chamado cada vez que um componente é renderizado. Substitua ShouldRender para gerenciar a atualização da interface do usuário. Se a implementação retornar true
, a interface do usuário será atualizada.
Mesmo que ShouldRender seja substituído, o componente é sempre renderizado inicialmente.
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++;
}
}
Para obter mais informações sobre as práticas recomendadas de desempenho referentes ao ShouldRender, consulte as práticas recomendadas de desempenho de ASP.NET Core Blazor.
StateHasChanged
Chamar StateHasChanged enfileira uma nova renderização para ocorrer quando a thread principal da aplicação estiver livre.
Os componentes são enfileirados para renderização e não são enfileirados novamente se já houver uma nova renderização pendente. Se um componente chamar StateHasChanged cinco vezes seguidas em um loop, o componente só renderizará uma vez. Esse comportamento está codificado no ComponentBase, que verifica primeiro se o próprio já enfileirou uma rerenderização antes de adicionar outra.
Um componente pode renderizar várias vezes durante o mesmo ciclo, o que geralmente ocorre quando um componente tem filhos que interagem uns com os outros:
- Um componente pai renderiza vários filhos.
- Os componentes filho renderizam e acionam uma atualização no pai.
- Um componente pai é renderizado novamente com um novo estado.
Este design permite que StateHasChanged seja chamado quando necessário, sem o risco de introduzir renderização desnecessária. Você pode sempre assumir o controle desse comportamento em componentes individuais implementando IComponent e manipulando direta e manualmente quando o componente é renderizado.
Considere o seguinte método IncrementCount
que incrementa uma contagem, chama StateHasChangede incrementa a contagem novamente:
private void IncrementCount()
{
currentCount++;
StateHasChanged();
currentCount++;
}
Ao percorrer o código no depurador, você pode pensar que a contagem é atualizada na interface do usuário para a primeira execução currentCount++
imediatamente após StateHasChanged ser chamada. No entanto, a interface do usuário não mostra uma contagem atualizada nesse ponto devido ao processamento síncrono que está ocorrendo para a execução desse método. Não há oportunidade para o renderizador renderizar o componente até que o manipulador de eventos seja concluído. A interface do usuário exibe aumentos para execuções currentCount++
em uma única renderização.
Se você aguardar algo entre as currentCount++
linhas, a chamada aguardada dá ao renderizador a chance de renderizar. Isso levou alguns desenvolvedores a invocar Delay com um atraso de um milissegundo nos componentes deles para permitir que uma renderização ocorra, mas não recomendamos abrandar arbitrariamente uma aplicação para enfileirar uma renderização.
A melhor abordagem é aguardar Task.Yield, que força o componente a processar o código de forma assíncrona e a gerar imagem durante o lote atual, com uma segunda geração de imagem num lote separado após a tarefa gerada executar a continuação.
Considere o seguinte método IncrementCount
revisado, que atualiza a interface do usuário duas vezes porque a renderização enfileirada por StateHasChanged é executada quando a tarefa é gerada com a chamada para Task.Yield:
private async Task IncrementCount()
{
currentCount++;
StateHasChanged();
await Task.Yield();
currentCount++;
}
Tenha cuidado para não chamar StateHasChanged desnecessariamente, o que é um erro comum que impõe custos de renderização desnecessários. O código não precisa chamar StateHasChanged quando:
- Manipulação rotineira de eventos, seja de forma síncrona ou assíncrona, uma vez que ComponentBase aciona uma renderização para a maioria dos manipuladores de eventos de rotina.
- Implementação de lógica de ciclo de vida típica, como
OnInitialized
ouOnParametersSetAsync
, seja de forma síncrona ou assíncrona, uma vez que ComponentBase aciona uma renderização para eventos típicos do ciclo de vida.
No entanto, pode fazer sentido chamar StateHasChanged nos casos descritos nas seguintes seções deste artigo:
- Um manipulador assíncrono envolve várias fases assíncronas
- Receber uma chamada de algo externo ao sistema de renderização e manipulação de eventos Blazor
- Para renderizar o componente fora da subárvore que é reprocessado por um evento específico
Um manipulador assíncrono envolve várias fases assíncronas
Devido à maneira como as tarefas são definidas no .NET, um recetor de um Task só pode observar sua conclusão final, não estados assíncronos intermediários. Portanto, ComponentBase só pode acionar a rerenderização quando o Task é retornado pela primeira vez e quando o Task finalmente é concluído. A estrutura não pode saber quando voltar a renderizar um componente a outros pontos intermediários, como quando um IAsyncEnumerable<T>retorna dados numa série de Task
s intermediários. Se quiser renderizar novamente em pontos intermediários, ative StateHasChanged nesses pontos.
Considere o seguinte componente CounterState1
, que atualiza a contagem quatro vezes cada vez que o método IncrementCount
é executado:
- As renderizações automáticas ocorrem após o primeiro e o último incremento de
currentCount
. - As renderizações manuais são acionadas por chamadas para StateHasChanged quando a estrutura não aciona automaticamente rerenderizações em pontos de processamento intermediários onde
currentCount
é incrementada.
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
}
}
Receber uma chamada de algo externo ao sistema de renderização e manipulação de eventos Blazor
ComponentBase só conhece os seus próprios métodos de ciclo de vida e os eventos acionados por Blazor. ComponentBase não sabe sobre outros eventos que podem ocorrer no código. Por exemplo, quaisquer eventos C# gerados por um armazenamento de dados personalizado são desconhecidos para Blazor. Para que esses eventos acionem a rerenderização para exibir valores atualizados na interface do usuário, chame StateHasChanged.
Considere o seguinte componente CounterState2
que usa System.Timers.Timer para atualizar uma contagem em um intervalo regular e chama StateHasChanged para atualizar a interface do usuário:
-
OnTimerCallback
é executado fora de qualquer fluxo de renderização gerenciado pelo Blazorou notificação de evento. Portanto,OnTimerCallback
deve chamar StateHasChanged porque Blazor não está ciente das alterações emcurrentCount
na chamada de retorno. - O componente implementa IDisposable, onde o Timer é descartado quando a estrutura chama o método
Dispose
. Para obter mais informações, consulte ciclo de vida do componente do ASP.NET Core Razor.
Como o retorno de chamada é invocado fora do contexto de sincronização do Blazor, o componente deve envolver a lógica do OnTimerCallback
em ComponentBase.InvokeAsync, para movê-lo para o contexto de sincronização do renderizador. Isso é equivalente a encaminhar para o thread da UI em outros frameworks de UI.
StateHasChanged só pode ser chamado a partir do contexto de sincronização do renderizador e, caso contrário, lança uma exceção.
System.InvalidOperationException: 'O thread atual não está associado ao Dispatcher. Use InvokeAsync() para alternar a execução para o Dispatcher ao ativar a renderização ou o estado do 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();
}
Para renderizar um componente fora da subárvore que é reprocessado por um evento específico
A interface do usuário pode envolver:
- Enviar um evento para um componente.
- Mudando algum estado.
- Rerenderizar um componente completamente diferente que não é descendente do componente que recebe o evento.
Uma maneira de lidar com esse cenário é fornecer uma classe de de gerenciamento de estado
Para abordagens para gerenciar o estado, consulte os seguintes recursos:
- Vincular através de mais de dois componentes usando associações de dados.
- Passe dados através de uma hierarquia de componentes usando valores e parâmetros em cascata.
- Seção do Serviço de contêiner de estado na memória do lado do servidor (equivalente ao lado do cliente) do artigo de Gerenciamento de Estado .
Para a abordagem do gestor de estado, os eventos C# estão fora do pipeline de renderização Blazor. Chame StateHasChanged em outros componentes que deseja renderizar novamente em resposta aos eventos do gestor de estado.
A abordagem do gestor estatal é semelhante ao caso anterior com System.Timers.Timer na seção anterior. Como a pilha de chamadas de execução normalmente permanece no contexto de sincronização do renderizador, a chamada InvokeAsync normalmente não é necessária. Chamar InvokeAsync só é necessário se a lógica escapar do contexto de sincronização, como chamar ContinueWith num Task ou esperar uma Task com ConfigureAwait(false)
. Para obter mais informações, consulte a seção Receber uma chamada de algo externo ao sistema de Blazor renderização e manipulação de eventos.
Indicador de progresso de carregamento do WebAssembly para Blazor Web Apps
Um indicador de progresso de carregamento não está presente em um aplicativo criado a partir do modelo de projeto Blazor Web App. Um novo recurso de indicador de progresso de carregamento está planejado para uma versão futura do .NET. Enquanto isso, um aplicativo pode adotar código personalizado para criar um indicador de progresso de carregamento. Para obter mais informações, consulte ASP.NET Core Blazor startup.