Partilhar via


Renderização de componentes 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 10 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.

Este artigo explica Razor a renderização de componentes nos aplicativos ASP.NET Core Blazor, incluindo quando chamar StateHasChanged para acionar manualmente a renderização de um componente.

Convenções de renderização para ComponentBase

Os componentes devem ser renderizados quando são pela primeira vez adicionados à hierarquia de componentes por um componente pai. Este é o único momento que um componente deve renderizar. Os componentes podem ser renderizados em outros momentos de acordo com a sua própria lógica e convenções.

Componentes Razor herdam da classe base ComponentBase, que contém lógica para disparar a re-renderização nos seguintes momentos:

Os componentes herdados do ComponentBase ignoram rerenderizações devido a atualizações de parâmetros se uma das seguintes opções for verdadeira:

  • Todos os parâmetros são de um conjunto de tipos conhecidos† ou qualquer tipo primitivo que não mudou desde que o conjunto anterior de parâmetros foi definido.

    †A estrutura Blazor usa um conjunto de regras internas e verificações explícitas de tipo de parâmetro para deteção de alterações. Estas regras e os tipos estão sujeitos a alterações a qualquer momento. Para obter mais informações, consulte a API ChangeDetection na fonte de referência do ASP.NET Core.

    Observação

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

  • A substituição do método ShouldRender do componente retorna false (a implementação de ComponentBase padrão sempre retorna true).

Controle o fluxo de renderização

Na maioria dos casos, as ComponentBase convenções resultam no subconjunto correto de rerenderizações de componentes após a ocorrência de um evento. Os desenvolvedores geralmente não são obrigados a fornecer lógica manual para dizer à estrutura quais componentes renderizar novamente e quando rerenderizá-los. O efeito geral das convenções da estrutura é que o componente que recebe um evento rerenderiza-se, o que, por sua vez, dispara recursivamente a rerenderização de componentes descendentes cujos parâmetros podem ter sido alterados.

Para obter mais informações sobre as implicações de desempenho das convenções da estrutura e como otimizar a hierarquia de componentes de um aplicativo para renderização, consulte ASP.NET Práticas recomendadas de desempenho de renderização principalBlazor.

Renderização de streaming

Use a renderização de streaming com renderização estática do lado do servidor (SSR estático) ou pré-renderização para atualizar o conteúdo no fluxo de resposta e melhorar a experiência do utilizador em componentes que executam tarefas assíncronas de longa duração para renderização completa.

Por exemplo, considere um componente que faz uma consulta de banco de dados de longa execução ou uma chamada de API da Web para renderizar dados quando a página é carregada. Normalmente, as tarefas assíncronas executadas como parte da renderização de um componente do lado do servidor devem ser concluídas antes que a resposta renderizada seja enviada, o que pode atrasar o carregamento da página. Qualquer atraso significativo na renderização da página prejudica a experiência do usuário. Para melhorar a experiência de utilizador, a renderização de streaming inicialmente renderiza a página inteira rapidamente com conteúdo temporário enquanto as operações assíncronas são executadas. Após a conclusão das operações, o conteúdo atualizado é enviado para o cliente na mesma conexão de resposta e integrado no DOM.

A renderização de streaming requer que o servidor evite o armazenamento em buffer da saída. Os dados de resposta devem fluir para o cliente à medida que os dados são gerados. Para hosts que impõem buffer, a renderização de streaming degrada-se de forma gradual e a página é carregada sem a necessidade de renderização de streaming.

Para transmitir atualizações de conteúdo, ao usar a renderização estática do lado do servidor (SSR estático) ou a pré-renderização, aplique o atributo [StreamRendering] no .NET 9 ou posterior (use [StreamRendering(true)] no .NET 8) ao componente. A renderização de streaming deve ser explicitamente habilitada porque as atualizações transmitidas podem fazer com que o conteúdo da página mude. Os componentes sem o atributo adotam automaticamente a renderização de streaming se o componente pai usar o recurso. Passe false para o atributo em um componente filho para desativar o recurso nesse ponto e mais abaixo na subárvore do componente. O atributo é funcional quando aplicado a componentes fornecidos por uma biblioteca de classes Razor.

