Round #299

08 May 2026, 13:06· csharp· openai/gpt-oss-120b:free· r13-clean-hints
Complexity
band 0
ZP / UO / OBS
2 / 5 / 1
Journey weight
494
Lightshift ΔL
+0.00

Prompt

Add global exception handling middleware that maps domain exceptions to HTTP status codes

Zero-point index (what the engine surfaced)

ZPI-ZPI-0001 | 2026-05-08T13:06:33Z Add global exception handling middleware that maps domain exceptions to HTTP status codes ZERO POINT — 2 entries (structural invariants) ● hsl(314.84, 100%, 32%) IMiddleware [contract] ● hsl(280.69, 100%, 32%) RequestDelegate [contract] UNOBSERVED — 5 entries (implied truths) ○ hsl(27.69, 100%, 50%) async [feature] ○ hsl(9.23, 100%, 50%) interface [feature] ○ hsl(77.54, 100%, 36%) Routing middleware [function] ○ hsl(83.08, 100%, 42%) ILogger of T injection [function] ○ hsl(31.38, 100%, 42%) Scoped registration [function] OBSERVED — 1 entries (explicit in request) ◉ hsl(86.15, 100%, 22%) global [feature] COMPLEXITY │ 8 entries │ spread: 77° │ band: LOW

Baseline response (no ZPI)

Below is a **complete, drop‑in example** that adds a global exception‑handling middleware to an ASP.NET Core Web API project and maps your *domain* exceptions to the appropriate HTTP status codes. --- ## 1. Define the domain‑exception hierarchy First create a small hierarchy (or use the one you already have). Each exception type knows which HTTP status it should be translated to. ```csharp // File: src/YourApp.Domain/Exceptions/DomainException.cs using System; namespace YourApp.Domain.Exceptions { /// <summary> /// Base class for all domain‑level errors. /// </summary> public abstract class DomainException : Exception { protected DomainException(string message) : base(message) { } /// <summary> /// The HTTP status code that should be returned for this exception. /// </summary> public abstract int StatusCode { get; } } } ``` ```csharp // File: src/YourApp.Domain/Exceptions/NotFoundException.cs using System.Net; namespace YourApp.Domain.Exceptions { public sealed class NotFoundException : DomainException { public NotFoundException(string message) : base(message) { } public override int StatusCode => (int)HttpStatusCode.NotFound; } } ``` ```csharp // File: src/YourApp.Domain/Exceptions/ConflictException.cs using System.Net; namespace YourApp.Domain.Exceptions { public sealed class ConflictException : DomainException { public ConflictException(string message) : base(message) { } public override int StatusCode => (int)HttpStatusCode.Conflict; } } ``` ```csharp // File: src/YourApp.Domain/Exceptions/ValidationException.cs using System.Net; namespace YourApp.Domain.Exceptions { public sealed class ValidationException : DomainException { public ValidationException(string message) : base(message) { } public override int StatusCode => (int)HttpStatusCode.BadRequest; } } ``` Add any other domain exceptions you need (e.g., `UnauthorizedException`, `ForbiddenException`, etc.) following the same pattern. --- ## 2. Create the middleware ```csharp // File: src/YourApp.Api/Middleware/ExceptionHandlingMiddleware.cs using System.Net; using System.Text.Json; using Microsoft.AspNetCore.Http; using YourApp.Domain.Exceptions; namespace YourApp.Api.Middleware { /// <summary> /// Catches all unhandled exceptions, logs them and converts known /// <see cref="DomainException"/> types to proper HTTP responses. /// </summary> public sealed class ExceptionHandlingMiddleware { private readonly RequestDelegate _next; private readonly ILogger<ExceptionHandlingMiddleware> _logger; public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger) { _next = next ?? throw new ArgumentNullException(nameof(next)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (Exception ex) { await HandleExceptionAsync(context, ex); } } private async Task HandleExceptionAsync(HttpContext context, Exception exception) { // Default to 500 – internal server error var statusCode = (int)HttpStatusCode.InternalServerError; var problemDetails = new ProblemDetails { Instance = context.Request.Path, Title = "An unexpected error occurred." }; // ------------------------------------------------- // 1️⃣ Domain exceptions – map to the status code they expose // ------------------------------------------------- if (exception is DomainException domainEx) { statusCode = domainEx.StatusCode; problemDetails.Title = domainEx.Message; problemDetails.Status = statusCode; problemDetails.Type = $"https://httpstatuses.com/{statusCode}"; } // ------------------------------------------------- // 2️⃣ Known framework exceptions (optional) // ------------------------------------------------- else if (exception is UnauthorizedAccessException) { statusCode = (int)HttpStatusCode.Unauthorized; problemDetails.Title = "Unauthorized."; problemDetails.Status = statusCode; problemDetails.Type = $"https://httpstatuses.com/{statusCode}"; } // ------------------------------------------------- // 3️⃣ Anything else – log and hide details // ------------------------------------------------- else { _logger.LogError(exception, "Unhandled exception processing request {Method} {Path}", context.Request.Method, context.Request.Path); problemDetails.Title = "Internal Server Error"; problemDetails.Status = statusCode; problemDetails.Type = $"https://httpstatuses.com/{statusCode}"; } // ------------------------------------------------- // Write the response // ------------------------------------------------- context.Response.ContentType = "application/problem+json"; context.Response.StatusCode = statusCode; var json = JsonSerializer.Serialize(problemDetails, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = false }); await context.Response.WriteAsync(json); } } // Extension method for a clean registration in the pipeline public static class ExceptionHandlingMiddlewareExtensions { public static IApplicationBuilder UseExceptionHandling(this IApplicationBuilder builder) { return builder.UseMiddleware<ExceptionHandlingMiddleware>(); } } } ``` ### Why `ProblemDetails`? * It follows the RFC 7807 standard. * Clients (including browsers, Postman, Swagger UI, etc.) already know how to render it. * You can later enrich it with `extensions` (e.g., validation errors) without breaking the contract. --- ## 3. Register the middleware in the request pipeline Place it **as the first middleware** (right after the built‑in `UseRouting` if you use endpoint routing, or even before that) so it can

