using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Mvc; namespace SatelliteProvider.Api; public sealed class GlobalExceptionHandler : IExceptionHandler { private readonly ILogger _logger; public GlobalExceptionHandler(ILogger logger) { _logger = logger; } public async ValueTask TryHandleAsync( HttpContext httpContext, Exception exception, CancellationToken cancellationToken) { // Framework-level request-binding/parsing failures carry their own HTTP status // (typically 400/415). Honor that status so we don't promote a client error to 5xx. if (exception is BadHttpRequestException badRequest) { await WriteClientErrorAsync(httpContext, badRequest, cancellationToken); return true; } var correlationId = httpContext.TraceIdentifier; _logger.LogError( exception, "Unhandled exception while processing {Method} {Path} (correlationId={CorrelationId})", httpContext.Request.Method, httpContext.Request.Path, correlationId); httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; var problem = new ProblemDetails { Status = StatusCodes.Status500InternalServerError, Title = "Internal Server Error", Detail = "An unexpected error occurred. Use the correlationId to look up the server log entry.", Type = "https://datatracker.ietf.org/doc/html/rfc9110#name-500-internal-server-error", }; problem.Extensions["correlationId"] = correlationId; await httpContext.Response.WriteAsJsonAsync( problem, options: null, contentType: "application/problem+json", cancellationToken: cancellationToken); return true; } private static async Task WriteClientErrorAsync( HttpContext httpContext, BadHttpRequestException badRequest, CancellationToken cancellationToken) { httpContext.Response.StatusCode = badRequest.StatusCode; var problem = new ProblemDetails { Status = badRequest.StatusCode, Title = "Bad Request", Detail = badRequest.Message, }; await httpContext.Response.WriteAsJsonAsync( problem, options: null, contentType: "application/problem+json", cancellationToken: cancellationToken); } }