Se a navegação melhorada estiver ativa, a renderização via streaming processa respostas de Não Encontrado sem recarregar a página. Quando a navegação aprimorada é bloqueada, a estrutura redireciona para o conteúdo Não encontrado com uma atualização de página.

A renderização de streaming só pode renderizar componentes que tenham uma rota, como uma NotFoundPage atribuição (NotFoundPage="...") ou uma atribuição de página de middleware de reexecução de páginas de código de status (UseStatusCodePagesWithReExecute). O fragmento de renderização Não Encontrado (<NotFound>...</NotFound>) e o conteúdo 404 ("DefaultNotFound" texto sem formatação) não têm rotas, portanto, não podem ser usados durante a renderização em fluxo contínuo.

Usos de renderização de conteúdo de streaming NavigationManager.NotFound (em ordem):

  • Caso esteja presente, um NotFoundPage passa para o componente Router.
  • Uma página de middleware de reexecução de páginas de código de status, se configurada.
  • Nenhuma ação se nenhuma das abordagens anteriores for adotada.

Usos de renderização de conteúdo sem streaming NavigationManager.NotFound (em ordem):

  • Caso esteja presente, um NotFoundPage passa para o componente Router.
  • Não foi encontrado o conteúdo do fragmento para renderizar, caso esteja presente. Não recomendado no .NET 10 ou posterior.
  • DefaultNotFound Conteúdo 404 ("Not found" texto simples).

O middleware de reexecução de páginas de código de status com UseStatusCodePagesWithReExecute tem precedência sobre problemas de roteamento de endereços baseados em navegador, como um URL incorreto digitado na barra de endereço do navegador ou a seleção de um link que não tenha ponto de extremidade no aplicativo.

O exemplo a seguir é baseado no componente Weather em um aplicativo criado a partir do modelo de projeto Blazor Web App. A chamada para Task.Delay simula a recuperação de dados meteorológicos de forma assíncrona. O componente inicialmente renderiza o conteúdo de espaço reservado ("Loading...") sem esperar que o atraso assíncrono seja concluído. Quando o atraso assíncrono é concluído e o conteúdo dos dados meteorológicos é gerado, o conteúdo é transmitido para a resposta e integrado na tabela de previsão do tempo.

Weather.razor:

@page "/weather"
@attribute [StreamRendering]

...

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        ...
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    ...

    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        await Task.Delay(500);

        ...

        forecasts = ...
    }
}

Suprimir a atualização da interface de utilizador (ShouldRender)

ShouldRender é chamado cada vez que um componente é renderizado. Substitua ShouldRender para gerenciar a atualização da interface do usuário. Se a implementação retornar true, a interface do usuário será atualizada.

Mesmo que ShouldRender seja substituído, o componente é sempre renderizado inicialmente.

ControlRender.razor:

@page "/control-render"

<PageTitle>Control Render</PageTitle>

<h1>Control Render Example</h1>

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender() => shouldRender;

    private void IncrementCount() => currentCount++;
}
@page "/control-render"

<PageTitle>Control Render</PageTitle>

<h1>Control Render Example</h1>

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender() => shouldRender;

    private void IncrementCount() => currentCount++;
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}

Para obter mais informações sobre as práticas recomendadas de desempenho relativas ao ShouldRender, consulte Práticas recomendadas de desempenho de renderização no ASP.NET CoreBlazor.

StateHasChanged

Chamar StateHasChanged enfileira uma nova renderização para ocorrer quando a thread principal da aplicação estiver livre.

Os componentes são enfileirados para renderização e não são enfileirados novamente se já houver uma nova renderização pendente. Se um componente chamar StateHasChanged cinco vezes seguidas em um loop, o componente só renderizará uma vez. Esse comportamento está codificado no ComponentBase, que verifica primeiro se o próprio já enfileirou uma rerenderização antes de adicionar outra.

