Compartilhar via


Ciclo de vida do componente Razor do ASP.NET Core

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.

Aviso

Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, consulte a Política de Suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 9 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 a versão atual, consulte a versão .NET 9 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 você precise acessar a fonte de referência ComponentBase para integrar o processamento de eventos personalizados com o 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 assíncrono.
  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 assíncrono.
  3. Renderize para todos os trabalhos síncronos e Task completos.

Observação

Ações assíncronas executadas em eventos de ciclo de vida podem atrasar a renderização de componentes ou a exibição de dados. Para obter mais informações, confira a seção Tratar 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 síncrona do componente pai for utilizada, é garantido que a inicialização do pai seja 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 do ciclo de vida 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 assíncrono.

Ciclo de vida de renderização

As chamadas do desenvolvedor para StateHasChanged resultam em renderizar novamente. Para mais informações, consulte renderização de componentes no Razor ASP.NET Core.

Quiescência durante a pré-renderização

Em aplicativos de Blazor do lado do servidor, a pré-renderização aguarda a quiescência, o que significa que um componente não é renderizado até que todos os componentes na árvore de renderização tenham terminado a renderização. A quiescência pode levar a atrasos perceptíveis na renderização quando um componente executa tarefas de longa execução durante a inicialização e outros métodos de ciclo de vida, levando a uma experiência de usuário ruim. Para obter mais informações, confira a seção Tratar ações assíncronas incompletas na renderização mais adiante neste artigo.

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 de SetParametersAsync define o valor de cada propriedade com o atributo [Parameter] ou [CascadingParameter], que possui um valor correspondente em 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, consulte descarte de componente do ASP.NET CoreRazor.

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?}"

<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 síncrona do componente pai for utilizada, a conclusão da inicialização do pai será garantida 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"

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

Um componente deve garantir que ele esteja em um estado válido para renderização quando OnInitializedAsync aguarda um potencialmente incompleto Task. Se o método retornar uma incompleta Task, a parte do método que for concluída de forma síncrona deverá deixar o componente em um estado válido para renderização. Para obter mais informações, consulte as observações introdutórias do contexto de sincronização do ASP.NET Core Blazor e do descarte de componentes do ASP.NET CoreRazor.

Os aplicativos Blazor que geram previamente seu conteúdo no servidor chamam 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 Web Apps 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, consulte Integrar componentes principais Razor do ASP.NET com MVC ou Razor Pages.

Durante a pré-renderização de um aplicativo Blazor, determinadas ações, como invocar o JavaScript (JS interop), 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, consulte descarte de componente do ASP.NET CoreRazor.

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 os recursos a seguir:

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}"

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

Um componente deve garantir que ele esteja em um estado válido para renderização quando OnParametersSetAsync aguarda um potencialmente incompleto Task. Se o método retornar uma incompleta Task, a parte do método que for concluída de forma síncrona deverá deixar o componente em um estado válido para renderização. Para obter mais informações, consulte as observações introdutórias do contexto de sincronização do ASP.NET Core Blazor e do descarte de componentes do ASP.NET CoreRazor.

Se os manipuladores de eventos forem fornecidos no código do desenvolvedor, libere-os no descarte. Para obter mais informações, consulte descarte de componente do ASP.NET CoreRazor.

