Files
DailyMeals/MealPlan.Api/Middleware/ExceptionMiddleware.cs
2026-06-04 06:24:56 +02:00

63 lines
1.9 KiB
C#

using System.Text.Json;
namespace MealPlan.Api.Middleware;
/// <summary>
/// Converts unhandled exceptions into RFC-7807-style JSON problem responses and logs them.
/// Internal details are never leaked to the client.
/// </summary>
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionMiddleware> _logger;
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (OperationCanceledException) when (context.RequestAborted.IsCancellationRequested)
{
// Client disconnected; nothing actionable to report.
_logger.LogDebug("Request {Path} was cancelled by the client", context.Request.Path);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled exception for {Method} {Path}",
context.Request.Method, context.Request.Path);
if (context.Response.HasStarted)
{
throw;
}
context.Response.Clear();
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = "application/problem+json";
var payload = new
{
type = "https://httpstatuses.io/500",
title = "An unexpected error occurred.",
status = StatusCodes.Status500InternalServerError,
traceId = context.TraceIdentifier,
};
await context.Response.WriteAsync(JsonSerializer.Serialize(payload));
}
}
}
public static class ExceptionMiddlewareExtensions
{
public static IApplicationBuilder UseExceptionMiddleware(this IApplicationBuilder app) =>
app.UseMiddleware<ExceptionMiddleware>();
}