Trate erros em aplicativos ASP.NET CoreBlazor

Observação

Esta não é a versão mais recente deste artigo. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Aviso

Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, confira .NET e a Política de Suporte do .NET Core. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Importante

Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.

Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Este artigo descreve como Blazor gerencia exceções sem tratamento e como desenvolver aplicativos que detectam e lidam com erros.

Erros detalhados durante o desenvolvimento

Quando um aplicativo Blazor não está funcionando corretamente durante o desenvolvimento, receber informações detalhadas de erro do aplicativo ajuda na solução de problemas e na correção do problema. Quando ocorre um erro, os aplicativos Blazor exibem uma barra amarelo-clara na parte inferior da tela:

  • Durante o desenvolvimento, é realizado o direcionamento para o console do navegador, onde a exceção pode ser vista.
  • Na produção, a barra notifica o usuário de que ocorreu um erro e recomenda a atualização do navegador.

A interface do usuário para essa experiência de tratamento de erros faz parte dos Blazor modelos de projeto. Nem todas as versões dos modelos de projeto Blazor usam o atributo data-nosnippet para sinalizar aos navegadores para não armazenar em cache o conteúdo da interface do usuário do erro, mas todas as versões da documentação Blazor aplicam o atributo.

Em um Blazor Web App, personalize a experiência no componente MainLayout. Como o Auxiliar de Marca de Ambiente (por exemplo, <environment include="Production">...</environment>) não tem suporte em componentes Razor, o exemplo a seguir injeta IHostEnvironment para configurar mensagens de erro para ambientes diferentes.

Na parte superior de MainLayout.razor:

@inject IHostEnvironment HostEnvironment

Crie ou modifique a marcação da interface do usuário do erro Blazor:

<div id="blazor-error-ui" data-nosnippet>
    @if (HostEnvironment.IsProduction())
    {
        <span>An error has occurred.</span>
    }
    else
    {
        <span>An unhandled exception occurred.</span>
    }
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
</div>

Em um aplicativo Blazor Server, personalize a experiência no arquivo Pages/_Host.cshtml. O exemplo a seguir usa o Auxiliar de Marca de Ambiente para configurar mensagens de erro para ambientes diferentes.

Em um aplicativo Blazor Server, personalize a experiência no arquivo Pages/_Layout.cshtml. O exemplo a seguir usa o Auxiliar de Marca de Ambiente para configurar mensagens de erro para ambientes diferentes.

Em um aplicativo Blazor Server, personalize a experiência no arquivo Pages/_Host.cshtml. O exemplo a seguir usa o Auxiliar de Marca de Ambiente para configurar mensagens de erro para ambientes diferentes.

Crie ou modifique a marcação da interface do usuário do erro Blazor:

<div id="blazor-error-ui" data-nosnippet>
    <environment include="Staging,Production">
        An error has occurred.
    </environment>
    <environment include="Development">
        An unhandled exception occurred.
    </environment>
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
</div>

Em um aplicativo Blazor WebAssembly, personalize a experiência no arquivo wwwroot/index.html:

<div id="blazor-error-ui" data-nosnippet>
    An unhandled error has occurred.
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
</div>

O elemento blazor-error-ui normalmente está oculto devido à presença do estilo display: none da classe CSS blazor-error-ui na folha de estilos gerada automaticamente do aplicativo. Quando ocorre um erro, a estrutura aplica display: block ao elemento.

O elemento blazor-error-ui normalmente está oculto devido à presença do estilo display: none da classe da CSS blazor-error-uina folha de estilos do site na pasta wwwroot/css. Quando ocorre um erro, a estrutura aplica display: block ao elemento.

Erros de circuito detalhados

Essa seção se aplica aos Blazor Web Apps que operam em um circuito.

Esta seção aplica-se a aplicativos Blazor Server.

Embora erros do lado do cliente não incluam a pilha de chamadas e não forneçam detalhes sobre a causa do erro, os logs do servidor contêm essas informações. Para fins de desenvolvimento, as informações confidenciais de erro de circuito podem ser disponibilizadas para o cliente por meio da habilitação de erros detalhados.

Defina CircuitOptions.DetailedErrors como true. Para obter mais informações e um exemplo, consulte ASP.NET Core BlazorSignalR diretrizes.

Uma alternativa à definição de CircuitOptions.DetailedErrors é definir a chave de configuração DetailedErrors como true no arquivo de configurações do ambiente Development do aplicativo (appsettings.Development.json). Além disso, defina o log do lado do servidorSignalR (Microsoft.AspNetCore.SignalR) como Depurar ou Rastrear para registro em log detalhado SignalR.

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

A chave de configuração DetailedErrors também pode ser definida como true usando a variável de ambiente ASPNETCORE_DETAILEDERRORS com um valor de true em servidores de ambiente Development/Staging ou em seu sistema local.

Aviso

Sempre evite expor informações de erro a clientes na Internet, pois trata-se de um risco à segurança.

Erros detalhados para Razor a renderização do lado do servidor do componente

Esta seção aplica a Blazor Web Apps.

Use a opção RazorComponentsServiceOptions.DetailedErrors para controlar a produção de informações detalhadas sobre erros para Razor a renderização do lado do servidor do componente. O valor padrão é false.

O exemplo a seguir habilita erros detalhados:

builder.Services.AddRazorComponents(options => 
    options.DetailedErrors = builder.Environment.IsDevelopment());

Aviso

Somente habilite os erros detalhados no ambiente Development. Erros detalhados podem conter informações confidenciais sobre o aplicativo que usuários mal-intencionados podem usar em um ataque.

O exemplo anterior fornece um grau de segurança definindo o valor de DetailedErrors com base no valor retornado por IsDevelopment. Quando o aplicativo está no ambiente Development, DetailedErrors é definido como true. Essa abordagem não é infalível porque é possível hospedar um aplicativo de produção em um servidor público no ambiente Development.

