Partilhar via


Ciclo de vida do componente do ASP.NET Core Razor

Observação

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

Advertência

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

Importante

Estas informações referem-se a um produto de pré-lançamento que pode ser substancialmente modificado antes de ser lançado comercialmente. A Microsoft não oferece garantias, expressas ou implícitas, em relação às informações fornecidas aqui.

Para a versão atual, consulte a versão .NET 9 deste artigo.

Este artigo explica o ciclo de vida do componente ASP.NET Core Razor e como usar eventos de ciclo de vida.

Eventos do ciclo de vida

O componente Razor processa eventos do ciclo de vida do componente Razor num 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 em componentes durante a inicialização e renderização de componentes.

Este artigo simplifica o processamento de eventos do ciclo de vida do componente para esclarecer a lógica complexa da estrutura e não cobre todas as alterações feitas ao longo dos anos. Talvez seja necessário acessar a origem 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 aparecem 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 a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa Alternar ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Os diagramas simplificados a seguir ilustram o processamento de eventos do ciclo de vida do componente Razor. Os métodos C# associados aos eventos do ciclo de vida são definidos com exemplos nas seções a seguir deste artigo.

Eventos do ciclo de vida do componente:

  1. Se o componente estiver renderizando pela primeira vez em uma solicitação:
    • Crie a instância do componente.
    • Execute a injeção de propriedade.
    • Ligue para OnInitialized{Async}. Se um Task incompleto for retornado, o Task será aguardado e, em seguida, o componente será renderizado novamente. O método síncrono é chamado antes do método assíncrono.
  2. Ligue para OnParametersSet{Async}. Se um Task incompleto for retornado, o Task será aguardado e, em seguida, o componente será renderizado novamente. O método síncrono é chamado antes do método assíncrono.
  3. Renderizar para todos os trabalhos síncronos e concluir Tasks.

Observação

As ações assíncronas executadas em eventos do ciclo de vida podem atrasar a renderização de componentes ou a exibição de dados. Para obter mais informações, consulte a seção Manipular ações assíncronas incompletas na renderização mais adiante neste artigo.

Um componente pai é renderizado antes dos seus componentes filhos porque 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 do componente pai terá a garantia de ser concluída primeiro. Se a inicialização assíncrona do componente pai for usada, a ordem de conclusão da inicialização do componente pai e filho não poderá ser determinada porque depende do código de inicialização em execução.

Eventos do ciclo de vida do componente Razor no 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á renderizado novamente.
  3. Efetuar renderização para todo o trabalho síncrono e concluir as Tasks.

processamento de eventos DOM

O ciclo de vida Render:

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

Renderizar o ciclo de vida

As chamadas dos desenvolvedores para StateHasChanged resultam numa nova renderização. Para obter mais informações, consulte ASP.NETde renderização de componentes do Core Razor .

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

Em aplicações Blazor do lado do servidor, a pré-renderização aguarda 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. A quiescência pode levar a atrasos percetí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, consulte a seção , intitulada "Manipular ações assíncronas incompletas na renderização", mais adiante neste artigo.

Quando os parâmetros são definidos (SetParametersAsync)

SetParametersAsync define parâmetros fornecidos pelo pai do componente na árvore de renderização ou a partir de parâmetros de rota.

O parâmetro ParameterView do método contém o conjunto de valores do parâmetro do componente cada vez 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 atributo [Parameter] ou [CascadingParameter] que tem um valor correspondente no ParameterView. Os parâmetros que não têm um valor correspondente em ParameterView são mantidos inalterados.

Geralmente, seu código deve chamar o método de classe base (await base.SetParametersAsync(parameters);) ao substituir SetParametersAsync. Em cenários avançados, o código do desenvolvedor pode interpretar os valores dos parâmetros de entrada de qualquer maneira necessária, não invocando o método de classe base. Por exemplo, não há nenhum requisito para atribuir os parâmetros de entrada às propriedades da classe. No entanto, deve-se referir à fonte de referência ComponentBase quando estrutura o seu código sem chamar o método da classe base, pois este invoca outros métodos do ciclo de vida e desencadeia a renderização de forma complexa.

Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa Mudar ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Se você quiser confiar na lógica de inicialização e renderização de ComponentBase.SetParametersAsync mas não processar parâmetros de entrada, você tem a opção de passar um ParameterView vazio para o método de classe base:

