Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
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 descreve como Blazor gerencia exceções não tratadas e como desenvolver aplicativos que detetam e manipulam 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, Blazor aplicativos exibem uma barra amarela clara na parte inferior da tela:
- Durante o desenvolvimento, a barra direciona você para o console do navegador, onde você pode ver a exceção.
- Na produção, a barra notifica o usuário de que ocorreu um erro e recomenda atualizar o navegador.
A interface do usuário para essa experiência de tratamento de erros faz parte do 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 de erro, mas todas as versões da documentação do Blazor aplicam o atributo.
No 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 é suportado em componentes Razor, o exemplo a seguir injeta IHostEnvironment para configurar mensagens de erro para diferentes ambientes.
No topo da MainLayout.razor:
@inject IHostEnvironment HostEnvironment
Crie ou modifique a marcação da interface do usuário de 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 Assistente de Marca de Ambiente para configurar mensagens de erro para diferentes ambientes.
Em um aplicativo Blazor Server, personalize a experiência no arquivo Pages/_Layout.cshtml. O exemplo a seguir usa o Assistente de Marca de Ambiente para configurar mensagens de erro para diferentes ambientes.
Em um aplicativo Blazor Server, personalize a experiência no arquivo Pages/_Host.cshtml. O exemplo a seguir usa o Assistente de Marca de Ambiente para configurar mensagens de erro para diferentes ambientes.
Crie ou modifique a marcação da interface do usuário de 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 fica oculto devido à presença do estilo display: none da classe CSS blazor-error-ui na folha de estilo 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 CSS blazor-error-ui na folha de estilo do site na pasta wwwroot/css. Quando ocorre um erro, a estrutura aplica display: block ao elemento.
Erros de circuito detalhados
Esta secção aplica-se aos Blazor Web Appque operam num circuito.
Esta secção aplica-se a aplicações Blazor Server.
Os erros do lado do cliente não incluem a pilha de chamadas e não fornecem detalhes sobre a causa do erro, mas os logs do servidor contêm essas informações. Para fins de desenvolvimento, informações sensíveis de erro de circuito podem ser disponibilizadas ao cliente, permitindo erros detalhados.
Defina CircuitOptions.DetailedErrors para true. Para obter mais informações e um exemplo, consulte orientação ASP.NET Core BlazorSignalR.
Uma alternativa para definir CircuitOptions.DetailedErrors é definir a chave de configuração DetailedErrors para true no arquivo de configurações do ambiente Development do aplicativo (appsettings.Development.json). Além disso, defina SignalR de log do lado do servidor (Microsoft.AspNetCore.SignalR) como Debug ou Trace para registro de SignalR detalhado.
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 nos servidores de ambiente Development/Staging ou no seu sistema local.
Advertência
Evite sempre expor informações de erro a clientes na Internet, o que é um risco de segurança.
Erros detalhados na renderização do servidor para o componente Razor
Esta secção aplica-se a Blazor Web Apps.
Utilize a opção RazorComponentsServiceOptions.DetailedErrors para controlar a produção de informações detalhadas sobre erros para o Razor componente de renderização do lado do servidor. O valor padrão é false.
O exemplo a seguir permite erros detalhados:
builder.Services.AddRazorComponents(options =>
options.DetailedErrors = builder.Environment.IsDevelopment());
Advertência
Habilite apenas 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.
Gerenciar exceções não tratadas no código do desenvolvedor
Para que um aplicativo continue após um erro, o aplicativo deve ter lógica de tratamento de erros. Seções posteriores deste artigo descrevem possíveis fontes de exceções não tratadas.
Na produção, não renderize mensagens de exceção do framework ou rastreamentos de pilha na interface de utilizador. A renderização de mensagens de exceção ou rastreamentos de pilha pode:
- Divulgue informações confidenciais aos usuários finais.
- Ajude um usuário mal-intencionado a descobrir pontos fracos em um aplicativo que podem comprometer a segurança do aplicativo, servidor ou rede.
Exceções não tratadas para circuitos
Esta secção aplica-se a aplicações do lado do servidor que operam através de um circuito.
Razor componentes com interatividade de servidor habilitada são submetidos a monitoração de estado no servidor. Enquanto os usuários interagem com o componente no servidor, eles mantêm uma conexão com o servidor conhecida como circuito . O circuito contém instâncias de componentes ativos, além de muitos outros aspetos do estado, como:
- A saída renderizada mais recente de componentes.
- O conjunto atual de delegados de manipulação de eventos que podem ser acionados 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 não tratadas como fatais para o circuito onde ocorrem. Se um circuito for encerrado devido a uma exceção não tratada, o usuário só poderá continuar a interagir com o aplicativo recarregando a página para criar um novo circuito. Circuitos fora daquele que é encerrado, que são circuitos para outros usuários ou outras guias do navegador, não são afetados. Este cenário é semelhante a uma aplicação de ambiente de trabalho que falha. O aplicativo com falha deve ser reiniciado, mas outros aplicativos não são afetados.
A estrutura encerra um circuito quando ocorre uma exceção não tratada pelos seguintes motivos:
- Uma exceção não tratada geralmente deixa o circuito em um estado indefinido.
- A operação normal do aplicativo não pode ser garantida após uma exceção não tratada.
- Vulnerabilidades de segurança podem aparecer no aplicativo se o circuito continuar em um estado indefinido.
Tratamento de exceções globais
Para abordagens para lidar com exceções globalmente, consulte as seguintes seções:
- Limites de erro: Aplica-se a todos os aplicativos Blazor.
- Alternativa de tratamento de exceções globais: Aplica-se a Blazor Server, Blazor WebAssemblye Blazor Web Apps (8.0 ou posterior) que adotam um modo de renderização interativo global.
Limites de erro
Os limites de erro fornecem uma abordagem conveniente para lidar com exceções. O componente ErrorBoundary:
- Renderiza o seu conteúdo filho quando não ocorre um erro.
- Renderiza a interface de erro quando uma exceção não tratada é lançada por qualquer componente dentro do perímetro 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 não tratadas geradas pelos componentes que ele encapsula.
<ErrorBoundary>
...
</ErrorBoundary>
Para implementar um limite de erro de forma global, adicione o limite ao redor 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 renderização estática do lado do servidor (SSR estático). O limite não é ativado apenas porque um componente mais abaixo na 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 de RenderFragment, que é um código arbitrário e não pode ser serializado. Para habilitar a interatividade amplamente para o componente MainLayout e o restante dos componentes mais abaixo na hierarquia de componentes, o aplicativo deve adotar um modo de renderização interativa global aplicando o modo de renderização interativa à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 Interactive Server (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 ter em mente são que, onde quer que o limite de erro seja colocado:
- Se o componente onde 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, a fronteira pode ser ativada quando um erro é disparado em um método de ciclo de vida do componente, mas não para um evento acionado pela interatividade do utilizador dentro do componente, como um erro disparado por um manipulador de clique de botão.
- Se o componente onde o limite de erro é colocado for interativo, o limite de erro é capaz de se ativar para os componentes interativos que ele encapsula.
Observação
As considerações anteriores não são relevantes para aplicativos Blazor WebAssembly autônomos porque a renderização do lado do cliente (CSR) de um aplicativo Blazor WebAssembly é completamente interativa.
Considere o exemplo a seguir, onde uma exceção lançada por um componente de contador incorporado é 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, onde uma exceção lançada por um componente de contador incorporado é 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 não tratada é gerada para uma currentCount maior que cinco:
- O erro é registado normalmente (
System.InvalidOperationException: Current count is too big!). - A exceção é tratada pelo limite de erro.
- A interface de utilizador de erro padrão é gerada pelo limite de erro.
O componente ErrorBoundary renderiza um elemento <div> vazio 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 estilo do aplicativo na pasta wwwroot, portanto, você pode personalizar a interface do usuário de erro.
Para alterar o conteúdo do erro padrão:
- Envolva na propriedade ChildContent os componentes do limite de erro.
- Defina a propriedade ErrorContent para 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 estilo do aplicativo presumivelmente inclui uma classe CSS errorUI para definir o estilo do conteúdo. O conteúdo do erro é renderizado a partir da propriedade ErrorContent sem um elemento de nível de bloco. Um elemento de nível de bloco, como um elemento de 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 pode nomear o contexto. No exemplo a seguir, o contexto é chamado exception:
<ErrorContent Context="exception">
@exception.HelpLink
</ErrorContent>
Advertência
Evite sempre expor informações de erro a clientes na Internet, o que é um risco de segurança.
Se o limite de erro for definido no layout da aplicação, a interface de erro será exibida independentemente da página para onde o usuário navega após a ocorrência do erro. Recomendamos definir limites de erro de forma restrita na maioria dos cenários. Se você definir o escopo geral de um limite de erro, poderá redefini-lo para um estado sem erro em eventos de navegação de página subsequentes chamando o método Recover do limite de erro.
Em MainLayout.razor:
- Adicione um campo do ErrorBoundary ao para capturar uma referência a ele com a diretiva de atributo
@ref. - No método de ciclo de vida
OnParameterSet, pode-se acionar uma recuperação no limite de erro com Recover para eliminar o erro quando o utilizador navega para um componente diferente.
...
<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 rerenderiza um componente que lança o erro novamente, não chame Recover da lógica de renderização. Só ligue Recover 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 que é executada também limpa a exceção. Quando o componente é rerenderizado, 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 subclassificar 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 desejado. Você pode remover a linha que retorna um CompletedTask se seu código aguarda 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 alternativo de exceções globais
A abordagem descrita nesta seção aplica-se a Blazor Server, Blazor WebAssemblye Blazor Web Appque adotam um modo de renderização interativa global (InteractiveServer, InteractiveWebAssemblyou InteractiveAuto). A abordagem não funciona com Blazor Web Appque adotam modos de renderização por página/componente ou renderização estática do lado do servidor (SSR estático), uma vez que se baseia num CascadingValue/CascadingParameter, que não funciona além das fronteiras dos modos de renderização ou com componentes que adotam SSR estático.
Uma alternativa ao uso limites de erro (ErrorBoundary) é passar um componente de erro personalizado como um CascadingValue para componentes filho. Uma vantagem de usar um componente em relação ao uso de um serviço injetado ou uma implementação de logger personalizada é que um componente de cascata pode renderizar conteúdo e aplicar estilos CSS quando ocorre um erro.
O exemplo de componente ProcessError a seguir apenas registra erros, mas os métodos do componente podem processar erros de qualquer maneira exigida 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" IsFixed="true">
@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, consulte os componentes do ASP.NET Core Razor.
CascadingValue<TValue>.IsFixed é usado para indicar que um parâmetro em cascata não é alterado após a inicialização.
Ao usar essa abordagem em um Blazor Web App, abra o componente Routes e envolva o componente Router (<Router>...</Router>) com o componente ProcessError. Isso permite que o componente ProcessError caia em cascata para qualquer componente do aplicativo onde 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, envolva o componente Router (<Router>...</Router>) com o componente ProcessError. Isso permite que o componente ProcessError caia em cascata para qualquer componente do aplicativo onde o componente ProcessError é recebido como um CascadingParameter.
Em App.razor:
<ProcessError>
<Router ...>
...
</Router>
</ProcessError>
Para processar erros em um componente:
Designe o componente
ProcessErrorcomo umCascadingParameterno bloco@code. Num componente de exemploCounternum aplicativo baseado num modelo de projeto Blazor, adicione a seguinte propriedadeProcessError:[CascadingParameter] private ProcessError? ProcessError { get; set; }Chame um método de processamento de erros em qualquer bloco de
catchcom um tipo de exceção apropriado. O componente de exemploProcessErroroferece apenas um único métodoLogError, mas o componente de processamento de erros pode fornecer qualquer número de métodos de processamento de erros para abordar requisitos alternativos de processamento de erros em todo o aplicativo. O exemplo de blocoCountercomponente@codeseguinte inclui o parâmetroProcessErrorem cascata e interceta uma exceção para registo em log quando a contagem é maior que cinco:@code { private int currentCount = 0; [CascadingParameter] private 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 registado:
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 mostrar uma barra de mensagens de erro personalizada ou alterar os estilos CSS dos elementos renderizados, chame StateHasChanged no final do método LogError para 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 de um aplicativo entre o cliente e o servidor não é interrompida quando ocorre um erro e o circuito permanece ativo. Outras exceções não tratadas permanecem fatais para um circuito. Para obter mais informações, consulte a seção sobre como um circuito reage a exceções não tratadas.
Um aplicativo pode usar um componente de processamento de erros como um valor em cascata para processar erros de forma centralizada.
O componente ProcessError a seguir passa a si mesmo como um CascadingValue para componentes filho. O exemplo a seguir apenas registra o erro, mas os métodos do componente podem processar erros de qualquer maneira exigida pelo aplicativo, inclusive por meio do uso de vários métodos de processamento de erros. Uma vantagem de usar um componente em relação ao uso de um serviço injetado ou uma implementação de logger personalizada é que um componente de 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" IsFixed="true">
@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, consulte os componentes do ASP.NET Core Razor.
CascadingValue<TValue>.IsFixed é usado para indicar que um parâmetro em cascata não é alterado após a inicialização.
No componente App, envolva o componente Router com o componente ProcessError. Isso permite que o componente ProcessError caia em cascata para qualquer componente do aplicativo onde o componente ProcessError é recebido como um CascadingParameter.
App.razor:
<ProcessError>
<Router ...>
...
</Router>
</ProcessError>
Para processar erros em um componente:
Designe o componente
ProcessErrorcomo umCascadingParameterno bloco@code:[CascadingParameter] private ProcessError ProcessError { get; set; }Chame um método de processamento de erros em qualquer bloco de
catchcom um tipo de exceção apropriado. O componente de exemploProcessErroroferece apenas um único métodoLogError, mas o componente de processamento de erros pode fornecer qualquer número de métodos de processamento de erros para abordar requisitos alternativos de processamento de erros em todo o aplicativo.try { ... } catch (Exception ex) { ProcessError.LogError(ex); }
Usando o exemplo anterior ProcessError componente e LogError método, o console de ferramentas de desenvolvedor do navegador indica o erro intercetado 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 mostrar uma barra de mensagens de erro personalizada ou alterar os estilos CSS dos elementos renderizados, chame StateHasChanged no final do método LogError para renderizar novamente a interface do usuário.
Como as abordagens nesta seção manipulam erros com uma instrução try-catch, a conexão Blazor de um aplicativo SignalR entre o cliente e o servidor não é interrompida quando ocorre um erro e o circuito permanece ativo. Qualquer exceção não tratada é fatal para um circuito. Para obter mais informações, consulte a seção sobre como um circuito reage a exceções não tratadas.
Registar erros com um fornecedor persistente
Se ocorrer uma exceção sem tratamento, a exceção será registrada em instâncias ILogger configuradas no contêiner de serviço. Blazor registo de saída do console de aplicações com o Provedor de Registo de Console. Considere registar num local no servidor (ou API web de back-end para aplicações do lado do cliente) com um fornecedor que gere o tamanho e a rotação do log. Como alternativa, o aplicativo pode usar um serviço de Gerenciamento de Desempenho de Aplicativo (APM), como Azure Application Insights (Azure Monitor).
Observação
O Native Application Insights recursos para oferecer suporte a aplicativos do lado do cliente e suporte nativo à estrutura de Blazor para do Google Analytics poderão estar disponíveis em versões futuras dessas tecnologias. Para obter mais informações, consulte Support App Insights in Blazor WASM Client Side (microsoft/ApplicationInsights-dotnet #2143) e Web analytics and diagnostics (inclui links para implementações da comunidade) (dotnet/aspnetcore #5461). Enquanto isso, um aplicativo do lado do cliente pode usar o SDK JavaScript do Application Insights com JS de interoperabilidade para registrar erros diretamente no Application Insights a partir de um aplicativo do lado do cliente.
Durante o desenvolvimento numa aplicação Blazor operando em um circuito, a aplicação geralmente envia todos os detalhes completos das exceções para o console do navegador para ajudar na depuração. Na 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 ser capazes de acionar erros deliberadamente. Por exemplo, não registe um incidente relacionado a um erro em que um ProductId desconhecido é introduzido na URL de um componente que exibe informações sobre o produto. Nem todos os erros devem ser tratados como incidentes de registo.
Para obter mais informações, consulte os seguintes artigos:
- Blazor registo do
- Manipular erros no ASP.NET Core‡
- Crie APIs da Web com ASP.NET Core
‡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 da Web para Blazor. As aplicações do lado do cliente podem intercetar e enviar informações de erro no cliente para uma API web, que regista as informações de erro num fornecedor de registo persistente.
Se ocorrer uma exceção sem tratamento, a exceção será registrada em instâncias ILogger configuradas no contêiner de serviço. Blazor registo de saída do console de aplicações com o Provedor de Registo de Console. Considere registrar em um local mais permanente no servidor enviando informações de erro para uma API da Web de back-end que usa um provedor de log com gerenciamento de tamanho de log e rotação de log. 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 que recebe de clientes.
Você deve decidir quais incidentes registrar e o nível de gravidade dos incidentes registrados. Usuários hostis podem ser capazes de acionar erros deliberadamente. Por exemplo, não registe um incidente relacionado a um erro em que um ProductId desconhecido é introduzido na URL de um componente que exibe informações sobre o produto. Nem todos os erros devem ser tratados como incidentes de registo.
Para obter mais informações, consulte os seguintes artigos:
- Blazor registo do
- Manipular erros no ASP.NET Core‡
- Crie APIs da Web com ASP.NET Core
Recursos nativos do Application Insights para suportar aplicações cliente e suporte nativo à estrutura de Blazor para o Google Analytics possam ser disponibilizados em versões futuras dessas tecnologias. Para obter mais informações, consulte Support App Insights in Blazor WASM Client Side (microsoft/ApplicationInsights-dotnet #2143) e Web analytics and diagnostics (inclui links para implementações da comunidade) (dotnet/aspnetcore #5461). Enquanto isso, um aplicativo do lado do cliente pode usar o SDK JavaScript do Application Insights com JS de interoperabilidade para registrar erros diretamente no Application Insights a partir de um aplicativo do lado do cliente.
‡Aplica-se a aplicações ASP.NET Core do lado do servidor que são aplicações de back-end de API Web para aplicações Blazor. As aplicações cliente intercetam e enviam informações de erro para uma API da Web, que regista as informações de erro num fornecedor de registo persistente.
Locais onde podem ocorrer erros
A estrutura e o código do aplicativo podem disparar exceções sem tratamento em qualquer um dos seguintes locais, que são descritos mais detalhadamente nas seguintes seções deste artigo:
- Instanciação de componentes
- Métodos de ciclo de vida
- Lógica de renderização
- Manipuladores de eventos
- Eliminação de componentes
- Interoperabilidade de JavaScript
- de pré-renderização
- Instanciação de componentes
- Métodos de ciclo de vida
- Lógica de renderização
- Manipuladores de eventos
- Eliminação de componentes
- Interoperabilidade de JavaScript
- de pré-renderização
Instanciação de componentes
Quando Blazor cria uma instância de um componente:
- O construtor do componente é invocado.
- Os construtores de serviços DI fornecidos ao construtor do componente através da diretiva
@injectou do atributo[Inject]são invocados.
Um erro em um construtor executado ou um setter para qualquer propriedade [Inject] resulta em uma exceção não tratada 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 pode lançar exceções, o aplicativo deve intercetar as exceções usando uma instrução try-catch com manipulação de erros e registro.
Métodos de ciclo de vida
Durante a vida útil de um componente, Blazor invoca métodos de ciclo de vida. Se qualquer método de ciclo de vida lançar 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 lógica de tratamento de erros.
No exemplo a seguir, onde OnParametersSetAsync chama um método para obter um produto:
- Uma exceção lançada no método
ProductRepository.GetProductByIdAsyncé manipulada por uma instruçãotry-catch. - Quando o bloco
catché executado:-
loadFailedestá definido comotrue, que é 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 descrevendo os elementos, o texto e os componentes filho 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 não tratada lançada pela lógica de renderização é fatal para o circuito do aplicativo.
Para evitar um NullReferenceException na lógica de renderização, verifique se há um objeto null antes de acessar seus membros. No exemplo a seguir, person.Address propriedades não serão acessadas se person.Address estiver 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. Muitas vezes, a estrutura do código garante que um objeto existe no momento em que o componente é renderizado. Nesses casos, não é necessário verificar se há null na lógica de renderização. No exemplo anterior, person pode ter a garantia de existir porque 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 aciona 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 não tratada nesses cenários.
Se a aplicação chamar código que pode falhar por motivos externos, intercete exceções usando uma instrução try-catch com gestão de erros e registo.
Se um manipulador de eventos lançar uma exceção não tratada (por exemplo, uma consulta de base de dados falhar) que não seja capturada e gerida pelo código do desenvolvedor:
- A estrutura registra a exceção.
- Numa aplicação Blazor a operar sobre um circuito, a exceção é fatal para o circuito da aplicação.
Eliminação de componentes
Um componente pode ser removido da interface do usuário, por exemplo, porque o usuário navegou para 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 lançar uma exceção não tratada numa aplicação Blazor operando num circuito, a exceção será fatal para o circuito da aplicação.
Se a lógica de descarte puder gerar exceções, o aplicativo deverá capturar as exceções usando uma instrução try-catch com tratamento e registo de erros.
Para obter mais informações sobre o descarte de componentes, consulte Razorde descarte de componentes do Core .
Interoperabilidade JavaScript
IJSRuntime está registado pelo framework Blazor. IJSRuntime.InvokeAsync permite que o código .NET faça chamadas assíncronas para o tempo de execução do JavaScript (JS) no navegador do usuário.
As seguintes condições aplicam-se ao tratamento de erros com InvokeAsync:
- Se uma chamada para InvokeAsync falhar de forma síncrona, ocorrerá uma exceção .NET. Uma chamada para InvokeAsync pode falhar, por exemplo, porque os argumentos fornecidos não podem ser serializados. O código do desenvolvedor deve 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, o .NET Task falhará. Uma chamada para InvokeAsync pode falhar, por exemplo, porque o código do lado JSgera uma exceção ou retorna um
Promiseconcluído comorejected. O código do desenvolvedor deve capturar a exceção. Se estiver usando o operadorawait, considere envolver a chamada de método em uma instruçãotry-catchcom tratamento de erros e registro. Caso contrário, em um aplicativo Blazor operando em um circuito, o código com falha resulta em uma exceção não tratada que é fatal para o circuito do aplicativo. - As chamadas para InvokeAsync devem ser concluídas dentro de um determinado período ou então a chamada expira. 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 código JS que nunca devolve uma mensagem de conclusão. Se a chamada expirar, o System.Threading.Tasks resultante falhará com um OperationCanceledException. Capture e processe a exceção com registo.
Da mesma forma, JS código pode iniciar chamadas para métodos .NET indicados pelo atributo [JSInvokable]. Se esses métodos .NET lançarem uma exceção não tratada:
- Numa aplicação Blazor a operar sobre um circuito, a exceção não é tratada como fatal para o circuito da aplicação.
- A JS do lado
Promiseé rejeitada.
Você tem a opção de usar o código de tratamento de erros no lado .NET ou no lado JS da chamada de método.
Para obter mais informações, consulte os seguintes artigos:
- Chamar funções JavaScript a partir de métodos .NET no ASP.NET Core Blazor
- Chamar métodos .NET a partir de funções JavaScript no ASP.NET Core Blazor
Pré-renderização
Razor componentes 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 operando em um circuito, a pré-renderização funciona por:
- Criação de um novo circuito para todos os componentes pré-renderizados que fazem parte da mesma página.
- Gerando o HTML inicial.
- Tratar o circuito como
disconnectedaté que o navegador do usuário estabeleça uma conexão SignalR de volta ao mesmo servidor. Quando a conexão é estabelecida, a interatividade no circuito é retomada e a marcação HTML dos componentes é atualizada.
Para componentes pré-renderizados no cliente, a pré-renderização funciona por:
- Gerando HTML inicial no servidor para todos os componentes pré-renderizados que fazem parte da mesma página.
- Tornar o componente interativo no cliente depois que o navegador tiver carregado o código compilado do aplicativo e o tempo de execução do .NET (se ainda não estiver carregado) em segundo plano.
Se um componente lançar uma exceção não tratada 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 operando 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 é propagada na pilha de chamadas do ComponentTagHelper.
Em circunstâncias normais, quando a pré-renderização falha, continuar a construir e renderizar o componente não faz sentido porque um componente de trabalho não pode ser renderizado.
Para tolerar erros que possam ocorrer durante a pré-renderização, a lógica de tratamento de erros deve ser colocada dentro de um componente que pode gerar exceções. Use instruções try-catch com manipulação de erros e registro. Em vez de envolver o ComponentTagHelper em uma instrução try-catch, coloque a lógica de tratamento de erros 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 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 contenham um ciclo. Por exemplo, não crie um layout cujo layout seja ele mesmo.
- Não permita que um usuário final viole invariantes de recursão (regras) por meio de entrada de dados mal-intencionada ou chamadas de interoperabilidade JavaScript.
Loops infinitos durante a renderização:
- Faz com que o processo de renderização continue para sempre.
- É equivalente a criar um loop não terminado.
Nesses cenários, o Blazor falha e geralmente tenta:
- Consuma o tempo de CPU permitido pelo sistema operacional, indefinidamente.
- Consuma uma quantidade ilimitada de memória. O consumo de memória ilimitada é equivalente ao cenário em que um loop não terminado adiciona entradas a uma coleção em cada iteração.
Para evitar padrões de recursão infinitos, certifique-se de que o código de renderização recursiva contenha condições de parada adequadas.
Lógica de árvore de renderização personalizada
A maioria dos componentes Razor são implementados como arquivos de componentes Razor (.razor) e são compilados pelo framework para produzir lógica que opera em um RenderTreeBuilder para produzir o seu resultado. No entanto, um desenvolvedor pode implementar manualmente a lógica RenderTreeBuilder usando código C# procedural. Para obter mais informações, consulte ASP.NET Core Blazor cenários avançados (construção de árvore de renderização).
Advertência
O uso da lógica manual do construtor de árvores de renderização é considerado um cenário avançado e inseguro, não recomendado para o desenvolvimento geral de componentes.
Se o código RenderTreeBuilder for escrito, o desenvolvedor deve garantir a correção do código. Por exemplo, o desenvolvedor deve garantir que:
- As chamadas para OpenElement e CloseElement estão corretamente balanceadas.
- Os atributos só são adicionados nos locais corretos.
A lógica incorreta do construtor de árvores de renderização manual pode causar comportamentos arbitrários e indefinidos, incluindo falhas, além de o aplicativo ou servidor poder parar de responder, e vulnerabilidades de segurança.
Considere a lógica manual do construtor de árvores de renderização no mesmo nível de complexidade e com o mesmo grau de perigo que escrever em código de assembly ou instruções de Microsoft Intermediate Language (MSIL) à mão.
Recursos adicionais
†Aplica-se a aplicativos de back-end ASP.NET Core API Web que os aplicativos de Blazor do lado do cliente usam para registo.