Pular para o conteúdo principal

🚨 ASP.NET Core — Exception Handling

Banner ASP.NET Core Exception Handling

1. Introdução

Quando construímos aplicações, é comum que ao testar ou implantar em produção algo não se comporte como esperado. Uma tentativa de dividir por zero, acessar uma propriedade de um objeto nulo, ou um schema de banco de dados desatualizado em produção — todas essas são condições excepcionais que resultam em uma exceção sendo lançada.

Por padrão, se não houver mecanismo de tratamento, a aplicação para de funcionar. Exception handling é o conjunto de técnicas que permitem capturar essas situações e manter a aplicação operacional, retornando respostas significativas ao usuário.

Em .NET / .NET Core: uma exceção é um objeto que herda da classe base System.Exception. Ela é lançada de uma área do código onde o problema ocorreu e percorre a pilha de chamadas até ser tratada — ou terminar o processo se não tratada.

Fluxo sem handler vs com handler


2. Hierarquia de Exceções no .NET

O .NET organiza as exceções em uma hierarquia clara. Entendê-la é essencial para saber qual tipo capturar e qual lançar.

Hierarquia de Exceções .NET

Dois grandes grupos

  • SystemException — erros fatais gerados pelo runtime (crash de SO, memória esgotada). Não podem ser evitados ou capturados de forma confiável pelo código da aplicação.
  • ApplicationException — erros recuperáveis que ocorrem durante a execução normal. São o alvo dos blocos try-catch.

Exceções built-in mais comuns

ExceçãoQuando ocorreExemplo prático
NullReferenceExceptionAcesso a propriedade de objeto nulostudent.FullName quando student == null
IndexOutOfRangeExceptionÍndice fora do limite do arrayarray[4] em array com 3 elementos
DivideByZeroExceptionDivisão por zero inteiroint result = x / 0
ArgumentExceptionArgumento inválido para o parâmetroID <= 0 passado a um endpoint
FormatExceptionFalha na conversão de tipoint.Parse("abc")
informação

Na prática moderna do .NET, herda-se diretamente de Exception — não de ApplicationException. A classe ApplicationException foi marcada como obsoleta conceitualmente desde o .NET 2.0.


3. Métodos de Tratamento de Exceções

Antes de começar a implementar, é importante entender quando usar cada abordagem:

MétodoPropósitoCaptura
try-catch-finallyTratar exceções locais e previsíveisExceções tratadas (handled)
Global HandlerCobrir toda a aplicaçãoExceções não tratadas (unhandled)
Exception FilterTratar por tipo em nível de controller/actionExceções de domínio específicas

4. Try-Catch-Finally

O bloco try-catch-finally é a forma mais direta de tratar exceções em qualquer aplicação .NET. Ele encapsula o código que pode lançar exceções e define o que fazer quando isso ocorre.

Responsabilidade de cada bloco

Implementação prática