await base.SetParametersAsync(ParameterView.Empty);

Se os manipuladores de eventos forem fornecidos no código do desenvolvedor, desconecte-os ao descartá-los. Para obter mais informações, consulte ASP.NETde descarte de componentes do Core Razor .

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 correspondência de parâmetros de rota não diferencie maiúsculas de minúsculas, TryGetValue só corresponde a nomes de parâmetros que diferenciam maiúsculas de minúsculas no modelo de rota. O exemplo a seguir requer o uso de /{Param?} no modelo de rota para obter o valor com TryGetValue, não /{param?}. Se /{param?} for usado neste cenário, TryGetValue retorna false e message não é definido como qualquer uma das strings 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 componentes (OnInitialized{Async})

OnInitialized e OnInitializedAsync são usados exclusivamente para inicializar um componente durante todo o tempo de vida da instância do componente. Os valores de parâmetros e as alterações de valores de parâmetros não devem afetar a inicialização executada nesses métodos. Por exemplo, o carregamento de opções estáticas em uma lista suspensa que não muda durante o tempo de vida do componente e que não depende de valores de parâmetros é realizado em um desses métodos de ciclo de vida. Se os valores dos parâmetros ou as alterações nos valores dos parâmetros afetarem o estado do componente, use OnParametersSet{Async} em vez disso.

Esses métodos são invocados quando o componente é inicializado depois de ter recebido seus parâmetros iniciais em SetParametersAsync. O método síncrono é chamado antes do método assíncrono.

Se for utilizada a inicialização síncrona do componente pai, é garantido que a inicialização do pai seja concluída antes da inicialização do componente filho. Se a inicialização assíncrona do componente pai for usada, a ordem de conclusão da inicialização do componente pai e filho não poderá ser determinada porque 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 com um for usada com lógica de inicialização personalizada, deve chamar 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 esteja em um estado válido para renderização quando OnInitializedAsync retorna após aguardar por um Taskpotencialmente incompleto. Se o método retornar um Taskincompleto, a parte do método que é concluída de forma síncrona deve deixar o componente em um estado válido para renderização. Para obter mais informações, consulte as observações introdutórias sobre o contexto de sincronização do ASP.NET CoreBlazor e o descarte de componentes do ASP.NET Core Razor.

Blazor aplicativos que pré-renderizam seu conteúdo no servidor chamam OnInitializedAsyncduas vezes:

  • Primeiramente, quando o componente é inicialmente renderizado de forma estática como parte integrante da página.
  • Uma segunda vez quando o navegador renderiza o componente.

Para evitar que o código do desenvolvedor no OnInitializedAsync seja executado duas vezes durante a pré-renderização, consulte a seção sobre reconexão com estado após a pré-renderização. O conteúdo da seção concentra-se em Blazor Web Apps e reconexão com estado SignalR. Para preservar o estado durante a execução do código de inicialização durante a pré-renderização, consulte Pré-renderização ASP.NET componentes do Core Razor.

Para evitar que o código do desenvolvedor no OnInitializedAsync seja executado duas vezes durante a pré-renderização, consulte a seção Reconexão com estado após a pré-renderização. Embora o conteúdo da seção se concentre em Blazor Server e na reconexão com estado SignalR, o cenário para pré-renderização em soluções de Blazor WebAssembly hospedadas (WebAssemblyPrerendered) envolve condições e abordagens semelhantes para evitar que o código do desenvolvedor seja executado duas vezes. Para preservar o estado durante a execução do código de inicialização na pré-renderização, consulte Integrar componentes do ASP.NET Core Razor com MVC ou Razor Páginas.

Enquanto um aplicativo Blazor está pré-renderizando, certas ações, como realizar chamadas para JavaScript (JS interoperabilidade), não são possíveis. Os componentes podem precisar renderizar de forma diferente quando pré-renderizados. Para obter mais informações, consulte a seção Prerenderização com interoperabilidade de JavaScript.