Gerencie exceções sem tratamento no código do desenvolvedor

Para que um aplicativo continue após um erro, o aplicativo deve ter lógica de tratamento de erros. Outras seções ao longo deste artigo descrevem possíveis fontes de exceções sem tratamento.

Em produção, não renderize mensagens de exceção de estrutura ou rastreamentos de pilha na interface do usuário. A renderização de mensagens de exceção ou rastreamentos de pilha pode:

  • Divulgar informações confidenciais a usuários finais.
  • Ajudar usuários mal-intencionados a descobrir pontos fracos em um aplicativo que podem comprometer a segurança do aplicativo, servidor ou rede.

Exceções sem tratamento para circuitos

Esta seção se aplica aos aplicativos do lado do servidor que operam em um circuito.

os componentes Razor com interatividade do servidor habilitada estão com estado no servidor. Embora os usuários interajam com o componente no servidor, eles mantêm uma conexão com o servidor conhecido como circuito. O circuito contém instâncias de componente ativas, além de muitos outros aspectos do estado, como:

  • A saída renderizada mais recente dos componentes.
  • O conjunto atual de representantes de tratamento de eventos que podem ser disparados por eventos do lado do cliente.

Se um usuário abrir o aplicativo em várias guias do navegador, o usuário criará vários circuitos independentes.

Blazor trata a maioria das exceções sem tratamento como fatais para o circuito em que elas ocorrem. Se um circuito for terminado devido a uma exceção sem tratamento, o usuário só poderá continuar a interagir com o aplicativo recarregando a página para criar um novo circuito. Circuitos não pertencentes ao que foi terminado, os quais são circuitos para outros usuários ou outras guias do navegador, não são afetados. Esse cenário é semelhante a um aplicativo da área de trabalho que falha. O aplicativo com falha deve ser reiniciado, mas outros aplicativos não são afetados.

A estrutura termina um circuito quando ocorre uma exceção sem tratamento pelos seguintes motivos:

  • Uma exceção sem tratamento geralmente deixa o circuito em um estado indefinido.
  • A operação regular do aplicativo não pode ser garantida após uma exceção sem tratamento.
  • O aplicativo poderá apresentar vulnerabilidades quanto à segurança se o circuito continuar em um estado indefinido.

Tratamento de exceções globais

Para ver abordagens que lidam com exceções globalmente, confira as seguintes seções:

Limites de erro

Limites de erro oferecem conveniência na abordagem usada ao lidar com exceções. O componente ErrorBoundary:

  • Renderiza seu conteúdo filho quando um erro não ocorreu.
  • Renderiza a interface do usuário de erro quando uma exceção sem tratamento é gerada por qualquer componente dentro do limite de erro.

Para definir um limite de erro, use o componente ErrorBoundary para encapsular um ou mais outros componentes. O limite de erro gerencia exceções sem tratamento geradas pelos componentes que ele encapsula.

<ErrorBoundary>
    ...
</ErrorBoundary>

Para implementar um limite de erro de forma global, adicione o limite em torno do conteúdo do corpo do layout principal do aplicativo.

Em MainLayout.razor:

<article class="content px-4">
    <ErrorBoundary>
        @Body
    </ErrorBoundary>
</article>

Em Blazor Web Apps com o limite de erro aplicado apenas a um componente MainLayout estático, o limite só fica ativo durante a SSR estática (renderização estática do lado do servidor). O limite não é ativado apenas porque um componente mais abaixo da hierarquia de componentes é interativo.

Um modo de renderização interativo não pode ser aplicado ao componente MainLayout porque o parâmetro Body do componente é um delegado RenderFragment, que é um código arbitrário e não pode ser serializado. Para habilitar amplamente a interatividade para o componente MainLayout e o rest dos componentes mais abaixo da hierarquia de componentes, o aplicativo deve adotar um modo de renderização interativo global aplicando o modo de renderização interativo às instâncias do componente HeadOutlet e Routes no componente raiz do aplicativo, que normalmente é o componente App. O exemplo a seguir adota o modo de renderização do Servidor Interativo (InteractiveServer) globalmente.

Em Components/App.razor:

<HeadOutlet @rendermode="InteractiveServer" />

...

<Routes @rendermode="InteractiveServer" />

Se preferir não habilitar a interatividade global, coloque o limite de erro mais abaixo na hierarquia de componentes. Os conceitos importantes a se ter em mente são que, onde quer que o limite de erro seja colocado:

  • Se o componente em que o limite de erro é colocado não for interativo, o limite de erro só poderá ser ativado no servidor durante o SSR estático. Por exemplo, o limite pode ser ativado quando um erro é gerado em um método de ciclo de vida do componente, mas não para um evento disparado pela interatividade do usuário dentro do componente, como um erro gerado por um manipulador de clique de botão.
  • Se o componente em que o limite de erro é colocado for interativo, o limite de erro será capaz de ativar para componentes interativos que ele encapsula.

Observação

As considerações anteriores não são relevantes para aplicativos Blazor WebAssembly autônomos porque a CSR (renderização do lado do cliente) de um aplicativo Blazor WebAssembly é completamente interativa.

Considere o exemplo a seguir, em que uma exceção gerada por um componente de contador inserido é capturada por um limite de erro no componente Home, que adota um modo de renderização interativo.

EmbeddedCounter.razor:

<h1>Embedded Counter</h1>

<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;

        if (currentCount > 5)
        {
            throw new InvalidOperationException("Current count is too big!");
        }
    }
}

Home.razor:

@page "/"
@rendermode InteractiveServer

<PageTitle>Home</PageTitle>

<h1>Home</h1>

<ErrorBoundary>
    <EmbeddedCounter />
</ErrorBoundary>

Considere o exemplo a seguir, em que uma exceção gerada por um componente de contador inserido é capturada por um limite de erro no componente Home.

