using System.Text.Json;
namespace MealPlan.Api.Middleware;
///
/// Converts unhandled exceptions into RFC-7807-style JSON problem responses and logs them.
/// Internal details are never leaked to the client.
///
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public ExceptionMiddleware(RequestDelegate next, ILogger 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();
}