Se um componente descartável não usar um CancellationToken, OnParametersSet e OnParametersSetAsync deverão verificar se o componente foi descartado. Se OnParametersSetAsync retornar um incompleto Task, o componente deverá garantir que a parte do método que é concluído de forma síncrona deixe o componente em um estado válido para renderização. Para obter mais informações, consulte as observações introdutórias do contexto de sincronização do ASP.NET Core Blazor e do descarte de componentes do ASP.NET CoreRazor.

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 SetParametersAsync manual para melhorar o desempenho em alguns cenários, consulte ASP.NET Core práticas recomendadas de desempenho de renderizaçãoBlazor.

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("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, consulte descarte de componente do ASP.NET CoreRazor.

Métodos de ciclo de vida de classe base

Ao sobrescrever os métodos de ciclo de vida de Blazor, não é necessário chamar os métodos de ciclo de vida da 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"
@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;

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 a linha de execução principal do aplicativo está livre.

StateHasChanged é chamado automaticamente nos 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.

Tratar 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 Slow a seguir, OnInitializedAsync é substituído para executar de forma assíncrona uma tarefa de longa execução. Enquanto isLoading é true, uma mensagem de carregamento é exibida para o usuário. Depois que o Task retornado por OnInitializedAsync for concluído, o componente será re-renderizado com o estado atualizado, mostrando a mensagem "Finished!".

Slow.razor:

@page "/slow"

<h2>Slow Component</h2>

@if (isLoading)
{
    <div><em>Loading...</em></div>
}
else
{
    <div>Finished!</div>
}

@code {
    private bool isLoading = true;

    protected override async Task OnInitializedAsync()
    {
        await LoadDataAsync();
        isLoading = false;
    }

    private Task LoadDataAsync()
    {
        return Task.Delay(10000);
    }
}

O componente anterior usa uma variável isLoading para exibir a mensagem de carregamento. Uma abordagem semelhante é usada para um componente que carrega dados em uma coleção e verifica se a coleção está null para apresentar a mensagem de carregamento. O exemplo a seguir verifica a coleção movies para null exibir a mensagem de carregamento ou exibir a coleção de filmes:

@if (movies == null)
{
    <p><em>Loading...</em></p>
}
else
{
    @* display movies *@
}

@code {
    private Movies[]? movies;

    protected override async Task OnInitializedAsync()
    {
        movies = await GetMovies();
    }
}

A pré-renderização aguarda a quiescência, o que significa que um componente não é renderizado até que todos os componentes na árvore de renderização tenham concluído a renderização. Isso significa que uma mensagem de carregamento não é exibida enquanto o método OnInitializedAsync de um componente filho está executando uma tarefa longa durante a pré-renderização. Para demonstrar esse comportamento, coloque o componente Slow anterior no componente Home de um aplicativo de teste:

@page "/"

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<Slow />

Observação

Embora os exemplos nesta seção discutam o método de ciclo de vida OnInitializedAsync, outros métodos de ciclo de vida executados durante a pré-geração podem atrasar a renderização final de um componente. Apenas OnAfterRender{Async} não é executado durante a pré-renderização e é imune a atrasos causados por quiescência.

Durante a pré-geração, o componente Home não é renderizado até que o componente Slow seja renderizado, o que leva dez segundos. A interface do usuário fica em branco durante esse período de dez segundos e não há nenhuma mensagem de carregamento. Após a pré-geração, o componente Home é renderizado e a mensagem de carregamento do componente Slow é exibida. Após mais dez segundos, o componente Slow finalmente exibe a mensagem concluída.

Como ilustra a demonstração anterior, a quiescência durante a pré-renderização é uma experiência ruim para o usuário. Para melhorar a experiência do usuário, comece a implementar a renderização de streaming para evitar a espera pela conclusão da tarefa assíncrona durante a pré-renderização.

Adicione o atributo [StreamRendering] ao componente Slow (use [StreamRendering(true)] no .NET 8):

@attribute [StreamRendering]

Quando o componente Home é pré-renderizado, o componente Slow é renderizado rapidamente com sua mensagem de carregamento. O componente Home não aguarda dez segundos para que o componente Slow conclua a renderização. No entanto, a mensagem concluída exibida no final da pré-geração é substituída pela mensagem de carregamento enquanto o componente finalmente é renderizado, o que é outro atraso de dez segundos. Isso ocorre porque o componente Slow está sendo renderizado duas vezes, juntamente com LoadDataAsync em execução duas vezes. Quando um componente está acessando recursos, como serviços e bancos de dados, a execução dupla de chamadas de serviço e consultas de banco de dados cria uma carga indesejável nos recursos do aplicativo.

Para resolver a renderização dupla da mensagem de carregamento e a reexecução de chamadas de serviço e banco de dados, persista o estado pré-renderizado com PersistentComponentState para a renderização final do componente, conforme visto nas seguintes atualizações para o componente Slow:

@page "/slow"
@attribute [StreamRendering]

<h2>Slow Component</h2>

@if (Data is null)
{
    <div><em>Loading...</em></div>
}
else
{
    <div>@Data</div>
}

@code {
    [SupplyParameterFromPersistentComponentState]
    public string? Data { get; set; }

    protected override async Task OnInitializedAsync()
    {
        Data ??= await LoadDataAsync();
    }

    private async Task<string> LoadDataAsync()
    {
        await Task.Delay(5000);
        return "Finished!";
    }
}
@page "/slow"
@attribute [StreamRendering]
@implements IDisposable
@inject PersistentComponentState ApplicationState

<h2>Slow Component</h2>

@if (data is null)
{
    <div><em>Loading...</em></div>
}
else
{
    <div>@data</div>
}

@code {
    private string? data;
    private PersistingComponentStateSubscription persistingSubscription;

    protected override async Task OnInitializedAsync()
    {
        if (!ApplicationState.TryTakeFromJson<string>(nameof(data), out var restored))
        {
            data = await LoadDataAsync();
        }
        else
        {
            data = restored!;
        }

        // Call at the end to avoid a potential race condition at app shutdown
        persistingSubscription = ApplicationState.RegisterOnPersisting(PersistData);
    }

    private Task PersistData()
    {
        ApplicationState.PersistAsJson(nameof(data), data);

        return Task.CompletedTask;
    }

    private async Task<string> LoadDataAsync()
    {
        await Task.Delay(5000);
        return "Finished!";
    }

    void IDisposable.Dispose()
    {
        persistingSubscription.Dispose();
    }
}

Ao combinar a renderização por streaming com o estado persistente do componente:

  • Serviços e bancos de dados exigem apenas uma única chamada para inicialização de componentes.
  • Os componentes renderizam suas interfaces do usuário rapidamente com o carregamento de mensagens durante tarefas de longa execução para obter a melhor experiência do usuário.

Para saber mais, consulte os recursos a seguir:

:::moniker-end

A quiescência durante o pré-renderização é uma experiência ruim para o usuário. O atraso pode ser resolvido em aplicativos direcionados ao .NET 8 ou posterior com um recurso chamado de renderização de streaming, geralmente combinado com estado de componente persistente durante a pré-renderização para evitar esperar a conclusão da tarefa assíncrona. Em versões do .NET anteriores ao .NET 8, executar uma tarefa em segundo plano de execução longa que carrega os dados após a renderização final pode resolver um longo atraso de renderização devido à quiescência.

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;

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 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, consulte Integrar componentes principais Razor do ASP.NET com MVC ou Razor Pages.

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 componentes do ASP.NET Core de pré-renderizaçãoRazor.

Observação

A navegação interna para roteamento interativo em Blazor Web Appnão envolve solicitar novo conteúdo de página do servidor. Portanto, a pré-renderizaçã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 a aplicativos Blazor WebAssembly hospedados que pré-renderizam componentes Razor. A pré-renderização é abordada em Integrar componentes do ASP.NET Core Razor com MVC ou Razor Pages.

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 forma compatível com a pré-renderização.

A função a scrollElementIntoView seguir:

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 renderização do componente com o novo estado obtido da JS chamada de interoperabilidade (para obter mais informações, consulte ASP.NET Core Razor renderização do componente). 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.

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(10000, 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"
@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;
        }
    }
}