Se os manipuladores de eventos forem fornecidos no código do desenvolvedor, desconecte-os ao descartá-los. Para obter mais informações, consulte ASP.NETde descarte de componentes do Core Razor .

Use renderização de streaming com renderização estática do lado do servidor (SSR estático) ou pré-renderização para melhorar a experiência do utilizador em componentes que executam tarefas assíncronas de longa duração para uma renderização completa em OnInitializedAsync. Para obter mais informações, consulte os seguintes recursos:

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 é rerenderizado e fornecido:

    • 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ções internas, 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 sobre convenções de renderização, consulte ASP.NET Core Razor de renderização de componentes.

O método síncrono é chamado antes do método assíncrono.

Os métodos podem ser invocados mesmo que os valores dos parâmetros não tenham sido alterados. Esse comportamento ressalta a necessidade de os desenvolvedores implementarem lógica adicional dentro dos métodos para verificar se os valores dos parâmetros realmente foram alterados antes de reinicializar os dados ou o estado dependente desses parâmetros.

Para o componente de exemplo a seguir, navegue até a página do componente em uma URL:

  • Com uma data de início obtida a partir de StartDate: /on-parameters-set/2021-03-19
  • Sem uma data de início, onde StartDate é atribuído 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 propriedades 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, desconecte-os ao descartá-los. Para obter mais informações, consulte ASP.NETde descarte de componentes do Core Razor .

Se um componente descartável não utilizar um CancellationToken, OnParametersSet e OnParametersSetAsync devem verificar se o componente está descartado. Se OnParametersSetAsync retornar um Taskincompleto, o componente deve garantir que a parte do método que é concluída 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 de ASP.NET de contexto de sincronização do Core Blazor e ASP.NETde descarte de componentes do Core Razor .

Para obter mais informações sobre parâmetros e restrições de rota, consulte ASP.NET Core Blazor roteamento e navegação.

Para obter um exemplo de implementação manual de SetParametersAsync para melhorar o desempenho em alguns cenários, consulte ASP.NET Core Blazor práticas recomendadas de desempenho.

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 elementos e componentes são preenchidas neste ponto. Use este estágio para executar etapas de inicialização adicionais com o conteúdo renderizado, como chamadas de interoperabilidade JS que interagem com os elementos 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 renderização estática do lado do servidor (SSR estático) no servidor porque esses processos não estão conectados a um DOM do navegador ativo e já estão concluídos antes que o DOM seja atualizado.

No caso de OnAfterRenderAsync, o componente não é re-renderizado automaticamente após a conclusão de qualquer Task que seja retornado 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 elementos e componentes são preenchidas neste ponto. Use este estágio para executar etapas de inicialização adicionais com o conteúdo renderizado, como chamadas de interoperabilidade JS que interagem com os elementos 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 ativo e já está concluída antes que o DOM seja atualizado.

Em OnAfterRenderAsync, o componente não é renderizado novamente automaticamente após a conclusão de qualquer Task retornado 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");
    }
}

O exemplo de AfterRender.razor produz a seguinte saída 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 do ciclo de vida do 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 do OnAfterRenderAsync, a estrutura não agendará um ciclo de renderização adicional para seu 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 assim 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 alguma marcação HTML estática na resposta HTTP. Durante esta 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 interativo. 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, desconecte-os ao descartá-los. Para obter mais informações, consulte ASP.NETde descarte de componentes do Core Razor .

Métodos de ciclo de vida da classe base

Ao substituir os métodos de ciclo de vida do Blazor, não é necessário chamar os métodos de ciclo de vida da classe base para ComponentBase. No entanto, um componente deve chamar um método de ciclo de vida da classe base que foi substituído nas seguintes situações:

  • Ao substituir ComponentBase.SetParametersAsync, await base.SetParametersAsync(parameters); geralmente é invocado porque o método da classe base chama outros métodos de ciclo de vida e aciona a renderização de maneira complexa. Para obter mais informações, consulte a seção Quando os parâmetros são definidos (SetParametersAsync).
  • Se o método da classe base contiver lógica que precise ser executada. Os consumidores de bibliotecas geralmente chamam os métodos de ciclo de vida da classe base ao herdar essa classe, pois as classes base das bibliotecas frequentemente possuem lógica de ciclo de vida personalizada a executar. Se o aplicativo usa uma classe base de uma biblioteca, consulte a documentação da biblioteca para obter orientação.

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 seu estado foi alterado. Quando aplicável, invocar StateHasChanged enfileira uma re-renderização que ocorre quando a thread principal do aplicativo está livre.

