mirror of
https://github.com/ivanch/ivanch.me.git
synced 2025-06-28 07:19:26 +00:00
104 lines
3.2 KiB
Markdown
104 lines
3.2 KiB
Markdown
---
|
|
title: ".NET - Proper API error handling"
|
|
date: 2024-06-20T20:00:06-03:00
|
|
draft: false
|
|
summary: "Because returning stack traces isn't secure."
|
|
---
|
|
|
|
The main idea behind having centralized error handling is that we can process any unhandled exception to:
|
|
* Return formatted responses without revealing any internal functionality
|
|
* Process issues so that they are properly logged on logs or other monitoring systems (like Sentry)
|
|
* Make sure all errors have the same external behavior
|
|
|
|
For that, we will use a new middleware class:
|
|
```csharp
|
|
public class ErrorResponse
|
|
{
|
|
public string Message { get; set; }
|
|
}
|
|
|
|
|
|
public class ErrorHandlerMiddleware {
|
|
private readonly RequestDelegate _next;
|
|
private readonly ILogger<ErrorHandlerMiddleware> _logger;
|
|
private readonly IHostEnvironment _env;
|
|
|
|
public ErrorHandlerMiddleware(RequestDelegate next, ILogger<ErrorHandlerMiddleware> logger, IHostEnvironment env){
|
|
_next = next;
|
|
_logger = logger;
|
|
_env = env;
|
|
}
|
|
|
|
public async Task Invoke(HttpContext httpContext)
|
|
{
|
|
// Attempts to execute the next action on the http chain
|
|
// If it fails, we log the exception and trigger the HandleErrorAsync method
|
|
try
|
|
{
|
|
await _next(httpContext);
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
_logger.LogError(ex, "An unhandled exception has occurred: {Message}", ex.Message);
|
|
await HandleErrorAsync(httpContext, ex);
|
|
}
|
|
}
|
|
|
|
private async Task HandleErrorAsync(HttpContext context, Exception exception)
|
|
{
|
|
context.Response.ContentType = "application/json";
|
|
context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
|
|
|
|
ErrorResponse errorResponse;
|
|
|
|
if (_env.IsDevelopment())
|
|
{
|
|
// In development, we want to see the full details for easier debugging.
|
|
errorResponse = new ErrorResponse
|
|
{
|
|
Message = exception.ToString()
|
|
};
|
|
}
|
|
else
|
|
{
|
|
// In production, we return a generic message to avoid leaking details.
|
|
errorResponse = new ErrorResponse
|
|
{
|
|
Message = "An internal server error occurred. Please try again later."
|
|
};
|
|
}
|
|
|
|
// We use the modern System.Text.Json for serialization via WriteAsJsonAsync
|
|
await context.Response.WriteAsJsonAsync(errorResponse);
|
|
}
|
|
}
|
|
```
|
|
|
|
We will also define a new Extension class to register this middleware:
|
|
```csharp
|
|
public static class ErrorHandlerExtensions
|
|
{
|
|
public static IApplicationBuilder UseErrorHandler(this IApplicationBuilder appBuilder)
|
|
{
|
|
return appBuilder.UseMiddleware<ErrorHandler>();
|
|
}
|
|
}
|
|
```
|
|
|
|
And then we just configure it on the `Configure()` method at the startup:
|
|
```csharp
|
|
public void Configure(IApplicationBuilder app)
|
|
{
|
|
app.UseErrorHandler();
|
|
}
|
|
```
|
|
|
|
Now, when there's an issue on the API execution, the API will return something like this:
|
|
```json
|
|
{
|
|
"Message": "Internal Server Error"
|
|
}
|
|
```
|
|
|
|
Sources:
|
|
* [https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/write?view=aspnetcore-8.0](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/write?view=aspnetcore-8.0) |