Para exibir um indicador de carregamento enquanto o trabalho em segundo plano está ocorrendo, use a abordagem a seguir.

Crie um componente de indicador de carregamento com o parâmetro Loading que pode exibir o conteúdo filho em um RenderFragment. Para o Loading parâmetro:

ContentLoading.razor:

@if (Loading)
{
    <progress id="loadingIndicator" aria-label="Content loading…"></progress>
}
else
{
    @ChildContent
}

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    [Parameter]
    public bool Loading { get; set; }
}

Para carregar estilos CSS para o indicador, adicione os estilos ao conteúdo <head> com o componente HeadContent. Para mais informações, confira o conteúdo do cabeçalho de controle em aplicativos ASP.NET CoreBlazor.

@if (Loading)
{
    <!-- OPTIONAL ...
    <HeadContent>
        <style>
            ...
        </style>
    </HeadContent>
    -->
    <progress id="loadingIndicator" aria-label="Content loading…"></progress>
}
else
{
    @ChildContent
}

...

Encapsule a marcação do componente Razor com o componente ContentLoading e passe um valor de um campo C# para o parâmetro Loading quando o trabalho de inicialização for executado pelo componente:

<ContentLoading Loading="@loading">
    ...
</ContentLoading>

@code {
    private bool loading = true;
    ...

    protected override async Task OnInitializedAsync()
    {
        await LongRunningWork().ContinueWith(_ => loading = false);
    }

    ...
}

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, consulte as diretrizes do ASP.NET Core.

Recursos adicionais

Trata exceções capturadas fora do ciclo de vida de um componente Razor