EmbeddedCounter.razor:

<h1>Embedded Counter</h1>

<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;

        if (currentCount > 5)
        {
            throw new InvalidOperationException("Current count is too big!");
        }
    }
}

Home.razor:

@page "/"

<PageTitle>Home</PageTitle>

<h1>Home</h1>

<ErrorBoundary>
    <EmbeddedCounter />
</ErrorBoundary>

Se a exceção sem tratamento for gerada para currentCount maior que cinco:

  • O erro é registrado normalmente (System.InvalidOperationException: Current count is too big!).
  • A exceção é tratada pelo limite de erro.
  • A interface do usuário de erro padrão é renderizada pelo limite de erro.

O componente ErrorBoundary renderiza um elemento vazio <div> usando a classe CSS blazor-error-boundary para seu conteúdo de erro. As cores, o texto e o ícone da interface do usuário padrão são definidos na folha de estilos do aplicativo na pasta wwwroot. Portanto, você está livre para personalizar a interface do usuário do erro.

A interface do usuário de erro padrão renderizada por um limite de erro, que tem um plano de fundo vermelho, o texto

Para alterar o conteúdo de erro padrão:

  • Encapsule os componentes do limite de erro na propriedade ChildContent.
  • Defina a propriedade ErrorContent com o conteúdo do erro.

O exemplo a seguir encapsula o componente EmbeddedCounter e fornece conteúdo de erro personalizado:

<ErrorBoundary>
    <ChildContent>
        <EmbeddedCounter />
    </ChildContent>
    <ErrorContent>
        <p class="errorUI">😈 A rotten gremlin got us. Sorry!</p>
    </ErrorContent>
</ErrorBoundary>

Para o exemplo anterior, a folha de estilos do aplicativo, presumivelmente, inclui uma classe CSS errorUI para estilizar o conteúdo. O conteúdo do erro é renderizado da propriedade ErrorContent sem um elemento em nível de bloco. Um elemento em nível de bloco, como uma divisão (<div>) ou um elemento de parágrafo (<p>), pode encapsular a marcação de conteúdo de erro, mas não é necessário.

Opcionalmente, use o contexto (@context) do ErrorContent para obter dados de erro:

<ErrorContent>
    @context.HelpLink
</ErrorContent>

O ErrorContent também podem nomear o contexto. No exemplo a seguir, o nome do contexto é exception:

<ErrorContent Context="exception">
    @exception.HelpLink
</ErrorContent>

Aviso

Sempre evite expor informações de erro a clientes na Internet, pois trata-se de um risco à segurança.

Se o limite de erro for definido no layout do aplicativo, a interface do usuário do erro será vista independentemente de para qual página o usuário navegará após a ocorrência do erro. Recomendamos definir rigorosamente os limites de erro de escopo na maioria dos cenários. Se a definição do escopo de um limite de erro for ampla, ele poderá ser redefinido para um estado de ausência de erro em eventos de navegação de página subsequentes, chamando o método 1 do limite de erro Recover.

No MainLayout.razor:

...

<ErrorBoundary @ref="errorBoundary">
    @Body
</ErrorBoundary>

...

@code {
    private ErrorBoundary? errorBoundary;

    protected override void OnParametersSet()
    {
        errorBoundary?.Recover();
    }
}

Para evitar o loop infinito em que a recuperação apenas remete um componente que gera o erro novamente, não chame Recover da lógica de renderização. Chame Recover apenas quando:

  • O usuário executa um gesto de interface do usuário, como selecionar um botão para indicar que deseja repetir um procedimento ou quando o usuário navega para um novo componente.
  • A lógica adicional executada também limpa a exceção. Quando o componente é renderizado, o erro não ocorre novamente.

O exemplo a seguir permite que o usuário se recupere da exceção com um botão:

<ErrorBoundary @ref="errorBoundary">
    <ChildContent>
        <EmbeddedCounter />
    </ChildContent>
    <ErrorContent>
        <div class="alert alert-danger" role="alert">
            <p class="fs-3 fw-bold">😈 A rotten gremlin got us. Sorry!</p>
            <p>@context.HelpLink</p>
            <button class="btn btn-info" @onclick="_ => errorBoundary?.Recover()">
                Clear
            </button>
        </div>
    </ErrorContent>
</ErrorBoundary>

@code {
    private ErrorBoundary? errorBoundary;
}

Você também pode criar uma subclasse ErrorBoundary para processamento personalizado substituindo OnErrorAsync. O exemplo a seguir apenas registra o erro, mas você pode implementar qualquer código de tratamento de erros que desejar. Você pode remover a linha que retorna um CompletedTask se o código aguardar uma tarefa assíncrona.

CustomErrorBoundary.razor:

@inherits ErrorBoundary
@inject ILogger<CustomErrorBoundary> Logger

@if (CurrentException is null)
{
    @ChildContent
}
else if (ErrorContent is not null)
{
    @ErrorContent(CurrentException)
}

@code {
    protected override Task OnErrorAsync(Exception ex)
    {
        Logger.LogError(ex, "😈 A rotten gremlin got us. Sorry!");
        return Task.CompletedTask;
    }
}

O exemplo anterior também pode ser implementado como uma classe.

CustomErrorBoundary.cs:

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;

namespace BlazorSample;

public class CustomErrorBoundary : ErrorBoundary
{
    [Inject]
    ILogger<CustomErrorBoundary> Logger {  get; set; } = default!;

    protected override Task OnErrorAsync(Exception ex)
    {
        Logger.LogError(ex, "😈 A rotten gremlin got us. Sorry!");
        return Task.CompletedTask;
    }
}

Qualquer uma das implementações anteriores usadas em um componente:

<CustomErrorBoundary>
    ...
</CustomErrorBoundary>

Tratamento de exceção global alternativo