StateHasChanged é automaticamente chamado para métodos EventCallback. Para obter mais informações sobre retornos de chamada de eventos, consulte ASP.NET Core Blazor event handling.

Para obter mais informações sobre a renderização de componentes e quando chamar StateHasChanged, incluindo quando invocá-la com ComponentBase.InvokeAsync, consulte a renderização de componentes do ASP.NET Core Razor.

Gerir ações assíncronas incompletas durante a renderização

As ações assíncronas executadas em eventos do ciclo de vida podem não ter sido concluídas antes que o componente seja renderizado. Os objetos podem estar null ou incompletamente preenchidos com dados enquanto o método de ciclo de vida está em execução. Forneça lógica de renderização para confirmar que os objetos foram inicializados. Renderize elementos de espaço reservado da interface do usuário (por exemplo, uma mensagem de carregamento) enquanto os objetos estão null.

No componente Slow seguinte, 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á renderizado novamente 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 é null para apresentar a mensagem de carregamento. O exemplo a seguir verifica a coleção de movies para null para exibir a mensagem de carregamento ou mostrar 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 de , o que significa que um componente não é renderizado até que todos os componentes na árvore de renderização tenham concluído a sua execução. Isso significa que uma mensagem de carregamento não é exibida enquanto o método OnInitializedAsync de um componente filho está executando uma tarefa de longa execução 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.

<SlowComponent />

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é-renderizaçã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 devido à inatividade.

Durante a pré-renderizaçã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é-renderizaçã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 a demonstração anterior ilustra, a inatividade durante a pré-renderização resulta em uma má experiência do usuário. Para melhorar a experiência do usuário, comece implementando de renderização de streaming para evitar esperar que a tarefa assíncrona seja concluída durante a pré-renderização.

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

@attribute [StreamRendering]

Quando o componente Home está em pré-renderização, o componente Slow é rapidamente renderizado com a sua mensagem de carregamento. O componente Home não espera dez segundos para que o componente Slow termine a renderização. No entanto, a mensagem concluída exibida no final da pré-renderização é substituída pela mensagem de carregamento enquanto o componente finalmente é renderizado, o que representa outro atraso de dez segundos. Isso ocorre porque o componente Slow está renderizando duas vezes, juntamente com LoadDataAsync sendo executado 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, como visto nas seguintes atualizações do componente Slow:

@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()
    {
        persistingSubscription =
            ApplicationState.RegisterOnPersisting(PersistData);

        if (!ApplicationState.TryTakeFromJson<string>("data", out var restored))
        {
            data = await LoadDataAsync();
        }
        else
        {
            data = restored!;
        }
    }

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

        return Task.CompletedTask;
    }

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

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

Combinando a renderização de 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 a melhor experiência do usuário.

Para obter mais informações, consulte os seguintes recursos:

A inatividade durante a pré-renderização resulta numa má experiência para o utilizador. O atraso pode ser resolvido em aplicativos destinados 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 aguardar a conclusão da tarefa assíncrona. Em versões do .NET anteriores à 8.0, executar uma tarefa em segundo plano de longa duração que carrega os dados após a renderização final pode resolver um longo atraso de renderização devido à inatividade.

Lidar com erros

Para obter informações sobre como gerir erros durante a execução dos métodos do ciclo de vida, consulte Gerir erros em aplicativos ASP.NET Core Blazor.

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. Uma vez que o navegador estabelece uma conexão SignalR de volta ao servidor, o componente é renderizado novamente e 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.
  • Após a conexão do servidor ter sido estabelecida.