Um componente pode renderizar várias vezes durante o mesmo ciclo, o que geralmente ocorre quando um componente tem filhos que interagem uns com os outros:

  • Um componente pai renderiza vários filhos.
  • Os componentes filho renderizam e acionam uma atualização no componente pai.
  • Um componente pai é renderizado novamente com um novo estado.

Este design permite que StateHasChanged seja chamado quando necessário, sem o risco de introduzir renderização desnecessária. Você pode sempre assumir o controle desse comportamento em componentes individuais implementando IComponent e manipulando direta e manualmente quando o componente é renderizado.

Considere o seguinte método IncrementCount que incrementa uma contagem, chama StateHasChangede incrementa a contagem novamente:

private void IncrementCount()
{
    currentCount++;
    StateHasChanged();
    currentCount++;
}

Ao percorrer o código no depurador, você pode pensar que a contagem é atualizada na interface do usuário para a primeira execução currentCount++ imediatamente após StateHasChanged ser chamada. No entanto, a interface do usuário não mostra uma contagem atualizada nesse ponto devido ao processamento síncrono que está ocorrendo para a execução desse método. Não há oportunidade para o renderizador renderizar o componente até que o manipulador de eventos seja concluído. A interface do utilizador exibe aumentos nas execuções currentCount++numa única renderização.

Se aguardares algo entre as currentCount++ linhas, a chamada esperada permite que o renderizador execute a renderização. Isso levou alguns desenvolvedores a invocar Delay com um atraso de um milissegundo nos componentes deles para permitir que uma renderização ocorra, mas não recomendamos abrandar arbitrariamente uma aplicação para enfileirar uma renderização.

A melhor abordagem é aguardar Task.Yield, que força o componente a processar o código de forma assíncrona e a gerar imagem durante o lote atual, com uma segunda geração de imagem num lote separado após a tarefa gerada executar a continuação.

Considere o seguinte método IncrementCount revisado, que atualiza a interface do usuário duas vezes porque a renderização enfileirada por StateHasChanged é executada quando a tarefa é gerada com a chamada para Task.Yield:

private async Task IncrementCount()
{
    currentCount++;
    StateHasChanged();
    await Task.Yield();
    currentCount++;
}

Tenha cuidado para não chamar StateHasChanged desnecessariamente, o que é um erro comum que impõe custos de renderização desnecessários. O código não precisa chamar StateHasChanged quando:

  • Manipulação rotineira de eventos, seja de forma síncrona ou assíncrona, uma vez que ComponentBase aciona uma renderização para a maioria dos manipuladores de eventos de rotina.
  • Implementação de lógica de ciclo de vida típica, como OnInitialized ou OnParametersSetAsync, seja de forma síncrona ou assíncrona, uma vez que ComponentBase aciona uma renderização para eventos típicos do ciclo de vida.

No entanto, pode fazer sentido chamar StateHasChanged nos casos descritos nas seguintes seções deste artigo:

Um manipulador assíncrono envolve várias fases assíncronas

Devido à maneira como as tarefas são definidas no .NET, um recetor de um Task só pode observar sua conclusão final, não estados assíncronos intermediários. Portanto, ComponentBase só pode acionar a rerenderização quando o Task é retornado pela primeira vez e quando o Task finalmente é concluído. A estrutura não pode saber quando voltar a renderizar um componente a outros pontos intermediários, como quando um IAsyncEnumerable<T>retorna dados numa série de Tasks intermediários. Se quiser renderizar novamente em pontos intermediários, ative StateHasChanged nesses pontos.

Considere o seguinte componente CounterState1, que atualiza a contagem quatro vezes cada vez que o método IncrementCount é executado:

  • As renderizações automáticas ocorrem após o primeiro e o último incremento de currentCount.
  • As renderizações manuais são acionadas por chamadas para StateHasChanged quando a estrutura não aciona automaticamente rerenderizações em pontos de processamento intermediários onde currentCount é incrementada.

CounterState1.razor:

@page "/counter-state-1"

<PageTitle>Counter State 1</PageTitle>

<h1>Counter State Example 1</h1>

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

<PageTitle>Counter State 1</PageTitle>

<h1>Counter State Example 1</h1>

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}

Receber uma chamada de algo externo ao sistema de renderização e manipulação de eventos Blazor

ComponentBase só conhece os seus próprios métodos de ciclo de vida e os eventos acionados por Blazor. ComponentBase não sabe sobre outros eventos que podem ocorrer no código. Por exemplo, quaisquer eventos C# gerados por um armazenamento de dados personalizado são desconhecidos para Blazor. Para que esses eventos acionem a rerenderização para exibir valores atualizados na interface do usuário, chame StateHasChanged.

Considere o seguinte componente CounterState2 que usa System.Timers.Timer para atualizar uma contagem em um intervalo regular e chama StateHasChanged para atualizar a interface do usuário:

  • OnTimerCallback é executado fora de qualquer fluxo de renderização gerenciado pelo Blazorou notificação de evento. Portanto, OnTimerCallback deve chamar StateHasChanged porque Blazor não está ciente das alterações em currentCount na chamada de retorno.
  • O componente implementa IDisposable, onde o Timer é descartado quando a estrutura chama o método Dispose. Para obter mais informações, consulte a eliminação de componentes do ASP.NET Core Razor.

Como o retorno de chamada é invocado fora do contexto de sincronização do Blazor, o componente deve envolver a lógica do OnTimerCallback em ComponentBase.InvokeAsync, para movê-lo para o contexto de sincronização do renderizador. Isso é equivalente a encaminhar para o thread da UI em outros frameworks de UI. StateHasChanged só pode ser chamado a partir do contexto de sincronização do renderizador e, caso contrário, lança uma exceção.

System.InvalidOperationException: 'O thread atual não está associado ao Dispatcher. Use InvokeAsync() para alternar a execução para o Dispatcher ao ativar a renderização ou o estado do componente.

CounterState2.razor:

@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<PageTitle>Counter State 2</PageTitle>

<h1>Counter State Example 2</h1>

<p>
    This counter demonstrates <code>Timer</code> disposal.
</p>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<PageTitle>Counter State 2</PageTitle>

<h1>Counter State Example 2</h1>

<p>
    This counter demonstrates <code>Timer</code> disposal.
</p>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new Timer(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

Para renderizar um componente fora da subárvore que é re-renderizado por um determinado evento

A interface do usuário pode envolver:

  1. Enviar um evento para um componente.
  2. Mudando algum estado.
  3. Rerenderizar um componente completamente diferente que não é descendente do componente que recebe o evento.

Uma maneira de lidar com este cenário é fornecer uma classe de gestão de estado, muitas vezes como um serviço de injeção de dependências (DI), injetado em vários componentes. Quando um componente chama um método no gerenciador de estado, o gerente de estado gera um evento C# que é recebido por um componente independente.

Para abordagens para gerenciar o estado, consulte os seguintes recursos:

Para a abordagem do gestor de estado, os eventos C# estão fora do pipeline de renderização Blazor. Chame StateHasChanged em outros componentes que deseja renderizar novamente em resposta aos eventos do gestor de estado.

A abordagem do gestor de estado é semelhante ao caso anterior com System.Timers.Timer na secção anterior. Como a pilha de chamadas de execução normalmente permanece no contexto de sincronização do renderizador, a chamada InvokeAsync normalmente não é necessária. Chamar InvokeAsync só é necessário se a lógica escapar do contexto de sincronização, como chamar ContinueWith num Task ou esperar uma Task com ConfigureAwait(false). Para obter mais informações, consulte a seção Receber uma chamada de algo externo ao sistema de Blazor renderização e manipulação de eventos.

Indicador de progresso de carregamento do WebAssembly para Blazor Web Apps

Um aplicativo pode adotar código personalizado para criar um indicador de progresso de carregamento. Para obter mais informações, consulte ASP.NET Core Blazor startup.