A abordagem descrita nesta seção se aplica aos aplicativos Blazor Server, Blazor WebAssembly e Blazor Web App que adotam um modo de renderização interativo global (InteractiveServer, InteractiveWebAssembly ou InteractiveAuto). A abordagem não funciona com Blazor Web Apps que adotam modos de renderização por página/componente ou a SSR estática (renderização estática do lado do servidor), porque a abordagem depende de um CascadingValue/CascadingParameter, que não funciona entre limites de modo de renderização ou com componentes que adotam a SSR estática.

Uma alternativa ao uso de Limites de erro (ErrorBoundary) é passar um componente de erro personalizado como um CascadingValue para componentes filho. Uma vantagem de usar um componente em vez de usar um serviço injetado ou uma implementação de agente personalizado é que um componente em cascata pode renderizar conteúdo e aplicar estilos CSS quando ocorre um erro.

O exemplo de componente ProcessError a seguir simplesmente registra erros, mas os métodos do componente podem processar erros de qualquer modo exigido pelo aplicativo, inclusive por meio do uso de vários métodos de processamento de erros.

ProcessError.razor:

@inject ILogger<ProcessError> Logger

<CascadingValue Value="this">
    @ChildContent
</CascadingValue>

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

    public void LogError(Exception ex)
    {
        Logger.LogError("ProcessError.LogError: {Type} Message: {Message}", 
            ex.GetType(), ex.Message);

        // Call StateHasChanged if LogError directly participates in 
        // rendering. If LogError only logs or records the error,
        // there's no need to call StateHasChanged.
        //StateHasChanged();
    }
}

Observação

Para obter mais informações sobre RenderFragment, confira Componentes Razordo ASP.NET Core.

Ao usar essa abordagem em um Blazor Web App, abra o componente Routes e encapsule o componente Router (<Router>...</Router>) com o componente ProcessError. Isso permite que o componente ProcessError seja reduzido em cascata a qualquer componente do aplicativo em que o componente ProcessError é recebido como um CascadingParameter.

Em Routes.razor:

<ProcessError>
    <Router ...>
        ...
    </Router>
</ProcessError>

Ao usar essa abordagem em um aplicativo Blazor Server ou Blazor WebAssembly, abra o componente App, encapsule o componente Router (<Router>...</Router>) com o componente ProcessError. Isso permite que o componente ProcessError seja reduzido em cascata a qualquer componente do aplicativo em que o componente ProcessError é recebido como um CascadingParameter.

No App.razor:

<ProcessError>
    <Router ...>
        ...
    </Router>
</ProcessError>

Para processar erros em um componente:

  • Designe o componente ProcessError como um CascadingParameter no bloco @code. Em um exemplo do componente Counter em um aplicativo com base em um modelo de projeto Blazor, adicione a seguinte propriedade ProcessError:

    [CascadingParameter]
    public ProcessError? ProcessError { get; set; }
    
  • Chame um método de processamento de erro em qualquer bloco catch com um tipo de exceção apropriado. O exemplo do componente ProcessError oferece apenas um único método LogError, mas o componente de processamento de erros pode fornecer qualquer número de métodos de processamento de erros para atender aos requisitos alternativos de processamento de erros em todo o aplicativo. O seguinte exemplo de bloco @code do componente Counter inclui o parâmetro em cascata ProcessError e intercepta uma exceção para registro em log quando a contagem é maior que cinco:

    @code {
        private int currentCount = 0;
    
        [CascadingParameter]
        public ProcessError? ProcessError { get; set; }
    
        private void IncrementCount()
        {
            try
            {
                currentCount++;
    
                if (currentCount > 5)
                {
                    throw new InvalidOperationException("Current count is over five!");
                }
            }
            catch (Exception ex)
            {
                ProcessError?.LogError(ex);
            }
        }
    }
    

O erro registrado em log:

fail: {COMPONENT NAMESPACE}.ProcessError[0]
ProcessError.LogError: System.InvalidOperationException Message: Current count is over five!

Se o método LogError participar diretamente da renderização, como por exemplo, mostrando uma barra de mensagens de erro personalizada ou alterando os estilos CSS dos elementos renderizados, chame StateHasChanged ao final do método LogErrorpara renderizar novamente a interface do usuário.

Como as abordagens nesta seção lidam com erros com uma instrução try-catch, a conexão SignalR do aplicativo entre o cliente e o servidor não é interrompida quando ocorre um erro e o circuito permanece ativo. Outras exceções sem tratamento permanecem fatais para circuitos. Para obter mais informações, consulte a seção sobre como um circuito reage a exceções sem tratamento.

Um aplicativo pode usar um componente de processamento de erros como um valor em cascata para processar erros de maneira centralizada.

O componente ProcessErrora seguir se faz passar por CascadingValue para componentes filhos. O exemplo a seguir apenas registra o erro, mas os métodos do componente podem processar erros de qualquer modo exigido pelo aplicativo, inclusive por meio do uso de vários métodos de processamento de erros. Uma vantagem de usar um componente em vez de usar um serviço injetado ou uma implementação de agente personalizado é que um componente em cascata pode renderizar conteúdo e aplicar estilos CSS quando ocorre um erro.

ProcessError.razor:

@using Microsoft.Extensions.Logging
@inject ILogger<ProcessError> Logger

<CascadingValue Value="this">
    @ChildContent
</CascadingValue>

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

    public void LogError(Exception ex)
    {
        Logger.LogError("ProcessError.LogError: {Type} Message: {Message}", 
            ex.GetType(), ex.Message);
    }
}

Observação

Para obter mais informações sobre RenderFragment, confira Componentes Razordo ASP.NET Core.

No componente App, encapsule o componente Router com o componente ProcessError. Isso permite que o componente ProcessError seja reduzido em cascata a qualquer componente do aplicativo em que o componente ProcessError é recebido como um CascadingParameter.