[HttpGet("get-all-students")]
public IActionResult GetAllStudents()
{
try
{
var allStudents = _context.Students.ToList();
return Ok(allStudents);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
finally
{
// Executado sempre — sucesso ou falha
// Ex: fechar conexão, liberar recursos
}
}
Atalho no Visual Studio

Digite try e pressione Tab duas vezes — o Visual Studio gera automaticamente a estrutura try-catch completa.

finally não suprime exceções

O bloco finally não captura exceções — ele apenas garante execução. Se o catch relançar a exceção (throw), o finally ainda executa antes da exceção continuar propagando.


5. Tipos de Exceção Built-in — Exemplos Práticos

Veja como cada tipo de exceção built-in se manifesta em um controller real:

[HttpGet("get-student-by-id/{id}")]
public IActionResult GetStudentById(int id)
{
// ArgumentException: ID deve ser positivo
if (id <= 0)
throw new ArgumentException("Informe um ID maior que zero.");

var studentInfo = _context.Students.FirstOrDefault(s => s.Id == id);

// NullReferenceException: studentInfo pode ser null
var fullName = studentInfo.FullName; // lança se id não existe

// IndexOutOfRangeException: acesso por índice no array
var allStudents = _context.Students.ToArray();
var fifth = allStudents[4]; // lança se array tiver menos de 5 elementos

return Ok(new { studentName = fullName });
}
Regra prática

Use ArgumentException (ou ArgumentNullException) para validar entradas do usuário antes de processar. Para objetos nulos vindos do banco, prefira verificar explicitamente com if (studentInfo == null) antes de acessar propriedades.


6. Exceções Customizadas

Quando os tipos built-in não são suficientemente expressivos para o seu domínio, crie exceções próprias herdando de Exception.

Hierarquia no projeto de exemplo

Construtores de StudentNameException

ConstrutorQuando usar
StudentNameException()Throw simples sem contexto
StudentNameException(string message)Mensagem descritiva ao usuário
StudentNameException(string message, Exception inner)Wrapping de outra exceção
StudentNameException(string message, string studentName)Propriedade extra com o nome do estudante

Implementação

// Exceptions/StudentNameException.cs
public class StudentNameException : Exception
{
public string StudentName { get; set; }

public StudentNameException() { }

public StudentNameException(string message)
: base(message) { }

public StudentNameException(string message, Exception innerException)
: base(message, innerException) { }

public StudentNameException(string message, string studentName)
: base(message)
{
StudentName = studentName;
}
}
// Exceptions/StudentAgeException.cs
public class StudentAgeException : Exception
{
public StudentAgeException(string message) : base(message) { }
}

Usando no controller

[HttpPost("add-student")]
public IActionResult AddNewStudent([FromBody] Student payload)
{
try
{
// Valida se o nome começa com dígito
if (Regex.IsMatch(payload.FullName, @"^\d"))
throw new StudentNameException("Nome inicia com número.", payload.FullName);

// Valida idade mínima
if (StudentIs20OrYounger(payload.DateOfBirth))
throw new StudentAgeException(
$"{payload.FullName} precisa ter mais de 20 anos. Ano: {payload.DateOfBirth.Year}");

_context.Students.Add(payload);
_context.SaveChanges();
return CreatedAtAction(nameof(GetAllStudents), payload);
}
catch (StudentNameException ex)
{
return BadRequest($"{ex.StudentName} começa com um dígito.");
}
catch (StudentAgeException ex)
{
return BadRequest(ex.Message);
}
catch (Exception ex)
{
return BadRequest("Não foi possível adicionar o estudante.");
}
}

private bool StudentIs20OrYounger(DateTime dateOfBirth)
{
var today = DateTime.Today;
var age = today.Year - dateOfBirth.Year;
if (dateOfBirth.Date > today.AddYears(-age)) age--;
return age <= 20;
}
informação

Use System.Text.RegularExpressions.Regex.IsMatch(valor, @"^\d") para verificar se uma string começa com dígito. O ^ ancora o início da string e \d corresponde a qualquer dígito.


7. Múltiplos Catch Blocks

Quando um método pode lançar diferentes tipos de exceção, use múltiplos blocos catch em ordem do mais específico para o mais genérico.

Fluxo de matching

catch (StudentNameException ex)
{
return BadRequest($"{ex.StudentName} começa com dígito.");
}
catch (StudentAgeException ex)
{
return BadRequest(ex.Message);
}
catch (Exception ex)
{
return BadRequest("Erro inesperado ao processar o estudante.");
}
Ordem importa

Colocar catch (Exception ex) antes de exceções mais específicas causa erro de compilação: "a previous catch clause already catches all exceptions of this type or of a super type". Exceções mais específicas sempre acima.


8. Tratamento Global — Por Quê?

Blocos try-catch locais são eficazes para exceções previsíveis, mas não é possível envolver todo o código da aplicação com eles. Exceções podem ocorrer em pontos inesperados:

  • Falha de conexão ao banco de dados
  • Permissão negada ao escrever arquivo em disco
  • Exceção em biblioteca de terceiros
  • Qualquer trecho de código sem try-catch

Global Exception Handlers interceptam todas as exceções não tratadas (unhandled), constroem uma resposta padronizada e evitam que a aplicação retorne um erro 500 bruto ao usuário.

O ASP.NET Core oferece duas abordagens:

AbordagemComo funciona
Built-in (UseExceptionHandler)Middleware nativo do framework
Custom MiddlewareMiddleware manual com try-catch no InvokeAsync

9. ErrorResponseData — Resposta Padronizada

Antes de implementar qualquer handler global, crie uma classe que padronize todas as respostas de erro:

// Data/Responses/ErrorResponseData.cs
public class ErrorResponseData
{
public int StatusCode { get; set; }
public string Message { get; set; }
public string Path { get; set; }

public override string ToString()
=> JsonConvert.SerializeObject(this); // Newtonsoft.Json
}

Resposta gerada:

{
"statusCode": 500,
"message": "Could not connect to the database.",
"path": "/students/get-all-students"
}
Content-Type é obrigatório

Sem definir context.Response.ContentType = "application/json" antes de escrever a resposta, o body chega vazio ao cliente, mesmo que o handler funcione corretamente.


10. Handler Built-in (UseExceptionHandler)

O ASP.NET Core fornece o middleware UseExceptionHandler que intercepta exceções não tratadas e permite construir uma resposta customizada.

Fluxo no pipeline

Implementação

// Exceptions/ExceptionMiddlewareExtensions.cs
public static class ExceptionMiddlewareExtensions
{
public static void ConfigureBuiltInExceptionHandler(this IApplicationBuilder app)
{
app.UseExceptionHandler(appError =>
{
appError.Run(async context =>
{
context.Response.ContentType = "application/json";

var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
var contextRequest = context.Features.Get<IHttpRequestFeature>();

if (contextFeature != null)
{
var errorResponse = new ErrorResponseData
{
StatusCode = (int)HttpStatusCode.InternalServerError,
Message = contextFeature.Error.Message,
Path = contextRequest?.Path
};

await context.Response.WriteAsync(errorResponse.ToString());
}
});
});
}
}
// Startup.cs — método Configure
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.ConfigureBuiltInExceptionHandler(); // registrar antes dos outros middlewares
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => endpoints.MapControllers());
}

