Ciclo de vida do componente Razor do ASP.NET Core
Observação
Esta não é a versão mais recente deste artigo. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Aviso
Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, confira .NET e a Política de Suporte do .NET Core. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Importante
Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.
Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Este artigo explica o ciclo de vida do componente Razor do ASP.NET Core e como usar os eventos de ciclo de vida.
Eventos de ciclo de vida
O componente Razor processa eventos de ciclo de vida do componente Razor em um conjunto de métodos de ciclo de vida síncronos e assíncronos. Os métodos de ciclo de vida podem ser substituídos para executar operações adicionais nos componentes durante a inicialização e a renderização do componente.
Este artigo simplifica o processamento de eventos do ciclo de vida do componente para esclarecer a lógica de estrutura complexa e não abrange todas as alterações feitas ao longo dos anos. Talvez seja necessário acessar a fonte de referência ComponentBase
para integrar o processamento de eventos personalizados ao processamento de eventos do ciclo de vida do Blazor. Os comentários de código na fonte de referência incluem comentários adicionais sobre o processamento de eventos do ciclo de vida que não são mostrados neste artigo ou na documentação da API.
Observação
Os links de documentação para a fonte de referência do .NET geralmente carregam o branch padrão do repositório, que representa o desenvolvimento atual da próxima versão do .NET. Para selecionar uma marca para uma versão específica, use a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).
Os diagramas simplificados a seguir ilustram o processamento de eventos do ciclo de vida do componente Razor. Os métodos do C# associados aos eventos de ciclo de vida são definidos com os exemplos nas seções a seguir deste artigo.
Eventos de ciclo de vida do componente:
- Se o componente estiver sendo renderizado pela primeira vez em uma solicitação:
- Crie a instância do componente.
- Execute a injeção de propriedade.
- Chame
OnInitialized{Async}
. Se um Task incompleto for retornado, o Task será aguardado e, em seguida, o componente será gerado novamente. O método síncrono é chamado antes do método assíncrono.
- Chame
OnParametersSet{Async}
. Se um Task incompleto for retornado, o Task será aguardado e, em seguida, o componente será gerado novamente. O método síncrono é chamado antes do método assíncrono. - Renderize para todos os trabalhos síncronos e Task completos.
Observação
As ações assíncronas executadas em eventos de ciclo de vida podem não ser concluídas antes que um componente seja renderizado. Para obter mais informações, confira a seção Manipular ações assíncronas incompletas na renderização mais adiante neste artigo.
Um componente pai é renderizado antes de seus componentes filhos, pois a renderização é o que determina quais filhos estão presentes. Se a inicialização do componente pai síncrono for usada, a inicialização pai será concluída primeiro. Se a inicialização de componente pai assíncrono for usada, a ordem de conclusão da inicialização do componente pai e filho não poderá ser determinada, pois depende do código de inicialização em execução.
Processamento de eventos DOM:
- O manipulador de eventos é executado.
- Se um Task incompleto for retornado, o Task será aguardado e, em seguida, o componente será gerado novamente.
- Renderize para todos os trabalhos síncronos e Task completos.
O ciclo de vida do Render
:
- Evite mais operações de renderização no componente quando ambas as seguintes condições forem atendidas:
- Não é a primeira renderização.
ShouldRender
retornafalse
.
- Crie a comparação da árvore de renderização (diferença) e renderize o componente.
- Aguarde o DOM para atualizar.
- Chame
OnAfterRender{Async}
. O método síncrono é chamado antes do método assíncrono.
As chamadas do desenvolvedor para StateHasChanged
resultam em uma nova renderização. Para saber mais, consulte Renderização de componentes de Razor no ASP.NET Core.
Quando os parâmetros são definidos (SetParametersAsync
)
SetParametersAsync define os parâmetros fornecidos pelo pai do componente na árvore de renderização ou nos parâmetros de rota.
O parâmetro do método ParameterView contém o conjunto de valores de parâmetro de componente para o componente sempre que SetParametersAsync é chamado. Ao substituir o método SetParametersAsync, o código do desenvolvedor pode interagir diretamente com os parâmetros do ParameterView.
A implementação padrão do SetParametersAsync define o valor de cada propriedade com o [Parameter]
ou atributo [CascadingParameter]
que tem um valor correspondente no ParameterView. Os parâmetros que não têm um valor correspondente em ParameterView são deixados inalterados.
Geralmente, seu código deve chamar o método de classe base (await base.SetParametersAsync(parameters);
) ao substituir SetParametersAsync. Nos cenários avançados, o código do desenvolvedor pode interpretar os valores dos parâmetros de entrada de qualquer maneira exigidos por não invocar o método de classe base. Por exemplo, não há requisitos para atribuir os parâmetros de entrada às propriedades da classe. Contudo, você deve se referir à ComponentBase
fonte de referência ao estruturar seu código sem chamar o método de classe base porque ele chama outros métodos de ciclo de vida e dispara a renderização de forma complexa.
Observação
Os links de documentação para a fonte de referência do .NET geralmente carregam o branch padrão do repositório, que representa o desenvolvimento atual da próxima versão do .NET. Para selecionar uma marca para uma versão específica, use a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).
Se você quiser contar com a lógica de inicialização e renderização de ComponentBase.SetParametersAsync, mas não processar os parâmetros de entrada, você terá a opção de passar uma ParameterView vazia para o método de classe base:
await base.SetParametersAsync(ParameterView.Empty);
Se os manipuladores de eventos forem fornecidos no código do desenvolvedor, libere-os no descarte. Para obter mais informações, confira a seção Descarte de componentes com IDisposable
e IAsyncDisposable
.
No exemplo a seguir, ParameterView.TryGetValue atribui o valor do parâmetro Param
a value
, se a análise de um parâmetro de rota para Param
for bem-sucedida. Quando value
não é null
, o valor é exibido pelo componente.
Embora a correspondência de parâmetros de rota não diferencie maiúsculas de minúsculas, TryGetValue corresponde apenas aos nomes de parâmetros que diferenciam maiúsculas de minúsculas no modelo de rota. O exemplo a seguir exige o uso de /{Param?}
no modelo de rota para obter o valor com TryGetValue, não /{param?}
. Se /{param?}
for usado nesse cenário, TryGetValue retornará false
e message
não será definido como nenhuma das cadeias de caracteres message
.
SetParamsAsync.razor
:
@page "/set-params-async/{Param?}"
<PageTitle>Set Parameters Async</PageTitle>
<h1>Set Parameters Async Example</h1>
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string? Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async/{Param?}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string? Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async/{Param?}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string? Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async/{Param?}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
@page "/set-params-async"
@page "/set-params-async/{Param}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string Param { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}
await base.SetParametersAsync(parameters);
}
}
Inicialização de componente (OnInitialized{Async}
)
OnInitialized e OnInitializedAsync são usados exclusivamente para inicializar um componente durante todo o tempo de vida da instância do componente. Valores de parâmetro e alterações de valor de parâmetro não devem afetar a inicialização executada nesses métodos. Por exemplo, carregar opções estáticas em uma lista suspensa que não é alterada ao longo do tempo de vida do componente e que não depende de valores de parâmetro é executado em um desses métodos de ciclo de vida. Se valores de parâmetro ou alterações em valores de parâmetro afetarem o estado do componente, use OnParametersSet{Async}
em vez disso.
Esses métodos são invocados quando o componente é inicializado depois de receber seus parâmetros iniciais em SetParametersAsync. O método síncrono é chamado antes do método assíncrono.
Se a inicialização do componente pai síncrono for usada, a inicialização pai será concluída antes da inicialização do componente filho. Se a inicialização de componente pai assíncrono for usada, a ordem de conclusão da inicialização do componente pai e filho não poderá ser determinada, pois depende do código de inicialização em execução.
Para uma operação síncrona, substitua 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"
<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}";
}
}
Para executar uma operação assíncrona, substitua OnInitializedAsync e use o operador await
:
protected override async Task OnInitializedAsync()
{
await ...
}
Se uma classe base personalizada for usada com lógica de inicialização personalizada, chame OnInitializedAsync na classe base:
protected override async Task OnInitializedAsync()
{
await ...
await base.OnInitializedAsync();
}
Não é necessário chamar ComponentBase.OnInitializedAsync, a menos que uma classe base personalizada seja usada com lógica personalizada. Para obter mais informações, consulte a seção Métodos de ciclo de vida da classe Base.
Os aplicativos Blazor que geram previamente seu conteúdo na chamada do servidor OnInitializedAsync duas vezes:
- Uma vez, quando o componente é renderizado inicialmente de forma estática como parte da página.
- Uma segunda vez, quando o navegador renderiza o componente.
Para impedir que o código do desenvolvedor no OnInitializedAsync seja executado duas vezes durante a pré-renderização, confira a seção Reconexão com estado após a pré-renderização. O conteúdo na seção se concentra em Blazor Web Apps e reconexãoSignalR com estado. Para preservar o estado durante a execução do código de inicialização durante a pré-renderização, confira Pré-renderizar componentes Razor do ASP.NET Core.
Para impedir que o código do desenvolvedor no OnInitializedAsync seja executado duas vezes durante a pré-renderização, confira a seção Reconexão com estado após a pré-renderização. Embora o conteúdo na seção se concentre em Blazor Server e SignalRreconexão com estado, o cenário de pré-renderização nas soluções Blazor WebAssembly hospedadas (WebAssemblyPrerendered) envolve condições e abordagens semelhantes para impedir que o código do desenvolvedor seja executado duas vezes. Para preservar o estado durante a execução do código de inicialização durante a pré-renderização, confira a Pré-renderização e integração de componentes Razor do AsP.NET Core.
Durante a pré-renderização de um aplicativo Blazor, determinadas ações, como chamar o JavaScript (interoperabilidade JS), não são possíveis. Os componentes podem precisar ser renderizados de forma diferente quando pré-renderizados. Para obter mais informações, confira a seção Pré-renderização com interoperabilidade do JavaScript.
Se os manipuladores de eventos forem fornecidos no código do desenvolvedor, libere-os no descarte. Para obter mais informações, confira a seção Descarte de componentes com IDisposable
IAsyncDisposable
.
Use renderização de streaming com renderização estática do lado do servidor (SSR estática) ou pré-renderização para melhorar a experiência do usuário em componentes que executam tarefas assíncronas de longa duração no OnInitializedAsync para renderizar totalmente. Para saber mais, consulte Renderização de componentes de Razor no ASP.NET Core.
Depois que os parâmetros são definidos (OnParametersSet{Async}
)
OnParametersSet ou OnParametersSetAsync são chamados:
Depois que o componente é inicializado em OnInitialized ou OnInitializedAsync.
Quando o componente pai pré-renderiza e fornece:
- Tipos imutáveis conhecidos ou primitivos quando pelo menos um parâmetro foi alterado.
- Parâmetros de tipo complexo. A estrutura não pode saber se os valores de um parâmetro de tipo complexo sofreram mutação interna. Portanto, a estrutura sempre trata o conjunto de parâmetros como alterado, quando um ou mais parâmetros de tipo complexo estão presentes.
Para obter mais informações, confira Renderização de componentes Razor do ASP.NET Core.
O método síncrono é chamado antes do método assíncrono.
Os métodos podem ser invocados mesmo se os valores de parâmetro não tiverem sido alterados. Este comportamento ressalta a necessidade de os desenvolvedores implementarem lógica adicional dentro dos métodos para verificar se os valores de parâmetro realmente foram alterados antes de inicializar novamente os dados ou o estado dependente desses parâmetros.
Para o seguinte componente de exemplo, navegue até a página do componente em uma URL:
- Com uma data de início recebida por
StartDate
:/on-parameters-set/2021-03-19
- Sem uma data de início, onde
StartDate
recebe um valor da hora local atual:/on-parameters-set
Observação
Em uma rota de componente, não é possível restringir um parâmetro DateTime com a restrição de rota datetime
e tornar o parâmetro opcional. Portanto, o componente OnParamsSet
a seguir usa duas diretivas @page
para manipular o roteamento com e sem um segmento de data fornecido na URL.
OnParamsSet.razor
:
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<PageTitle>On Parameters Set</PageTitle>
<h1>On Parameters Set Example</h1>
<p>
Pass a datetime in the URI of the browser's address bar.
For example, add <code>/1-1-2024</code> to the address.
</p>
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied " +
$"(StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used " +
$"(StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
{
if (StartDate == default)
{
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used (StartDate: {StartDate}).";
}
}
}
O trabalho assíncrono ao aplicar parâmetros e valores de propriedade deve ocorrer durante o evento de ciclo de vida OnParametersSetAsync:
protected override async Task OnParametersSetAsync()
{
await ...
}
Se uma classe base personalizada for usada com lógica de inicialização personalizada, chame OnParametersSetAsync na classe base:
protected override async Task OnParametersSetAsync()
{
await ...
await base.OnParametersSetAsync();
}
Não é necessário chamar ComponentBase.OnParametersSetAsync, a menos que uma classe base personalizada seja usada com lógica personalizada. Para obter mais informações, consulte a seção Métodos de ciclo de vida da classe Base.
Se os manipuladores de eventos forem fornecidos no código do desenvolvedor, libere-os no descarte. Para obter mais informações, confira a seção Descarte de componentes com IDisposable
IAsyncDisposable
.
Para obter mais informações sobre parâmetros e restrições de rota, confira Roteamento e navegação Blazor do ASP.NET Core.
Para obter um exemplo de implementação manual de SetParametersAsync
para melhorar o desempenho em alguns cenários, consulte as melhores práticas de desempenhoBlazor do ASP.NET Core.
Após a renderização do componente (OnAfterRender{Async}
)
OnAfterRender e OnAfterRenderAsync são invocados depois que um componente é renderizado interativamente e a interface do usuário termina de atualizar (por exemplo, depois que elementos são adicionados ao DOM do navegador). As referências de elemento e componente são preenchidas nesse ponto. Use esse estágio para executar etapas de inicialização adicionais com o conteúdo renderizado, como chamadas de interoperabilidade JS que interagem com os elementos de DOM renderizados. O método síncrono é chamado antes do método assíncrono.
Esses métodos não são invocados durante a pré-renderização ou a SSR (renderização estática do lado do servidor) porque esses processos não estão anexados a um DOM do navegador dinâmico e já estão concluídos antes que o DOM seja atualizado.
Para OnAfterRenderAsync, o componente não é renderizado de novo automaticamente após a conclusão de qualquer Task
retornada para evitar um loop de renderização infinito.
OnAfterRender e OnAfterRenderAsync são chamados depois que um componente termina a renderização. As referências de elemento e componente são preenchidas nesse ponto. Use esse estágio para executar etapas de inicialização adicionais com o conteúdo renderizado, como chamadas de interoperabilidade JS que interagem com os elementos de DOM renderizados. O método síncrono é chamado antes do método assíncrono.
Esses métodos não são invocados durante a pré-renderização porque a pré-renderização não está anexada a um DOM do navegador dinâmico e já está concluída antes que o DOM seja atualizado.
Para OnAfterRenderAsync, o componente não é renderizado de novo automaticamente após a conclusão de qualquer Task
retornada para evitar um loop de renderização infinito.
O parâmetro firstRender
para OnAfterRender e OnAfterRenderAsync:
- É definido como
true
na primeira vez que a instância do componente é renderizada. - Pode ser usado para garantir que o trabalho de inicialização seja executado apenas uma vez.
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("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");
}
}
A amostra AfterRender.razor
produz a saída a seguir para o console quando a página é carregada e o botão é selecionado:
OnAfterRender: firstRender = True
HandleClick called
OnAfterRender: firstRender = False
O trabalho assíncrono imediatamente após a renderização deve ocorrer durante o evento de ciclo de vida OnAfterRenderAsync:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
...
}
Se uma classe base personalizada for usada com lógica de inicialização personalizada, chame OnAfterRenderAsync na classe base:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
...
await base.OnAfterRenderAsync(firstRender);
}
Não é necessário chamar ComponentBase.OnAfterRenderAsync, a menos que uma classe base personalizada seja usada com lógica personalizada. Para obter mais informações, consulte a seção Métodos de ciclo de vida da classe Base.
Mesmo que você retorne um Task de OnAfterRenderAsync, a estrutura não agendará um ciclo de renderização adicional para o componente, depois que essa tarefa for concluída. Isso é para evitar um loop de renderização infinito. Isso é diferente dos outros métodos de ciclo de vida, que agendam um ciclo de renderização adicional depois que um Task retornado é concluído.
OnAfterRender e OnAfterRenderAsync não são chamados durante o processo de pré-renderização no servidor. Os métodos são chamados quando o componente é renderizado interativamente após a pré-renderização. Quando o aplicativo pré-renderiza:
- O componente é executado no servidor para produzir uma marcação HTML estática na resposta HTTP. Durante essa fase, OnAfterRender e OnAfterRenderAsync não são chamados.
- Quando o script Blazor (
blazor.{server|webassembly|web}.js
) é iniciado no navegador, o componente é reiniciado em um modo de renderização interativa. Depois que um componente é reiniciado, OnAfterRender e OnAfterRenderAsync são chamados porque o aplicativo não está mais na fase de pré-renderização.
Se os manipuladores de eventos forem fornecidos no código do desenvolvedor, libere-os no descarte. Para obter mais informações, confira a seção Descarte de componentes com IDisposable
IAsyncDisposable
.
Métodos de ciclo de vida de classe base
Ao substituir os métodos de ciclo de vida de Blazor, não é necessário chamar métodos de ciclo de vida de classe base para ComponentBase. Porém, um componente deve chamar um método de ciclo de vida de classe base substituído nas seguintes situações:
- Ao substituir ComponentBase.SetParametersAsync,
await base.SetParametersAsync(parameters);
geralmente é invocado porque o método de classe base chama outros métodos de ciclo de vida e dispara a renderização de forma complexa. Para obter mais informações, consulte a seção Quando os parâmetros são definidos (SetParametersAsync
). - Se o método de classe base contiver a lógica que deve ser executada. Os consumidores de biblioteca geralmente chamam métodos de ciclo de vida de classe base ao herdar uma classe base porque as classes base da biblioteca geralmente têm lógica de ciclo de vida personalizada a ser executada. Se o aplicativo usar uma classe base de uma biblioteca, consulte a documentação da biblioteca para obter diretrizes.
No exemplo a seguir, base.OnInitialized();
é chamado para garantir que o método OnInitialized
da classe base seja executado. Sem a chamada, BlazorRocksBase2.OnInitialized
não é executado.
BlazorRocks2.razor
:
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<h1>Blazor Rocks! Example 2</h1>
<p>
@BlazorRocksText
</p>
@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
}
}
BlazorRocksBase2.cs
:
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } = "Blazor rocks the browser!";
protected override void OnInitialized() =>
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
}
Alterações de estado (StateHasChanged
)
StateHasChanged notifica o componente de que o estado foi alterado. Quando aplicável, chamar StateHasChanged enfileira uma nova renderização que ocorre quando o thread principal do aplicativo está livre.
StateHasChanged é chamado automaticamente para métodos EventCallback. Para obter mais informações sobre retornos de chamada de evento, confira Manipulação de eventos Blazor do ASP.NET Core.
Para obter mais informações sobre renderização de componentes e quando chamar StateHasChanged, incluindo quando invocá-lo com ComponentBase.InvokeAsync, confira Renderização de componentes Razor do ASP.NET Core.
Manipular ações assíncronas incompletas na renderização
Aa ações assíncronas executadas em eventos de ciclo de vida podem não ter sido concluídas antes da renderização do componente. Os objetos podem ser null
ou preenchidos de maneira incompleta com dados, enquanto o método de ciclo de vida está em execução. Forneça a lógica de renderização para confirmar se os objetos foram inicializados. Renderize os elementos de interface do usuário do espaço reservado (por exemplo, uma mensagem de carregamento), enquanto os objetos forem null
.
No componente a seguir, OnInitializedAsync é substituído para fornecer de forma assíncrona dados de classificação de filme (movies
). Quando movies
é null
, uma mensagem de carregamento é exibida para o usuário. Depois que o Task
retornado por OnInitializedAsync for concluído, o componente será renderizado novamente com o estado atualizado.
<h1>Sci-Fi Movie Ratings</h1>
@if (movies == null)
{
<p><em>Loading...</em></p>
}
else
{
<ul>
@foreach (var movie in movies)
{
<li>@movie.Title — @movie.Rating</li>
}
</ul>
}
@code {
private Movies[]? movies;
protected override async Task OnInitializedAsync()
{
movies = await GetMovieRatings(DateTime.Now);
}
}
Tratar erros
Para obter informações sobre como lidar com erros durante a execução do método de ciclo de vida, confira Tratar erros em aplicativos Blazor ASP.NET Core.
Reconexão com estado após a pré-renderização
Ao pré-renderizar no servidor, um componente é inicialmente renderizado estaticamente como parte da página. Depois que o navegador estabelece uma conexão SignalR com o servidor, o componente é renderizado novamente e fica interativo. Se o método de ciclo de vida OnInitialized{Async}
para inicializar o componente estiver presente, o método será executado duas vezes:
- Quando o componente é pré-renderizado estaticamente.
- Depois que a conexão do servidor for estabelecida.
Isso pode resultar em uma alteração perceptível nos dados exibidos na interface do usuário, quando o componente é finalmente renderizado. Para evitar esse comportamento, passe um identificador para armazenar o estado em cache durante a pré-renderização e recuperar o estado após a pré-renderização.
O código a seguir demonstra um WeatherForecastService
que evita a alteração na exibição de dados devido à pré-renderização. O Delay aguardado (await Task.Delay(...)
) simula um pequeno atraso antes de retornar dados do método GetForecastAsync
.
Adicione IMemoryCache serviços com AddMemoryCache na coleção de serviços no arquivo Program
do aplicativo:
builder.Services.AddMemoryCache();
WeatherForecastService.cs
:
using Microsoft.Extensions.Caching.Memory;
namespace BlazorSample;
public class WeatherForecastService(IMemoryCache memoryCache)
{
private static readonly string[] summaries =
[
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
];
public IMemoryCache MemoryCache { get; } = memoryCache;
public Task<WeatherForecast[]?> GetForecastAsync(DateOnly startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
});
}
}
using Microsoft.Extensions.Caching.Memory;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]?> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
});
}
}
using Microsoft.Extensions.Caching.Memory;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
});
}
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
var rng = new Random();
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = summaries[rng.Next(summaries.Length)]
}).ToArray();
});
}
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;
public class WeatherForecastService
{
private static readonly string[] summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastService(IMemoryCache memoryCache)
{
MemoryCache = memoryCache;
}
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});
var rng = new Random();
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = summaries[rng.Next(summaries.Length)]
}).ToArray();
});
}
}
Para obter mais informações sobre o RenderMode, confira Diretrizes BlazorSignalR do ASP.NET Core.
O conteúdo nesta seção se concentra em Blazor Web Apps e reconexão SignalR com estado. Para preservar o estado durante a execução do código de inicialização durante a pré-renderização, confira Pré-renderizar componentes Razor do ASP.NET Core.
Embora o conteúdo da seção se concentre em Blazor Server e SignalRreconexão com estado, o cenário de pré-renderização nos aplicativos Blazor WebAssembly hospedados (WebAssemblyPrerendered) envolve condições e abordagens semelhantes para impedir que o código do desenvolvedor seja executado duas vezes. Para preservar o estado durante a execução do código de inicialização durante a pré-renderização, confira a Pré-renderização e integração de componentes Razor do AsP.NET Core.
Pré-renderização com interoperabilidade do JavaScript
Esta seção se aplica a aplicativos do lado do servidor que pré-renderizam componentes Razor. A pré-renderização é abordada em Pre-renderizar componentes Razor do ASP.NET Core.
Observação
A navegação interna de roteamento interativo em Blazor Web Apps não envolve a solicitação de novo conteúdo de página a partir do servidor. Portanto, a pré-geração não ocorre para solicitações de página internas. Se o aplicativo adotar o roteamento interativo, execute um recarregamento de página inteira para obter exemplos de componente que demonstram o comportamento de pré-geração. Para mais informações, confira Pré-renderizar componentes Razor do ASP.NET Core.
Esta seção se aplica a aplicativos do lado do servidor e aplicativos Blazor WebAssembly hospedados que pré-renderizam componentes Razor. A pré-renderização é abordada em Pre-renderizar e integrar componentes do Razor ASP.NET Core.
Durante a pré-renderização, não é possível chamar JavaScript (JS). O exemplo a seguir demonstra como usar a interoperabilidade JS como parte da lógica de inicialização de um componente de uma maneira compatível com a pré-renderização.
A seguinte função de scrollElementIntoView
:
- Rola para o elemento passado com
scrollIntoView
. - Retorna o valor da propriedade
top
do elemento pelo métodogetBoundingClientRect
.
window.scrollElementIntoView = (element) => {
element.scrollIntoView();
return element.getBoundingClientRect().top;
}
Onde IJSRuntime.InvokeAsync chama a função JS no código do componente, ElementReference é usado somente em OnAfterRenderAsync e não em métodos de ciclo de vida anteriores porque não há nenhum elemento HTML DOM até que o componente seja renderizado.
StateHasChanged
(fonte de referência) é chamado para enfileirar a nova renderização do componente com o novo estado obtido da chamada de interoperabilidade JS (para mais informações, confira Renderização do componente Razor do ASP.NET Core). Não é criado um loop infinito, pois StateHasChanged é chamado apenas 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();
}
}
}
O exemplo anterior polui o cliente com uma função global. Para obter uma abordagem melhor em aplicativos de produção, confira Isolamento de JavaScript em módulos JavaScript.
Descarte de componentes com IDisposable
e IAsyncDisposable
Se um componente implementar IDisposable, IAsyncDisposable ou ambos, a estrutura solicitará o descarte de recursos quando o componente for removido da interface do usuário. Não confie no tempo exato de quando esses métodos são executados. Por exemplo, IAsyncDisposable pode ser acionado antes ou depois que um Task assíncrono aguardado em OnInitalizedAsync
é chamado ou concluído. Além disso, o código de descarte de objetos não deve assumir que os objetos criados durante a inicialização ou outros métodos de ciclo de vida existem.
Os componentes não deveriam precisar implementar IDisposable e IAsyncDisposable simultaneamente. Se ambos forem implementados, a estrutura executará apenas a sobrecarga assíncrona.
O código do desenvolvedor deve garantir que as implementações IAsyncDisposable não demorem muito para serem concluídas.
Descarte de referências de objeto de interoperabilidade do JavaScript
Os exemplos em todos os artigos de interoperabilidade do JavaScript (JS) demonstram padrões típicos de descarte de objetos:
Ao chamar JS a partir do .NET, conforme descrito em Chamar funções JavaScript a partir de métodos do .NET no Blazor do ASP.NET Core, descarte qualquer IJSObjectReference/IJSInProcessObjectReference/JSObjectReference criado a partir do .NET ou do JS para evitar perda de memória JS.
Ao chamar o .NET a partir do JS, conforme descrito em Chamar métodos do .NET a partir das funções JavaScript no Blazor do ASP.NET Core, descarte um DotNetObjectReference criado a partir do .NET ou do JS para evitar perda de memória do .NET.
As referências de objeto de interoperabilidade JS são implementadas como um mapa com chave de um identificador no lado da chamada de interoperabilidade JS que cria a referência. Quando o descarte de objetos é iniciado no .NET ou do lado do JS, Blazor remove a entrada do mapa e o objeto pode ser coletado como lixo, desde que nenhuma outra referência forte ao objeto esteja presente.
No mínimo, sempre descarte objetos criados no lado do .NET para evitar o vazamento de memória gerenciada do .NET.
Tarefas de limpeza do DOM no descarte de componentes
Para obter mais informações, confira Interoperabilidade ASP.NET Core Blazor JavaScript (interoperabilidade JS).
Para obter diretrizes sobre JSDisconnectedException quando um circuito é desconectado, consulte Interoperabilidade de JavaScript no ASP.NET CoreBlazor (JS interoperabilidade). Para obter diretrizes gerais sobre tratamento de erros de interoperabilidade do JavaScript, confira a seção Interoperabilidade do JavaScript em Tratar erros em aplicativos Blazor do ASP.NET Core.
IDisposable
síncrono
Para tarefas de descarte síncrono, use IDisposable.Dispose.
O seguinte componente:
- Implementa IDisposable com a diretiva
@implements
Razor. - Descarta
obj
, que é um tipo que implementa IDisposable. - Uma marcar nula é executada porque
obj
é criado em um método de ciclo de vida (não mostrado).
@implements IDisposable
...
@code {
...
public void Dispose()
{
obj?.Dispose();
}
}
Se um único objeto exigir descarte, um lambda poderá ser usado para descartar o objeto quando Dispose for chamado. O exemplo a seguir é mostrado no artigo Renderização de componentes Razor do ASP.NET Core e demonstra o uso de uma expressão lambda para o descarte de um Timer.
TimerDisposal1.razor
:
@page "/timer-disposal-1"
@using System.Timers
@implements IDisposable
<PageTitle>Timer Disposal 1</PageTitle>
<h1>Timer Disposal Example 1</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
CounterWithTimerDisposal1.razor
:
@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
CounterWithTimerDisposal1.razor
:
@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
CounterWithTimerDisposal1.razor
:
@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
CounterWithTimerDisposal1.razor
:
@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer = new Timer(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
Observação
No exemplo anterior, a chamada para StateHasChanged é encapsulada por uma chamada para ComponentBase.InvokeAsync, pois o retorno de chamada é invocado fora do contexto de sincronização do Blazor. Para saber mais, consulte Renderização de componentes de Razor no ASP.NET Core.
Se o objeto for criado em um método de ciclo de vida, como OnInitialized{Async}
, verifique o null
antes de chamar o Dispose
.
TimerDisposal2.razor
:
@page "/timer-disposal-2"
@using System.Timers
@implements IDisposable
<PageTitle>Timer Disposal 2</PageTitle>
<h1>Timer Disposal Example 2</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer? timer;
protected override void OnInitialized()
{
timer = new Timer(1000);
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer?.Dispose();
}
CounterWithTimerDisposal2.razor
:
@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer? timer;
protected override void OnInitialized()
{
timer = new Timer(1000);
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer?.Dispose();
}
CounterWithTimerDisposal2.razor
:
@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer? timer;
protected override void OnInitialized()
{
timer = new Timer(1000);
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer?.Dispose();
}
CounterWithTimerDisposal2.razor
:
@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer;
protected override void OnInitialized()
{
timer = new Timer(1000);
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer?.Dispose();
}
CounterWithTimerDisposal2.razor
:
@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer;
protected override void OnInitialized()
{
timer = new Timer(1000);
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer?.Dispose();
}
Para obter mais informações, consulte:
IAsyncDisposable
assíncrono
Para tarefas de descarte assíncrono, use IAsyncDisposable.DisposeAsync.
O seguinte componente:
- Implementa IAsyncDisposable com a diretiva
@implements
Razor. - Descarta
obj
, que é um tipo não gerenciado que implementa IAsyncDisposable. - Uma marcar nula é executada porque
obj
é criado em um método de ciclo de vida (não mostrado).
@implements IAsyncDisposable
...
@code {
...
public async ValueTask DisposeAsync()
{
if (obj is not null)
{
await obj.DisposeAsync();
}
}
}
Para obter mais informações, consulte:
Atribuição de null
aos objetos descartados
Normalmente, não é necessário atribuir null
a objetos descartados depois de chamar Dispose/DisposeAsync. Casos raros para atribuição de null
incluem o seguinte:
- Se o tipo do objeto for implementado incorretamente e não tolerar chamadas repetidas para Dispose/DisposeAsync, atribua
null
após o descarte para ignorar normalmente outras chamadas para Dispose/DisposeAsync. - Se um processo de longa duração continuar mantendo uma referência a um objeto descartado, atribuir
null
permitirá que o coletor de lixo libere o objeto, apesar do processo de longa duração que contém uma referência a ele.
Esses são cenários incomuns. Para objetos implementados corretamente e que se comportam normalmente, não faz sentido atribuir null
aos objetos descartados. Nos casos raros em que um objeto deve receber null
, recomendamos documentar o motivo e buscar uma solução que evite a necessidade de atribuir null
.
StateHasChanged
Observação
Não há suporte para chamar StateHasChanged em Dispose
e DisposeAsync
. StateHasChanged pode ser invocado como parte da desativação do renderizador. Portanto, não há suporte para a solicitação de atualizações de interface do usuário nesse ponto.
Manipuladores de eventos
Sempre cancele a assinatura dos manipuladores de eventos nos eventos do .NET. Os seguintes exemplos de formulário Blazor mostram como cancelar a assinatura de um manipulador de eventos no método Dispose
:
Campo privado e abordagem lambda
@implements IDisposable <EditForm ... EditContext="editContext" ...> ... <button type="submit" disabled="@formInvalid">Submit</button> </EditForm> @code { ... private EventHandler<FieldChangedEventArgs>? fieldChanged; protected override void OnInitialized() { editContext = new(model); fieldChanged = (_, __) => { ... }; editContext.OnFieldChanged += fieldChanged; } public void Dispose() { editContext.OnFieldChanged -= fieldChanged; } }
Abordagem de método privado
@implements IDisposable <EditForm ... EditContext="editContext" ...> ... <button type="submit" disabled="@formInvalid">Submit</button> </EditForm> @code { ... protected override void OnInitialized() { editContext = new(model); editContext.OnFieldChanged += HandleFieldChanged; } private void HandleFieldChanged(object sender, FieldChangedEventArgs e) { ... } public void Dispose() { editContext.OnFieldChanged -= HandleFieldChanged; } }
Para obter mais informações, confira a seção Descarte de componentes com IDisposable
e IAsyncDisposable
.
Para obter mais informações sobre o componente e os formulários do EditForm, consulte Visão geral dos formulários do ASP.NET Core Blazor e os outros artigos de formulários no nó Forms.
Funções anônimas, métodos e expressões
Quando funções anônimas, métodos ou expressões são usados, não é necessário implementar IDisposable e cancelar a assinatura de delegados. No entanto, não assinar um delegado é um problema quando o objeto que expõe o evento ultrapassa o tempo de vida do componente que registra o delegado. Quando isso ocorre, o resultado é um vazamento de memória porque o delegado registrado mantém o objeto original ativo. Portanto, use apenas as abordagens a seguir quando souber que o delegado do evento será descartado rapidamente. Quando estiver em dúvida sobre o tempo de vida dos objetos que exigem descarte, assine um método delegado e descarte corretamente o delegado, como mostram os exemplos anteriores.
Abordagem anônima do método lambda (descarte explícito não necessário):
private void HandleFieldChanged(object sender, FieldChangedEventArgs e) { formInvalid = !editContext.Validate(); StateHasChanged(); } protected override void OnInitialized() { editContext = new(starship); editContext.OnFieldChanged += (s, e) => HandleFieldChanged((editContext)s, e); }
Abordagem anônima da expressão lambda (descarte explícito não necessário):
private ValidationMessageStore? messageStore; [CascadingParameter] private EditContext? CurrentEditContext { get; set; } protected override void OnInitialized() { ... messageStore = new(CurrentEditContext); CurrentEditContext.OnValidationRequested += (s, e) => messageStore.Clear(); CurrentEditContext.OnFieldChanged += (s, e) => messageStore.Clear(e.FieldIdentifier); }
O exemplo completo do código anterior com expressões lambda anônimas é mostrado no artigo Validação de formulários do ASP.NET Core Blazor.
Para obter mais informações, confira Limpeza de recursos não gerenciados e os tópicos que o seguem na implementação dos métodos Dispose
e DisposeAsync
.
Trabalho em segundo plano cancelável
Os componentes geralmente realizam trabalhos em segundo plano de execução prolongada, como fazer chamadas de rede (HttpClient) e interagir com bancos de dados. É desejável interromper o trabalho em segundo plano para conservar os recursos do sistema em várias situações. Por exemplo, as operações assíncronas em segundo plano não param automaticamente quando um usuário navega para longe de um componente.
Outros motivos pelos quais os itens de trabalho em segundo plano podem exigir cancelamento incluem:
- Uma tarefa em segundo plano em execução foi iniciada usando dados de entrada com defeito ou parâmetros de processamento.
- O conjunto atual de itens de trabalho em segundo plano em execução deve ser substituído por um novo conjunto de itens de trabalho.
- A prioridade das tarefas em execução no momento deve ser alterada.
- O aplicativo deve ser desligado para reimplantação do servidor.
- Os recursos do servidor tornam-se limitados, exigindo o reagendamento dos itens de trabalho em segundo plano.
Para implementar um padrão de trabalho em segundo plano cancelável em um componente:
- Use um CancellationTokenSource e CancellationToken.
- No descarte o componente e a qualquer momento em que o cancelamento for desejado cancelando manualmente o token, chame
CancellationTokenSource.Cancel
para sinalizar que o trabalho em segundo plano deve ser cancelado. - Depois que a chamada assíncrona retornar, chame ThrowIfCancellationRequested no token.
No exemplo a seguir:
await Task.Delay(5000, cts.Token);
representa um trabalho em segundo plano assíncrono de execução prolongada.BackgroundResourceMethod
representa um método em segundo plano de execução prolongada que não deve ser iniciado, se oResource
for descartado antes que o método seja chamado.
BackgroundWork.razor
:
@page "/background-work"
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<PageTitle>Background Work</PageTitle>
<h1>Background Work Example</h1>
<p>
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
</p>
<p>Study logged messages in the console.</p>
<p>
If you trigger disposal within 10 seconds of page load, the
<code>BackgroundResourceMethod</code> isn't executed.
</p>
<p>
If disposal occurs after <code>BackgroundResourceMethod</code> is called but
before action is taken on the resource, an <code>ObjectDisposedException</code>
is thrown by <code>BackgroundResourceMethod</code>, and the resource isn't
processed.
</p>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
private IList<string> messages = [];
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(10000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
if (!cts.IsCancellationRequested)
{
cts.Cancel();
}
cts?.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose() => disposed = true;
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new Resource();
private CancellationTokenSource cts = new CancellationTokenSource();
protected async Task LongRunningWork()
{
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
public void Dispose()
{
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
}
private class Resource : IDisposable
{
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
{
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
}
public void Dispose()
{
disposed = true;
}
}
}
Eventos de reconexão do Blazor Server
Os eventos de ciclo de vida do componente abordados neste artigo operam separadamente de manipuladores de eventos de reconexão do lado do servidor. Quando a conexão SignalR com o cliente é perdida, somente as atualizações da interface do usuário são interrompidas. As atualizações de interface do usuário são retomadas quando a conexão é restabelecida. Para obter mais informações sobre eventos e configuração do manipulador de circuito, confira Diretrizes do BlazorSignalR no ASP.NET Core.
Recursos adicionais
Tratar exceções capturadas fora do ciclo de vida de um componente Razor