App.razor:

<ProcessError>
    <Router ...>
        ...
    </Router>
</ProcessError>

Para processar erros em um componente:

  • Designe o componente ProcessError como um CascadingParameter no bloco @code:

    [CascadingParameter]
    public ProcessError ProcessError { get; set; }
    
  • Chame um método de processamento de erro em qualquer bloco catch com um tipo de exceção apropriado. O exemplo do componente ProcessError oferece apenas um único método LogError, mas o componente de processamento de erros pode fornecer qualquer número de métodos de processamento de erros para atender aos requisitos alternativos de processamento de erros em todo o aplicativo.

    try
    {
        ...
    }
    catch (Exception ex)
    {
        ProcessError.LogError(ex);
    }
    

Usando o exemplo do componente ProcessError e do método LogError anteriores, o console de ferramentas de desenvolvedor do navegador indica o erro interceptado e registrado:

fail: {COMPONENT NAMESPACE}.Shared.ProcessError[0]
ProcessError.LogError: System.NullReferenceException Message: Object reference not set to an instance of an object.

Se o método LogError participar diretamente da renderização, como por exemplo, mostrando uma barra de mensagens de erro personalizada ou alterando os estilos CSS dos elementos renderizados, chame StateHasChanged ao final do método LogErrorpara renderizar novamente a interface do usuário.

Como as abordagens nesta seção lidam com erros com uma instrução try-catch, a conexão SignalR do aplicativo Blazor entre o cliente e o servidor não é interrompida quando ocorre um erro e o circuito permanece ativo. Qualquer exceção sem tratamento é fatal para um circuito. Para obter mais informações, consulte a seção sobre como um circuito reage a exceções sem tratamento.

Erros de registro com um provedor persistente

Se ocorrer uma exceção sem tratamento, a exceção será registrada em instâncias ILogger configuradas no contêiner de serviço. Os aplicativos Blazor fazem registro em log da saída do console com o Provedor de Log do Console. Considere fazer registro em log em um local do servidor (ou em um API Web de back-end para aplicativos do lado do cliente) com um provedor que gerencie o tamanho do log e a rotação de logs. Como alternativa, o aplicativo pode usar um serviço de Gerenciamento de Desempenho de Aplicativos (APM), como o Azure Application Insights (Azure Monitor).

Observação

Os recursos nativos do Application Insights para dar suporte a aplicativos do lado do cliente e o suporte de estrutura nativa Blazor do Google Analytics podem ser disponibilizados em versões futuras dessas tecnologias. Para obter mais informações, consulte Insights sobre Aplicativos de Suporte em Blazor Lado do Cliente WASM (microsoft/ApplicationInsights-dotnet #2143) e Análise e Diagnóstico da Web (com links para implementações da comunidade) (dotnet/aspnetcore #5461). Enquanto isso, um aplicativo do lado do cliente pode usar o SDK do JavaScript no Application InsightsJS com interoperabilidade para registrar erros diretamente no Application Insights de um aplicativo do lado do cliente.

Durante o desenvolvimento em um aplicativo Blazor operando um circuito, o aplicativo geralmente envia todos os detalhes das exceções ao console do navegador para auxiliar na depuração. Em produção, erros detalhados não são enviados aos clientes, mas os detalhes completos de uma exceção são registrados no servidor.

Você deve decidir quais incidentes registrar e o nível de gravidade dos incidentes registrados. Usuários hostis podem disparar erros deliberadamente. Por exemplo, não registre um incidente com base em um erro em que é fornecido um ProductIddesconhecido na URL de um componente que exiba os detalhes do produto. Nem todos os erros devem ser tratados como incidentes para registro em log.

Para obter mais informações, confira os seguintes artigos:

‡Aplica-se a aplicativos Blazor do lado do servidor e outros aplicativos ASP.NET Core do lado do servidor que são aplicativos de back-end de API Web para Blazor. Aplicativos do lado do cliente podem interceptar e enviar informações de erro do cliente para uma API Web, que registra as informações de erro em um provedor de log persistente.

Se ocorrer uma exceção sem tratamento, a exceção será registrada em instâncias ILogger configuradas no contêiner de serviço. Os aplicativos Blazor fazem registro em log da saída do console com o Provedor de Log do Console. Considere fazer logon em um local mais permanente no servidor, enviando informações de erro a uma API Web de back-end que usa um provedor de log com gerenciamento de tamanho de log e rotação de logs. Como alternativa, o aplicativo de API Web de back-end pode usar um serviço de Gerenciamento de Desempenho de Aplicativo (APM), como o Azure Application Insights (Azure Monitor)†, para registrar informações de erro recebidas dos clientes.

Você deve decidir quais incidentes registrar e o nível de gravidade dos incidentes registrados. Usuários hostis podem disparar erros deliberadamente. Por exemplo, não registre um incidente com base em um erro em que é fornecido um ProductIddesconhecido na URL de um componente que exiba os detalhes do produto. Nem todos os erros devem ser tratados como incidentes para registro em log.

Para obter mais informações, confira os seguintes artigos:

†Os recursos nativos do Application Insights para dar suporte a aplicativos do lado do cliente e suporte de estrutura nativa do Blazor para Google Analytics podem ser disponibilizados em versões futuras dessas tecnologias. Para obter mais informações, consulte Insights sobre Aplicativos de Suporte em Blazor Lado do Cliente WASM (microsoft/ApplicationInsights-dotnet #2143) e Análise e Diagnóstico da Web (com links para implementações da comunidade) (dotnet/aspnetcore #5461). Enquanto isso, um aplicativo do lado do cliente pode usar o SDK do JavaScript no Application InsightsJS com interoperabilidade para registrar erros diretamente no Application Insights de um aplicativo do lado do cliente.

‡Aplica-se a aplicativos ASP.NET Core do lado do servidor que são aplicativos de back-end de API Web para aplicativos Blazor. Aplicativos do lado do cliente podem interceptar e enviar informações de erro a uma API Web, que registra as informações de erro em um provedor de log persistente.

Locais em que erros podem ocorrer

A estrutura e o código do aplicativo podem disparar exceções sem tratamento em qualquer dos locais a seguir, que são descritos mais adiante ao longo deste artigo:

Instanciação de componentes

Quando Blazor cria uma instância de um componente:

  • O construtor do componente é invocado.
  • Os construtores de serviços de DI fornecidos ao construtor do componente por meio da diretiva @injectou do [Inject] atributo são invocados.

Um erro em um construtor executado ou um setter para qualquer propriedade [Inject] resulta em uma exceção sem tratamento e impede que a estrutura instancie o componente. Se o aplicativo estiver operando em um circuito, o circuito falhará. Se a lógica do construtor puder gerar exceções, o aplicativo deverá interceptar as exceções usando uma instrução try-catch com tratamento de erros e registro em log.

Métodos de ciclo de vida

Durante o tempo de vida de um componente, Blazor invoca métodos de ciclo de vida. Se qualquer método de ciclo de vida gerar uma exceção, de forma síncrona ou assíncrona, a exceção será fatal para um circuito. Para que os componentes lidem com erros em métodos de ciclo de vida, adicione a lógica de tratamento de erros.

No exemplo a seguir, em que OnParametersSetAsync chama um método para obter um produto:

  • Uma exceção gerada no método ProductRepository.GetProductByIdAsync é tratada por uma instrução try-catch.
  • Quando o bloco catch é executado:
    • loadFailed é definido como true, o qual é usado para exibir uma mensagem de erro para o usuário.
    • O erro é registrado.
@page "/product-details/{ProductId:int?}"
@inject ILogger<ProductDetails> Logger
@inject IProductRepository Product

<PageTitle>Product Details</PageTitle>

<h1>Product Details Example</h1>

@if (details != null)
{
    <h2>@details.ProductName</h2>
    <p>
        @details.Description
        <a href="@details.Url">Company Link</a>
    </p>
    
}
else if (loadFailed)
{
    <h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
    <h1>Loading...</h1>
}

@code {
    private ProductDetail? details;
    private bool loadFailed;

    [Parameter]
    public int ProductId { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        try
        {
            loadFailed = false;

            // Reset details to null to display the loading indicator
            details = null;

            details = await Product.GetProductByIdAsync(ProductId);
        }
        catch (Exception ex)
        {
            loadFailed = true;
            Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
        }
    }

    public class ProductDetail
    {
        public string? ProductName { get; set; }
        public string? Description { get; set; }
        public string? Url { get; set; }
    }

    /*
    * Register the service in Program.cs:
    * using static BlazorSample.Components.Pages.ProductDetails;
    * builder.Services.AddScoped<IProductRepository, ProductRepository>();
    */

    public interface IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id);
    }

    public class ProductRepository : IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id)
        {
            return Task.FromResult(
                new ProductDetail()
                {
                    ProductName = "Flowbee ",
                    Description = "The Revolutionary Haircutting System You've Come to Love!",
                    Url = "https://flowbee.com/"
                });
        }
    }
}
@page "/product-details/{ProductId:int?}"
@inject ILogger<ProductDetails> Logger
@inject IProductRepository Product