Namespaces necessários

Classe / InterfaceNamespace
IExceptionHandlerFeatureMicrosoft.AspNetCore.Diagnostics
IHttpRequestFeatureMicrosoft.AspNetCore.Http.Features
HttpStatusCodeSystem.Net
ErrorResponseDataSchool.API.Data.Responses

11. Custom Middleware Handler

Para maior controle sobre o tratamento, implemente um middleware manual. A vantagem é poder adicionar lógica customizada — como logging, transformação de mensagem ou diferenciação por tipo de exceção.

Built-in vs Custom — comparação de fluxo

Implementação

// Exceptions/CustomExceptionHandler.cs
public class CustomExceptionHandler
{
private readonly RequestDelegate _next;

public CustomExceptionHandler(RequestDelegate next)
{
_next = next;
}

public async Task InvokeAsync(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (Exception ex)
{
await HandleExceptionAsync(httpContext, ex);
}
}

private Task HandleExceptionAsync(HttpContext httpContext, Exception ex)
{
httpContext.Response.ContentType = "application/json";

var errorResponse = new ErrorResponseData
{
StatusCode = (int)HttpStatusCode.InternalServerError,
Message = ex.Message,
Path = httpContext.Request.Path.ToString()
};

return httpContext.Response.WriteAsync(errorResponse.ToString());
}
}
// Exceptions/ExceptionMiddlewareExtensions.cs — adicionar método
public static void ConfigureCustomExceptionHandler(this IApplicationBuilder app)
{
app.UseMiddleware<CustomExceptionHandler>();
}
// Startup.cs
app.ConfigureCustomExceptionHandler(); // substitui o built-in

12. Exception Filters

Exception Filters são executados antes que o response body seja populado quando uma exceção ocorre dentro do pipeline de actions do MVC. Diferente dos global handlers (que são middleware), os filters operam no nível do MVC.

Posição no pipeline de filters

Implementação

// Exceptions/Filters/CustomExceptionFilter.cs
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
context.HttpContext.Response.ContentType = "application/json";

var statusCode = HttpStatusCode.InternalServerError;

// Diferentes status codes por tipo de exceção
if (context.Exception is StudentNameException)
statusCode = HttpStatusCode.NotFound;

var errorResponse = new ErrorResponseData
{
StatusCode = (int)statusCode,
Message = context.Exception.Message,
Path = context.Exception.StackTrace?.ToString()
};

// Usar JsonResult — NÃO chamar .ToString() aqui
context.Result = new JsonResult(errorResponse)
{
StatusCode = (int)statusCode
};
}
}
JsonResult vs ToString

Ao atribuir context.Result, use new JsonResult(errorResponse) — não errorResponse.ToString(). O JsonResult serializa automaticamente o objeto; .ToString() retornaria uma string já serializada, causando double-encoding.

Escopo dos Exception Filters

Exception Filters não interceptam exceções lançadas fora do pipeline de actions MVC (ex: em middlewares, no Startup). Para cobrir esses casos, use os Global Handlers das seções 10 e 11.

Aplicando no controller

// Aplicar em toda a controller
[ApiController]
[Route("students")]
[CustomExceptionFilter]
public class StudentsController : ControllerBase { ... }

// Ou apenas em um action específico
[HttpGet("get-all")]
[CustomExceptionFilter]
public IActionResult GetAllStudents()
{
throw new StudentNameException("Teste do filter");
// ...
}

13. Qual Abordagem Usar?

Comparação de Abordagens

Árvore de decisão

Comparação completa

AbordagemEscopoControleCaptura unhandledQuando usar
try-catch localMétodo específicoTotalNãoExceção previsível e localizada
UseExceptionHandlerToda a aplicaçãoMédioSimSetup rápido, sem lógica extra
CustomExceptionHandlerToda a aplicaçãoTotalSimLogging, transformações, lógica complexa
ExceptionFilterAttributeController ou ActionAltoNãoDiferentes responses por tipo de exceção

Referência rápida de namespaces

Classe / InterfaceNamespace
IExceptionHandlerFeatureMicrosoft.AspNetCore.Diagnostics
IHttpRequestFeatureMicrosoft.AspNetCore.Http.Features
RequestDelegateMicrosoft.AspNetCore.Http
ExceptionFilterAttributeMicrosoft.AspNetCore.Mvc.Filters
JsonResultMicrosoft.AspNetCore.Mvc
JsonConvertNewtonsoft.Json
HttpStatusCodeSystem.Net

Resumo

O exception handling em ASP.NET Core é uma camada de resiliência essencial. A abordagem ideal combina as três técnicas:

  1. try-catch local — para pontos previsíveis onde você quer controle total da resposta
  2. Global Handler (built-in ou custom middleware) — para cobrir todos os casos não previstos e garantir que nenhuma exceção não tratada chegue ao cliente como erro bruto
  3. Exception Filter — para respostas diferenciadas por tipo de exceção em controllers de domínio específico

Combine com logging estruturado (ex: Serilog + SEQ) para rastrear todas as exceções em produção.

Publicidade