using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
namespace ROLAC.API.Middleware;
///
/// Catches any unhandled exception from the downstream pipeline, logs it (which flows through
/// the DB sink into SystemLogs at Error level with full stack + StatusCode 500), and returns a
/// clean RFC7807 problem+json response. Stack traces are never leaked to the client outside
/// Development. Registered as the FIRST middleware so it also catches auth/authorization faults.
///
public sealed class ExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly IHostEnvironment _env;
public ExceptionHandlingMiddleware(
RequestDelegate next, ILogger logger, IHostEnvironment env)
{
_next = next;
_logger = logger;
_env = env;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (OperationCanceledException) when (context.RequestAborted.IsCancellationRequested)
{
// The client went away — not a server error; don't log as 500.
return;
}
catch (Exception ex)
{
await HandleAsync(context, ex);
}
}
private async Task HandleAsync(HttpContext context, Exception exception)
{
// Logged here → picked up by the DB sink (Error ≥ Warning floor) with full ex.ToString().
_logger.LogError(
exception,
"Unhandled exception for {Method} {Path} (traceId {TraceId})",
context.Request.Method, context.Request.Path, context.TraceIdentifier);
if (context.Response.HasStarted)
{
// Too late to write a clean body; the log row above is still captured.
return;
}
var problem = new ProblemDetails
{
Status = StatusCodes.Status500InternalServerError,
Title = "An unexpected error occurred.",
Type = "https://tools.ietf.org/html/rfc7231#section-6.6.1",
};
problem.Extensions["traceId"] = context.TraceIdentifier;
if (_env.IsDevelopment())
problem.Detail = exception.ToString();
context.Response.Clear();
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = "application/problem+json";
await context.Response.WriteAsync(JsonSerializer.Serialize(problem, JsonSerializerOptions));
}
private static readonly JsonSerializerOptions JsonSerializerOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
}