<PageTitle>Product Details</PageTitle>

<h1>Product Details Example</h1>

@if (details != null)
{
    <h2>@details.ProductName</h2>
    <p>
        @details.Description
        <a href="@details.Url">Company Link</a>
    </p>
    
}
else if (loadFailed)
{
    <h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
    <h1>Loading...</h1>
}

@code {
    private ProductDetail? details;
    private bool loadFailed;

    [Parameter]
    public int ProductId { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        try
        {
            loadFailed = false;

            // Reset details to null to display the loading indicator
            details = null;

            details = await Product.GetProductByIdAsync(ProductId);
        }
        catch (Exception ex)
        {
            loadFailed = true;
            Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
        }
    }

    public class ProductDetail
    {
        public string? ProductName { get; set; }
        public string? Description { get; set; }
        public string? Url { get; set; }
    }

    /*
    * Register the service in Program.cs:
    * using static BlazorSample.Components.Pages.ProductDetails;
    * builder.Services.AddScoped<IProductRepository, ProductRepository>();
    */

    public interface IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id);
    }

    public class ProductRepository : IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id)
        {
            return Task.FromResult(
                new ProductDetail()
                {
                    ProductName = "Flowbee ",
                    Description = "The Revolutionary Haircutting System You've Come to Love!",
                    Url = "https://flowbee.com/"
                });
        }
    }
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository

@if (details != null)
{
    <h1>@details.ProductName</h1>
    <p>@details.Description</p>
}
else if (loadFailed)
{
    <h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
    <h1>Loading...</h1>
}

@code {
    private ProductDetail? details;
    private bool loadFailed;

    [Parameter]
    public int ProductId { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        try
        {
            loadFailed = false;

            // Reset details to null to display the loading indicator
            details = null;

            details = await ProductRepository.GetProductByIdAsync(ProductId);
        }
        catch (Exception ex)
        {
            loadFailed = true;
            Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
        }
    }

    public class ProductDetail
    {
        public string? ProductName { get; set; }
        public string? Description { get; set; }
    }

    public interface IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id);
    }
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository

@if (details != null)
{
    <h1>@details.ProductName</h1>
    <p>@details.Description</p>
}
else if (loadFailed)
{
    <h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
    <h1>Loading...</h1>
}

