Improve exception handling with ProblemDetails responses

ExceptionHandlingMiddleware now returns structured ProblemDetails
responses for errors, including specific handling for
InvalidOperationException (400 Bad Request) and general exceptions
(500 Internal Server Error). Responses use "application/problem+json"
content type and include trace IDs. ProblemDetails support is also
registered in Program.cs.
This commit is contained in:
OlgunR
2026-04-21 13:18:59 +02:00
parent b7e66ab5f9
commit 612d8371d3
2 changed files with 19 additions and 17 deletions

View File

@@ -1,5 +1,4 @@
using System.Net; using Microsoft.AspNetCore.Mvc;
using System.Text.Json;
namespace DbFirst.API.Middleware; namespace DbFirst.API.Middleware;
@@ -20,33 +19,35 @@ public class ExceptionHandlingMiddleware
{ {
await _next(context); await _next(context);
} }
catch (InvalidOperationException ex)
{
_logger.LogWarning(ex, "Domain validation error");
await WriteProblemAsync(context, StatusCodes.Status400BadRequest, "Eingabe ungültig", ex.Message);
}
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Unhandled exception"); _logger.LogError(ex, "Unhandled exception");
await WriteProblemDetailsAsync(context, ex); await WriteProblemAsync(context, StatusCodes.Status500InternalServerError, "Serverfehler", ex.Message);
} }
} }
private static async Task WriteProblemDetailsAsync(HttpContext context, Exception ex) private static async Task WriteProblemAsync(HttpContext context, int status, string title, string detail)
{ {
if (context.Response.HasStarted) if (context.Response.HasStarted) return;
{
throw ex;
}
context.Response.Clear(); context.Response.Clear();
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; context.Response.StatusCode = status;
context.Response.ContentType = "application/json"; context.Response.ContentType = "application/problem+json";
var problem = new var problem = new ProblemDetails
{ {
type = "https://tools.ietf.org/html/rfc9110#section-15.6.1", Type = $"https://tools.ietf.org/html/rfc9110#section-{(status == 400 ? "15.5.1" : "15.6.1")}",
title = "Serverfehler", Title = title,
status = context.Response.StatusCode, Status = status,
detail = ex.Message, Detail = detail,
traceId = context.TraceIdentifier Extensions = { ["traceId"] = context.TraceIdentifier }
}; };
await context.Response.WriteAsync(JsonSerializer.Serialize(problem)); await context.Response.WriteAsJsonAsync(problem);
} }
} }

View File

@@ -13,6 +13,7 @@ var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(); builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen();
builder.Services.AddProblemDetails();
// TODO: allow listed origins configured in appsettings.json // TODO: allow listed origins configured in appsettings.json
// In any case, dont let them to free to use without cors. if there is no origin specified, block all. // In any case, dont let them to free to use without cors. if there is no origin specified, block all.