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.

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:

  1. 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 assicrono.
  2. 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 assicrono.
  3. Renderize para todos os trabalhos síncronos e Task completos.

Observação

Aa ações assíncronas executadas em eventos de ciclo de vida podem não ter sido concluídas antes da renderização de um componente. 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.

Eventos de ciclo de vida de componente de um componente Razor em Blazor

Processamento de eventos DOM:

  1. O manipulador de eventos é executado.
  2. Se um Task incompleto for retornado, o Task será aguardado e, em seguida, o componente será gerado novamente.
  3. Renderize para todos os trabalhos síncronos e Task completos.

Processamento de eventos DOM

O ciclo de vida do Render:

  1. Evite mais operações de renderização no componente quando ambas as seguintes condições forem atendidas:
    • Não é a primeira renderização.
    • ShouldRender retorna false.
  2. Crie a comparação da árvore de renderização (diferença) e renderize o componente.
  3. Aguarde o DOM para atualizar.
  4. Chame OnAfterRender{Async}. O método síncrono é chamado antes do método assicrono.

Ciclo de vida de renderização

As chamadas do desenvolvedor para StateHasChanged resultam em uma 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.

Se ComponentBase.SetParametersAsync não for invocado com base.SetParametersAsync();, o código do desenvolvedor poderá interpretar os valores dos parâmetros de entrada de qualquer maneira necessária. Por exemplo, não há requisitos para atribuir os parâmetros de entrada às propriedades da classe.

Se você optar por não chamar o método de classe base, deverá chamar seus próprios métodos de inicialização de componente (OnInitialized/OnInitializedAsync). Caso contrário, eles não serão chamados porque chamar ComponentBase.SetParametersAsync é o que os invoca. StateHasChanged também deve ser chamado após a inicialização. Consulte a ComponentBase fonte de referência ao estruturar seu código se você não planeja chamar ComponentBase.SetParametersAsync.

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 API de inicialização e renderização de ComponentBase.SetParametersAsync mas não processar parâmetros de entrada, você terá a opção de passar um ParameterView vazio para o método de classe base:

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

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 OnInitializedAsyncduas 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 Aplicativos Web e SignalRreconexão 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 IDisposableIAsyncDisposable.

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

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

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

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

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("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"
@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 OnAfterRenderAsyncnã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:

  1. 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.
  2. 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 OnAfterRenderAsyncsã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 IDisposableIAsyncDisposable.

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. No entanto, um componente deve chamar um método de ciclo de vida de classe base substituído se o método de classe base contiver 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 faz com que o componente seja renderizado novamente.

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 &mdash; @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 Aplicativos Web e SignalRreconexão 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 para roteamento interativo em Aplicativos Web Blazor não envolve a solicitação de novo conteúdo de página 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 de um aplicativo, determinadas ações, como chamar o JavaScript (JS), não são possíveis.

Para o exemplo a seguir, a função setElementText1 é chamada com JSRuntimeExtensions.InvokeVoidAsync e não retorna um valor.

Observação

Para obter diretrizes gerais sobre o local JS e nossas recomendações para aplicativos de produção, consulte Localização do JavaScript em aplicativos ASP.NET Core Blazor.

<script>
  window.setElementText1 = (element, text) => element.innerText = text;
</script>

Aviso

O exemplo anterior modifica o DOM diretamente apenas por motivos de demonstração. Modificar o DOM diretamente com JS não é recomendado na maioria dos cenários, pois JS pode interferir no controle de alterações do Blazor. Para obter mais informações, confira Interoperabilidade ASP.NET Core Blazor JavaScript (interoperabilidade JS).

O evento de ciclo de vida OnAfterRender{Async} não é chamado durante o processo de pré-renderização no servidor. Substitua o método OnAfterRender{Async} para atrasar as chamadas de interoperabilidade JS até que o componente esteja renderizado e interativo no cliente após a pré-renderização.

PrerenderedInterop1.razor:

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

<PageTitle>Prerendered Interop 1</PageTitle>

<h1>Prerendered Interop Example 1</h1>

<div @ref="divElement">Text during render</div>

@code {
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync(
                "setElementText1", divElement, "Text after render");
        }
    }
}

Observação

O exemplo anterior polui o cliente com funções globais. Para obter uma abordagem melhor em aplicativos de produção, confira Isolamento de JavaScript em módulos JavaScript.

Exemplo:

export setElementText1 = (element, text) => element.innerText = text;

O componente 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. O componente mostra que é possível disparar uma atualização de renderização de dentro de OnAfterRenderAsync. O desenvolvedor deve ter cuidado para evitar a criação de um loop infinito nesse cenário.

Para o exemplo a seguir, a função setElementText2 é chamada com IJSRuntime.InvokeAsync e retorna um valor.

Observação

Para obter diretrizes gerais sobre o local JS e nossas recomendações para aplicativos de produção, consulte Localização do JavaScript em aplicativos ASP.NET Core Blazor.

<script>
  window.setElementText2 = (element, text) => {
    element.innerText = text;
    return text;
  };
</script>

Aviso

O exemplo anterior modifica o DOM diretamente apenas por motivos de demonstração. Modificar o DOM diretamente com JS não é recomendado na maioria dos cenários, pois JS pode interferir no controle de alterações do Blazor. Para obter mais informações, confira Interoperabilidade ASP.NET Core Blazor JavaScript (interoperabilidade JS).

Onde JSRuntime.InvokeAsync é chamado, o 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 é chamado para renderizar novamente o componente com o novo estado obtido da chamada de interoperabilidade JS (para obter mais informações, confira Renderização de componentes Razor AsP.NET Core). O código não cria um loop infinito, pois StateHasChanged é chamado apenas quando data é null.

PrerenderedInterop2.razor:

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

<PageTitle>Prerendered Interop 2</PageTitle>

<h1>Prerendered Interop Example 2</h1>

<p>
    Get value via JS interop call:
    <strong id="val-get-by-interop">@(infoFromJs ?? "No value yet")</strong>
</p>

<p>
    Set value via JS interop call: 
    <strong id="val-set-by-interop" @ref="divElement"></strong>
</p>



@code {
    private string? infoFromJs;
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && infoFromJs == null)
        {
            infoFromJs = await JS.InvokeAsync<string>(
                "setElementText2", divElement, "Hello from interop call!");

            StateHasChanged();
        }
    }
}

Observação

O exemplo anterior polui o cliente com funções globais. Para obter uma abordagem melhor em aplicativos de produção, confira Isolamento de JavaScript em módulos JavaScript.

Exemplo:

export setElementText2 = (element, text) => {
  element.innerText = text;
  return text;
};

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. O descarte pode ocorrer a qualquer momento, inclusive durante a inicialização do componente.

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:

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 @implementsRazor.
  • 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 @implementsRazor.
  • 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:

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 o Resource 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