@code {
    private ProductDetail? details;
    private bool loadFailed;

    [Parameter]
    public int ProductId { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        try
        {
            loadFailed = false;

            // Reset details to null to display the loading indicator
            details = null;

            details = await ProductRepository.GetProductByIdAsync(ProductId);
        }
        catch (Exception ex)
        {
            loadFailed = true;
            Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
        }
    }

    public class ProductDetail
    {
        public string? ProductName { get; set; }
        public string? Description { get; set; }
    }

    public interface IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id);
    }
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository

@if (details != null)
{
    <h1>@details.ProductName</h1>
    <p>@details.Description</p>
}
else if (loadFailed)
{
    <h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
    <h1>Loading...</h1>
}

@code {
    private ProductDetail details;
    private bool loadFailed;

    [Parameter]
    public int ProductId { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        try
        {
            loadFailed = false;

            // Reset details to null to display the loading indicator
            details = null;

            details = await ProductRepository.GetProductByIdAsync(ProductId);
        }
        catch (Exception ex)
        {
            loadFailed = true;
            Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
        }
    }

    public class ProductDetail
    {
        public string ProductName { get; set; }
        public string Description { get; set; }
    }

    public interface IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id);
    }
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository

@if (details != null)
{
    <h1>@details.ProductName</h1>
    <p>@details.Description</p>
}
else if (loadFailed)
{
    <h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
    <h1>Loading...</h1>
}

@code {
    private ProductDetail details;
    private bool loadFailed;

    [Parameter]
    public int ProductId { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        try
        {
            loadFailed = false;

            // Reset details to null to display the loading indicator
            details = null;

            details = await ProductRepository.GetProductByIdAsync(ProductId);
        }
        catch (Exception ex)
        {
            loadFailed = true;
            Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
        }
    }

    public class ProductDetail
    {
        public string ProductName { get; set; }
        public string Description { get; set; }
    }

    public interface IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id);
    }
}

Lógica de renderização

A marcação declarativa em um arquivo de componente Razor (.razor) é compilada em um método C# chamado BuildRenderTree. Quando um componente é renderizado, BuildRenderTree executa e cria uma estrutura de dados que descreve os elementos, o texto e os componentes filhos do componente renderizado.

A lógica de renderização pode gerar uma exceção. Um exemplo desse cenário ocorre quando @someObject.PropertyName é avaliado, mas @someObject é null. Para aplicativos Blazor que operam em um circuito, uma exceção sem tratamento gerada pela lógica de renderização é fatal para o circuito do aplicativo.

Para impedir um NullReferenceException na lógica de renderização, verifique se há um objeto null antes de acessar seus membros. No exemplo a seguir, as propriedades person.Address não serão acessadas se person.Address for null:

@if (person.Address != null)
{
    <div>@person.Address.Line1</div>
    <div>@person.Address.Line2</div>
    <div>@person.Address.City</div>
    <div>@person.Address.Country</div>
}

O código anterior pressupõe que person não é null. Geralmente, a estrutura do código garante que um objeto exista no momento em que o componente é renderizado. Nesses casos, não é necessário verificar a presença de null na lógica de renderização. No exemplo anterior, é possível garantir a presença de person, pois person é criado quando o componente é instanciado, como mostra o exemplo a seguir:

@code {
    private Person person = new();

    ...
}

Manipuladores de eventos

O código do lado do cliente dispara invocações de código C# quando manipuladores de eventos são criados usando:

  • @onclick
  • @onchange
  • Outros atributos @on...
  • @bind

O código do manipulador de eventos pode gerar uma exceção sem tratamento nesses cenários.

Se o aplicativo chamar o código que pode falhar por motivos externos, intercepte exceções usando uma instrução try-catch com tratamento de erros e registro em log.

Se um manipulador de eventos gerar uma exceção sem tratamento (por exemplo, se uma consulta de banco de dados falhar) que não está interceptada e manipulada pelo código do desenvolvedor:

  • A estrutura registra a exceção.
  • Em um aplicativo Blazor que opera em um circuito, a exceção é fatal para o circuito do aplicativo.

Descarte de componentes

Um componente pode ser removido da interface do usuário, por exemplo, porque o usuário navegou até outra página. Quando um componente que implementa System.IDisposable é removido da interface do usuário, a estrutura chama o método Dispose do componente.

Se o método Dispose do componente gerar uma exceção sem tratamento em um aplicativo Blazoroperando em um circuito, a exceção será fatal para o circuito do aplicativo.

Se a lógica de descarte puder gerar exceções, o aplicativo deverá interceptar as exceções usando uma instrução try-catch com tratamento de erros e registro em log.

Para obter mais informações sobre o descarte de componentes, consulte Ciclo de vida do componente Razor do ASP.NET Core.

Interoperabilidade do JavaScript

IJSRuntime é registrado pela estrutura Blazor. IJSRuntime.InvokeAsync permite que o código .NET faça chamadas assíncronas para o runtime (JS) do JavaScript no navegador do usuário.

As seguintes condições se aplicam ao tratamento de erros com InvokeAsync:

  • Se uma chamada para InvokeAsync falhar de forma síncrona, ocorrerá uma exceção do .NET. Uma chamada para InvokeAsync pode falhar, por exemplo, porque os argumentos fornecidos não podem ser serializados. O código do desenvolvedor deverá capturar a exceção. Se o código do aplicativo em um manipulador de eventos ou método de ciclo de vida do componente não manipular uma exceção em um aplicativo Blazor operando em um circuito, a exceção resultante será fatal para o circuito do aplicativo.
  • Se uma chamada para InvokeAsync falhar de forma assíncrona, Task do .NET falhará. Uma chamada para InvokeAsync pode falhar, por exemplo, porque o código do lado do JS gera uma exceção ou retorna uma Promise que foi concluída como rejected. O código do desenvolvedor deverá capturar a exceção. Se estiver usando o operador await, considere encapsular a chamada de método em uma instrução try-catch com tratamento de erros e registro em log. Caso contrário, em um aplicativo Blazor operando em um circuito, o código com falha resultará em uma exceção sem tratamento fatal para o circuito do aplicativo.
  • As chamadas para InvokeAsync devem ser concluídas em um determinado período ou então a chamada atinge o tempo limite. O período de tempo limite padrão é de um minuto. O tempo limite protege o código contra a perda de conectividade de rede ou o código JS que nunca envia uma mensagem de conclusão. Se a chamada atingir o tempo limite, o resultado System.Threading.Tasks falhará com um OperationCanceledException. Intercepte e processe a exceção com registro em log.

