using Microsoft.AspNetCore.Mvc; namespace Tiku.Api.Middleware; internal sealed class ExceptionHandlingMiddleware( RequestDelegate next, ILogger logger, IHostEnvironment environment, IEnumerable mappers) { private readonly IReadOnlyCollection problemDetailsMappers = mappers.ToArray(); public async Task InvokeAsync(HttpContext context) { try { await next(context); } catch (Exception exception) { var mappings = problemDetailsMappers .Select(mapper => mapper.TryMap(exception, out var mapping) ? mapping : null) .Where(mapping => mapping is not null) .Cast() .ToArray(); if (mappings.Length == 1) { await WriteProblemAsync(context, mappings[0]); return; } if (mappings.Length > 1) logger.LogCritical(exception, "Multiple ProblemDetails mappers matched {ExceptionType}", exception.GetType().FullName); else logger.LogError(exception, "Unhandled API exception"); var problem = new ProblemDetails { Title = "An unexpected error occurred.", Status = StatusCodes.Status500InternalServerError, Detail = environment.IsDevelopment() ? exception.Message : null, Instance = context.Request.Path }; problem.Extensions["traceId"] = context.TraceIdentifier; context.Response.StatusCode = StatusCodes.Status500InternalServerError; await context.Response.WriteAsJsonAsync(problem); } } private static async Task WriteProblemAsync( HttpContext context, ExceptionProblemDetailsMapping mapping) { var problem = new ProblemDetails { Title = mapping.Title, Status = mapping.Status, Instance = context.Request.Path }; problem.Extensions["code"] = mapping.Code; problem.Extensions["traceId"] = context.TraceIdentifier; foreach (var extension in mapping.Extensions) problem.Extensions[extension.Key] = extension.Value; context.Response.StatusCode = mapping.Status; await context.Response.WriteAsJsonAsync(problem); } }