Enhanced response (with ZPI)

**1. Global exception‑handling middleware** ```csharp using System; using System.Collections.Generic; using System.Net; using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; /// <summary> /// Middleware that catches all exceptions thrown by downstream components, /// translates known domain exceptions into appropriate HTTP status codes, /// and returns a JSON error payload. /// </summary> public sealed class GlobalExceptionHandlingMiddleware { private readonly RequestDelegate _next; private readonly ILogger<GlobalExceptionHandlingMiddleware> _logger; private readonly IDictionary<Type, HttpStatusCode> _exceptionStatusMap; public GlobalExceptionHandlingMiddleware( RequestDelegate next, ILogger<GlobalExceptionHandlingMiddleware> logger, IDictionary<Type, HttpStatusCode>? exceptionStatusMap = null) { _next = next ?? throw new ArgumentNullException(nameof(next)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); // Default mapping – can be overridden via DI registration _exceptionStatusMap = exceptionStatusMap ?? new Dictionary<Type, HttpStatusCode> { // Domain‑specific mappings { typeof(EntityNotFoundException), HttpStatusCode.NotFound }, { typeof(ValidationException), HttpStatusCode.BadRequest }, { typeof(ConflictException), HttpStatusCode.Conflict }, // Fallback for any other known domain exception { typeof(DomainException), HttpStatusCode.BadRequest } }; } public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (Exception ex) when (IsDomainException(ex)) { // Log with appropriate level _logger.LogWarning(ex, "Handled domain exception: {Message}", ex.Message); await WriteErrorResponseAsync(context, ex); } catch (Exception ex) { // Unexpected exception – log as error and return 500 _logger.LogError(ex, "Unhandled exception"); await WriteErrorResponseAsync(context, ex, HttpStatusCode.InternalServerError); } } private bool IsDomainException(Exception ex) => ex is DomainException || _exceptionStatusMap.ContainsKey(ex.GetType()); private HttpStatusCode ResolveStatusCode(Exception ex) => _exceptionStatusMap.TryGetValue(ex.GetType(), out var code) ? code : HttpStatusCode.BadRequest; // fallback for unmapped domain exceptions private async Task WriteErrorResponseAsync(HttpContext context, Exception ex, HttpStatusCode? forcedStatus = null) { var status = forcedStatus ?? ResolveStatusCode(ex); var problem = new ProblemDetails { Status = (int)status, Title = ex.Message, Type = ex.GetType().FullName, Instance = context.Request.Path }; var payload = JsonSerializer.Serialize(problem, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); context.Response.ContentType = "application/problem+json"; context.Response.StatusCode = (int)status; await context.Response.WriteAsync(payload); } // Simple Problem Details DTO – you can replace with Microsoft.AspNetCore.Mvc.ProblemDetails private sealed class ProblemDetails { public int Status { get; set; } public string? Title { get; set; } public string? Type { get; set; } public string? Instance { get; set; } } } ``` **2. Domain‑exception base types (example)** These are the exceptions the middleware knows how to translate. You can place them in a shared project/namespace. ```csharp using System; public abstract class DomainException : Exception { protected DomainException(string message) : base(message) { } protected DomainException(string message, Exception inner) : base(message, inner) { } } public sealed class EntityNotFoundException : DomainException { public EntityNotFoundException(string entityName, object key) : base($"{entityName} with key '{key}' was not found.") { } } public sealed class ValidationException : DomainException { public ValidationException(string message) : base(message) { } } public sealed class ConflictException : DomainException { public ConflictException(string message) : base(message) { } } ``` **3. Extension method for easy registration** ```csharp using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using System.Collections.Generic; using System.Net; public static class GlobalExceptionHandlingExtensions { /// <summary> /// Adds the <see cref="GlobalExceptionHandlingMiddleware"/> to the pipeline. /// </summary> public static IApplicationBuilder UseGlobalExceptionHandling(this IApplicationBuilder app, IDictionary<Type, HttpStatusCode>? customMap = null) { // The middleware itself is resolved from DI, so we can pass the map via options. return app.UseMiddleware<GlobalExceptionHandlingMiddleware>(customMap); } /// <summary> /// Registers the middleware as a scoped service (so ILogger<T> is injected correctly). /// </summary> public static IServiceCollection AddGlobalExceptionHandling(this IServiceCollection services, IDictionary<Type, HttpStatusCode>? customMap = null) { // Register the map as a singleton – the middleware receives it via constructor injection. if (customMap != null) { services.AddSingleton(customMap); } // The middleware itself does not need explicit registration; UseMiddleware // will resolve it from the container. Registering as scoped makes ILogger<T> // scoped to the request pipeline. services.AddScoped<GlobalExceptionHandlingMiddleware>(); return services; } } ``` **4. Wire‑up in `Program.cs` (minimal API style)** ```csharp var builder = WebApplication.CreateBuilder(args); // Register logging, controllers, etc. builder.Services.AddControllers(); // Register the exception‑handling middleware (optional custom map) builder.Services.AddGlobalExceptionHandling(new Dictionary<Type, HttpStatusCode> { // Example of overriding the default mapping { typeof(ValidationException), HttpStatusCode.UnprocessableEntity } }); var app = builder.Build(); // Global exception handling must be the first middleware so it can catch everything. app.UseGlobalExceptionHandling(); // Typical ASP.NET Core pipeline app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run(); ``` **5. What the code does** | Step | Behaviour | |------|-----------| | **Middleware construction** | Receives `RequestDelegate` (the next component), an `ILogger<GlobalExceptionHandlingMiddleware>` and an optional dictionary that maps exception types to `HttpStatusCode`. | | **InvokeAsync** | Executes the downstream pipeline inside a `try`. If a known domain exception is thrown, it logs a warning, maps the exception to a status code, and returns a JSON *Problem Details* payload. Any other exception is logged as an error and results in a `500 Internal Server Error`. | | **Mapping** | By default the middleware knows about `EntityNotFoundException → 404`, `ValidationException → 400`, `ConflictException → 409`, and a generic `DomainException → 400`. The map can be overridden or extended via DI registration. | | **Response format** | Returns `application/problem+json` with fields `status`, `title`, `type`, and `instance` – a lightweight version of the RFC‑7807 Problem Details format. | | **DI registration** | `AddGlobalExceptionHandling` registers the middleware as scoped (so the injected `ILogger<T>` is request‑scoped) and optionally registers a custom map. The extension `UseGlobalExceptionHandling` adds the middleware to the pipeline. | | **Placement** | The middleware should be placed **before** any other component that could throw domain exceptions (typically right after routing is set up, but before authentication/authorization if you want to capture those as well). | With this implementation you have a single, reusable piece of infrastructure that converts your domain‑level errors into the correct HTTP responses, logs them appropriately, and keeps the rest of your controllers/services clean from repetitive `try/catch` blocks.

Grader chain

Each grader reads the same baseline + enhanced pair and casts an independent verdict. Disagreement is the framework's own signal — not noise to be averaged away.

V1claude-sonnet-4-6
Better
V4qwen2.5-coder-7b-instruct
Better

Comments

Our grader said what it said. What do you say? Comment as a guest below.

No comments yet. Be the first to say what you make of this round.

← back to all rounds