Da mesma forma, o código JS pode iniciar chamadas para métodos .NET indicados pelo [JSInvokable] atributo . Se esses métodos .NET gerarem uma exceção sem tratamento:

  • Em um aplicativo Blazor que opera em um circuito, a exceção não é tratada como fatal para o circuito do aplicativo.
  • O Promise do lado do JS é rejeitado.

Há a opção de uso do código de tratamento de erros no lado do .NET ou no lado de JSda chamada de método.

Para obter mais informações, confira os seguintes artigos:

Pré-renderização

os componentes Razor são pré-renderizados por padrão para que sua marcação HTML renderizada seja retornada como parte da solicitação HTTP inicial do usuário.

Em um aplicativo Blazor que opera em um circuito, a pré-renderização funciona por:

  • Criação de um novo circuito para todos os componentes pré-gerados que fazem parte da mesma página.
  • Geração do HTML inicial.
  • Tratamento do circuito como disconnected até que o navegador do usuário estabeleça uma reconexão SignalR com o mesmo servidor. Quando a conexão é estabelecida, a interatividade no circuito é retomada e a marcação HTML dos componentes é atualizada.

Para componentes pré-renderizados do lado do cliente, a pré-renderização funciona por:

  • Gerando a HTML inicial no servidor para todos os componentes pré-renderizados que fazem parte da mesma página.
  • Tornando o componente interativo no cliente depois que o navegador carregar o código compilado do aplicativo e o runtime do .NET (se ainda não estiver carregado) em segundo plano.

Se um componente gerar uma exceção sem tratamento durante a pré-renderização, por exemplo, durante um método de ciclo de vida ou na lógica de renderização:

  • Em um aplicativo Blazor que opera em um circuito, a exceção é fatal para o circuito. Para componentes pré-renderizados do lado do cliente, a exceção impede a renderização do componente.
  • A exceção é gerada na pilha de chamadas do ComponentTagHelper.

Em circunstâncias normais, quando a pré-renderização falha, continuar a compilar e renderizar o componente não faz sentido porque um componente de trabalho não pode ser renderizado.

Para tolerar erros que podem ocorrer durante a pré-renderização, a lógica de tratamento de erros deverá ser colocada no interior de um componente que pode gerar exceções. Use instruções try-catch com tratamento de erros e registro em log. Em vez de encapsular ComponentTagHelper em uma instrução try-catch, coloque a lógica de tratamento de erro no componente renderizado pelo ComponentTagHelper.

Cenários avançados

Renderização recursiva

Os componentes podem ser aninhados recursivamente. Isso é útil para representar estruturas de dados recursivas. Por exemplo, um componente TreeNode pode renderizar mais componentes TreeNode para cada um dos elementos filhos do nó.

Ao renderizar recursivamente, evite padrões de codificação que resultem em recursão infinita:

  • Não renderize recursivamente uma estrutura de dados que contenha um ciclo. Por exemplo, não renderize um nó de árvore cujos filhos se incluam.
  • Não crie uma cadeia de layouts que contenha um ciclo. Por exemplo, não crie um layout cujo layout seja ele mesmo.
  • Não permita que um usuário final viole invariáveis de recursão (regras) por meio de entrada de dados ou chamadas de interoperabilidade do JavaScript maliciosas.

Loops infinitos durante a renderização:

  • Fazem com que o processo de renderização jamais terminem.
  • É equivalente à criação de um loop não finalizado.

Nesses cenários, Blazor falha e geralmente tenta:

  • Consumir o tempo de CPU permitido pelo sistema operacional, indefinidamente.
  • Consumir uma quantidade ilimitada de memória. Consumir memória ilimitada é equivalente ao cenário em que um loop não finalizado adiciona entradas a uma coleção em cada iteração.

Para evitar padrões infinitos de recursão, verifique se o código de renderização recursivo contém condições de interrupção adequadas.

Lógica de árvore de renderização personalizada

A maioria dos componentes Razor é implementada como arquivos de componente Razor (.razor) e é compilada pela estrutura para produzir lógica que opera em um RenderTreeBuilder para renderizar sua saída. No entanto, um desenvolvedor pode implementar manualmente a lógica RenderTreeBuilder usando código C# de procedimento. Para obter mais informações, consulte ASP.NET Core Blazor cenários avançados (construção de árvore de renderização).

Aviso

O uso da lógica do construtor de árvore de renderização manual é considerado um cenário avançado e não seguro, não sendo recomendado para o desenvolvimento geral de componentes.

Se o código RenderTreeBuilder for gravado, o desenvolvedor deverá garantir a correção do código. Por exemplo, o desenvolvedor deverá garantir que:

  • As chamadas para OpenElement e CloseElement sejam equilibradas corretamente.
  • Os atributos só são adicionados nos locais corretos.

A lógica incorreta do construtor de árvores de renderização manual poderá causar um comportamento indefinido arbitrário, como falhas, falta de resposta do servidor ou do aplicativo e vulnerabilidades de segurança.

Considere que a lógica do construtor de árvores de renderização manual tem o mesmo grau de complexidade e de perigo que a gravação do código de assembly ou das instruções manuais da MSIL (Linguagem Intermediária da Microsoft).

Recursos adicionais

†Aplica-se a aplicativos de back-end de API Web do ASP.NET Core que os aplicativos Blazor do lado do cliente usam para registro em log.