Isso pode resultar em uma alteração percetível nos dados exibidos na interface do usuário quando o componente é finalmente renderizado. Para evitar esse comportamento, passe um identificador para armazenar em cache o estado durante a pré-renderização e para recuperar o estado após a pré-renderização.

O código a seguir demonstra uma WeatherForecastService que evita a alteração na exibição de dados devido à pré-renderização. O aguardado Delay (await Task.Delay(...)) simula um pequeno atraso antes de devolver dados do método GetForecastAsync.

Adicione IMemoryCache serviços com AddMemoryCache na coleção de serviços do aplicativo no ficheiro 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, consulte ASP.NET Core BlazorSignalR guidance.

O conteúdo desta seção concentra-se em Blazor Web Apps e na reconexãoSignalRcom estado. Para preservar o estado durante a execução do código de inicialização na pré-renderização, consulte componentes do ASP.NET Core Razor.

Embora o conteúdo desta seção se concentre em Blazor Server e na reconexão stateful SignalR, o cenário de pré-renderização em soluções hospedadas Blazor WebAssembly (WebAssemblyPrerendered) envolve condições e abordagens semelhantes para evitar a execução repetida de código de desenvolvedor. Para preservar o estado durante a execução do código de inicialização durante a pré-renderização, consulte Integrar ASP.NET componentes do Core Razor com MVC ou Razor Pages.

Pré-renderização com interoperação JavaScript

Esta seção se aplica a aplicativos do lado do servidor que pré-renderizam componentes Razor. A pré-renderização é abordada em Prerender ASP.NET Core components Razor.

Observação

A navegação interna para o encaminhamento interativo nos Blazor Web Apps não envolve pedir 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 roteamento interativo, execute uma recarga de página inteira para exemplos de componentes que demonstrem o comportamento de pré-renderização. Para obter mais informações, consulte Prerender componentes do ASP.NET Core Razor.

Esta seção se aplica a aplicativos do lado do servidor e aplicativos de Blazor WebAssembly hospedados que pré-renderizam componentes Razor. A prerenderização é abordada em Integrar componentes do ASP.NET Core Razor com MVC ou Razor Pages.

Durante a pré-renderização, a chamada para JavaScript (JS) não é possível. 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 seguinte função scrollElementIntoView:

window.scrollElementIntoView = (element) => {
  element.scrollIntoView();
  return element.getBoundingClientRect().top;
}

Onde IJSRuntime.InvokeAsync chama a função JS no código do componente, o ElementReference só é usado em OnAfterRenderAsync e não em qualquer método de ciclo de vida anterior, porque não há nenhum elemento HTML DOM até que o componente seja renderizado.

StateHasChanged (fonte de referência) é chamada para enfileirar a rerenderização do componente com o novo estado obtido da chamada de interoperabilidade JS (para obter mais informações, consulte ASP.NETde renderização do componente Core Razor ). Um loop infinito não é criado porque StateHasChanged só é chamado 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 uma melhor abordagem em aplicações de produção, consulte isolamento de JavaScript em módulos JavaScript.

Trabalho em segundo plano cancelável

Os componentes geralmente executam trabalho em segundo plano de longa duração, como fazer chamadas de rede (HttpClient) e interagir com bancos de dados. É desejável parar 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 itens de trabalho em segundo plano podem exigir cancelamento incluem:

  • Uma tarefa em segundo plano foi iniciada com dados de entrada ou parâmetros de processamento defeituosos.
  • O conjunto atual de execução de itens de trabalho em segundo plano deve ser substituído por um novo conjunto de itens de trabalho.
  • A prioridade das tarefas atualmente em execução deve ser alterada.
  • O aplicativo deve ser desligado para reimplantação do servidor.
  • Os recursos do servidor tornam-se limitados, exigindo o reagendamento de 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 trabalho em segundo plano assíncrono de longa duração.
  • BackgroundResourceMethod representa um método em segundo plano de longa duração 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;
        }
    }
}

Blazor Server eventos de reconexão

Os eventos do 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, apenas as atualizações da interface do usuário são interrompidas. As atualizações da 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 circuitos, consulte ASP.NET Core BlazorSignalR guidance.

Recursos adicionais

Manipular exceções detetadas fora do ciclo de vida de um componente Razor