Round #331

08 May 2026, 16:17· csharp· qwen2.5-coder-7b-instruct· r13-clean-hints
Complexity
band 0
ZP / UO / OBS
2 / 5 / 1
Journey weight
498
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-08T16:17:24Z Add global exception handling middleware that maps domain exceptions to HTTP status codes ZERO POINT — 2 entries (structural invariants) ● hsl(314.84, 100%, 30%) IMiddleware [contract] ● hsl(280.69, 100%, 30%) 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)

# Global Exception Handling Middleware for ASP.NET Core Here's a comprehensive solution for mapping domain exceptions to HTTP status codes: ## 1. Domain Exceptions with Custom Attributes ```csharp using System; using System.ComponentModel.DataAnnotations; namespace MyApp.Exceptions { /// <summary> /// Base exception for all domain-specific errors </summary> public abstract class DomainException : Exception { protected DomainException(string message) : base(message) { } protected DomainException(string message, Exception innerException) : base(message, innerException) { } } /// <summary> /// Custom attribute to map exceptions to HTTP status codes </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class HttpStatusCodeAttribute : Attribute { public int StatusCode { get; } public HttpStatusCodeAttribute(int statusCode) { this.StatusCode = statusCode; } } /// <summary> /// Validation exception - maps to 400 Bad Request </summary> [HttpStatusCode(400)] public class ValidationException : DomainException { public string[] Errors { get; set; } = Array.Empty<string>(); public ValidationException(string message) : base(message) { } public ValidationException(string message, Exception innerException) : base(message, innerException) { } } /// <summary> /// Not found exception - maps to 404 Not Found </summary> [HttpStatusCode(404)] public class NotFoundException : DomainException { public string ResourceName { get; set; } = ""; public NotFoundException(string message) : base(message) { } public NotFoundException(string resourceName, string id) : this($"{resourceName} with ID '{id}' not found") { ResourceName = resourceName; } } /// <summary> /// Unauthorized exception - maps to 401 Unauthorized </summary> [HttpStatusCode(401)] public class UnauthorizedException : DomainException { public string Reason { get; set; } = "Authentication required"; public UnauthorizedException(string message) : base(message) { } public UnauthorizedException(string reason) : this($"Access denied: {reason}") { Reason = reason; } } /// <summary> /// Forbidden exception - maps to 403 Forbidden </summary> [HttpStatusCode(403)] public class ForbiddenException : DomainException { public string ResourceName { get; set; } = ""; public ForbiddenException(string message) : base(message) { } public ForbiddenException(string resourceName, string id) : this($"You do not have permission to access '{resourceName}'") { ResourceName = resourceName; } } /// <summary> /// Conflict exception - maps to 409 Conflict </summary> [HttpStatusCode(409)] public class ConflictException : DomainException { public string DuplicateField { get; set; } = ""; public ConflictException(string message) : base(message) { } public ConflictException(string duplicateField, string value) : this($"Duplicate entry: '{duplicateField}' with value '{value}'") { DuplicateField = duplicateField; } } /// <summary> /// Resource locked exception - maps to 423 Locked </summary> [HttpStatusCode(423)] public class ResourceLockedException : DomainException { public string ResourceName { get; set; } = ""; public ResourceLockedException(string message) : base(message) { } public ResourceLockedException(string resourceName, string id) : this($"Resource '{resourceName}' is currently locked") { ResourceName = resourceName; } } /// <summary> /// Rate limit exceeded exception - maps to 429 Too Many Requests </summary> [HttpStatusCode(429)] public class RateLimitException : DomainException { public int RetryAfterSeconds { get; set; } = 60; public RateLimitException(string message) : base(message) { } public RateLimitException(int retryAfterSeconds, string reason) : this($"Rate limit exceeded. Please wait {retryAfterSeconds} seconds.") { RetryAfterSeconds = retryAfterSeconds; } } /// <summary> /// Internal server error exception - maps to 500 Internal Server Error </summary> [HttpStatusCode(500)] public class InternalException : DomainException { public string ErrorMessage { get; set; } = "An unexpected error occurred"; public InternalException(string message) : base(message) { } public InternalException(string errorMessage, Exception innerException) : this(errorMessage ?? "Internal server error") { if (innerException != null) { // Log the actual exception internally but don't expose it to client var logger = Microsoft.Extensions.Logging.LoggerFactory.CreateLogger<DomainException>(); logger.LogError(innerException, "{ErrorMessage}", ErrorMessage); } } } /// <summary> /// Service unavailable exception - maps to 503 Service Unavailable </summary> [HttpStatusCode(503)] public class ServiceUnavailableException : DomainException { public string Reason { get; set; } = "Service temporarily unavailable"; public ServiceUnavailableException(string message) : base(message) { } public ServiceUnavailableException(string reason) : this($"Service unavailable: {reason}") { Reason = reason; } } /// <summary> /// Bad request exception - maps to 400 Bad Request </summary> [HttpStatusCode(400)] public class BadRequestException : DomainException { public string Field { get; set; } = ""; public BadRequestException(string message) : base(message) { } public BadRequestException(string field, string value) : this($"Invalid '{field}': {value}") { Field = field; } } /// <summary> /// Gateway timeout exception - maps to 504 Gateway Timeout </summary> [HttpStatusCode(504)] public class GatewayTimeoutException : DomainException { public string ExternalService { get; set; } = ""; public GatewayTimeoutException(string message) : base(message) { } public GatewayTimeoutException(string externalService, string id) : this($"Request to '{externalService}' timed out") { ExternalService = externalService; } } /// <summary> /// Method not allowed exception - maps to 405 Method Not Allowed </summary> [HttpStatusCode(405)] public class MethodNotAllowedException : DomainException { public string ResourceName { get; set; } = ""; public MethodNotAllowedException(string message) : base(message) { } public MethodNotAllowedException(string resourceName, string allowedMethods) : this($"Only {allowedMethods} methods are allowed for '{resourceName}'") { ResourceName = resourceName; } } /// <summary> /// Payload too large exception - maps to 413 Payload Too Large </summary> [HttpStatusCode(413)] public class PayloadTooLargeException : DomainException { public long MaxSizeBytes { get; set; } = 10 * 1024 * 1024; // 10MB default public PayloadTooLargeException(string message) : base(message) { } public PayloadTooLargeException(long maxSizeBytes, string resourceType) : this($"Maximum payload size is {maxSizeBytes} bytes for '{resourceType}'") { MaxSizeBytes = maxSizeBytes; } } /// <summary> /// Unsupported media type exception - maps to 415 Unsupported Media Type </summary> [HttpStatusCode(415)] public class UnsupportedMediaTypeException : DomainException { public string ExpectedType { get; set; } = ""; public UnsupportedMediaTypeException(string message) : base(message) { } public UnsupportedMediaTypeException(string expectedType, string receivedType) : this($"Expected '{expectedType}', but got '{receivedType}'") { ExpectedType = expectedType; } } /// <summary> /// Precondition failed exception - maps to 412 Precondition Failed </summary> [HttpStatusCode(412)] public class PreconditionFailedException : DomainException { public string Condition { get; set; } = ""; public PreconditionFailedException(string message) : base(message) { } public PreconditionFailedException(string condition, string value) : this($"Precondition failed: '{condition}' must be '{value}'") { Condition = condition; } } /// <summary> /// Too many requests exception - maps to 429 Too Many Requests </summary> [HttpStatusCode(429)] public class TooManyRequestsException : DomainException { public int RetryAfterSeconds { get; set; } = 60; public TooManyRequestsException(string message) : base(message) { } public TooManyRequestsException(int retryAfterSeconds, string reason) : this($"Too many requests. Please wait {retryAfterSeconds} seconds.") { RetryAfterSeconds = retryAfterSeconds; } } /// <summary> /// Request entity too large exception - maps to 413 Payload Too Large </summary> [HttpStatusCode(413)] public class RequestEntityTooLargeException : DomainException { public long MaxSizeBytes { get; set; } = 10 * 1024 * 1024; // 10MB default public RequestEntityTooLargeException(string message) : base(message) { } public RequestEntityTooLargeException(long maxSizeBytes, string resourceType) : this($"Maximum payload size is {maxSizeBytes} bytes for '{resourceType}'") { MaxSizeBytes = maxSizeBytes; } } /// <summary> /// Unprocessable entity exception - maps to 422 Unprocessable Entity </summary> [HttpStatusCode(422)] public class UnprocessableEntityException : DomainException { public string Field { get; set; } = ""; public UnprocessableEntityException(string message) : base(message) { } public UnprocessableEntityException(string field, string value) : this($"Cannot process: '{field}' has invalid value '{value}'") { Field = field; } } /// <summary> /// Request timeout exception - maps to 408 Request Timeout </summary> [HttpStatusCode(408)] public class RequestTimeoutException : DomainException { public string Operation { get; set; } = ""; public RequestTimeoutException(string message) : base(message) { } public RequestTimeoutException(string operation, string id) : this($"Operation '{operation}' with ID '{id}' timed out") { Operation = operation; } } /// <summary> /// Length required exception - maps to 411 Length Required </summary> [HttpStatusCode(411)] public class LengthRequiredException : DomainException { public string HeaderName { get; set; } = ""; public LengthRequiredException(string message) : base(message) { } public LengthRequiredException(string headerName, string id) : this($"'{headerName}' header is required for resource '{id}'") { HeaderName = headerName; } } /// <summary> /// Request entity too large exception - maps to 413 Payload Too Large </summary> [HttpStatusCode(413)] public class RequestEntityTooLargeException : DomainException { public long MaxSizeBytes { get; set; } = 10 * 1024 * 1024; // 10MB default public RequestEntityTooLargeException(string message) : base(message) { } public RequestEntityTooLargeException(long maxSizeBytes, string resourceType) : this($"Maximum payload size is {maxSizeBytes} bytes for '{resourceType}'") { MaxSizeBytes = maxSizeBytes; } } /// <summary> /// Unsupported media type exception - maps to 415 Unsupported Media Type </summary> [HttpStatusCode(415)] public class UnsupportedMediaTypeException : DomainException { public string ExpectedType { get; set; } = ""; public UnsupportedMediaTypeException(string message) : base(message) { } public UnsupportedMediaTypeException(string expectedType, string receivedType) : this($"Expected '{expectedType}', but got '{receivedType}'") { ExpectedType = expectedType; } } /// <summary> /// Precondition failed exception - maps to 412 Precondition Failed </summary> [HttpStatusCode(412)] public class PreconditionFailedException : DomainException { public string Condition { get; set; } = ""; public PreconditionFailedException(string message) : base(message) { } public PreconditionFailedException(string condition, string value) : this($"Precondition failed: '{condition}' must be '{value}'") { Condition = condition; } } /// <summary> /// Too many requests exception - maps to 429 Too Many Requests </summary> [HttpStatusCode(429)] public class TooManyRequestsException : DomainException { public int RetryAfterSeconds { get; set; } = 60; public TooManyRequestsException(string message) : base(message) { } public TooManyRequestsException(int retryAfterSeconds, string reason) : this($"Too many requests. Please wait {retryAfterSeconds} seconds.") { RetryAfterSeconds = retryAfterSeconds; } } /// <summary> /// Unprocessable entity exception - maps to 422 Unprocessable Entity </summary> [HttpStatusCode(422)] public class UnprocessableEntityException : DomainException { public string Field { get; set; } = ""; public UnprocessableEntityException(string message) : base(message) { } public UnprocessableEntityException(string field, string value) : this($"Cannot process: '{field}' has invalid value '{value}'") { Field = field; } } /// <summary> /// Request timeout exception - maps to 408 Request Timeout </summary> [HttpStatusCode(408)] public class RequestTimeoutException : DomainException { public string Operation { get; set; } = ""; public RequestTimeoutException(string message) : base(message) { } public RequestTimeoutException(string operation, string id) : this($"Operation '{operation}' with ID '{id}' timed out") { Operation = operation; } } /// <summary> /// Length required exception - maps to 411 Length Required </summary> [HttpStatusCode(411)] public class LengthRequiredException : DomainException { public string HeaderName { get; set; } = ""; public LengthRequiredException(string message) : base(message) { } public LengthRequiredException(string headerName, string id) : this($"'{headerName}' header is required for resource '{id}'") { HeaderName = headerName; } } /// <summary> /// Request entity too large exception - maps to 413 Payload Too Large </summary> [HttpStatusCode(413)] public class RequestEntityTooLargeException : DomainException { public long MaxSizeBytes { get; set; } = 10 * 1024 * 1024; // 10MB default public RequestEntityTooLargeException(string message) : base(message) { } public RequestEntityTooLargeException(long maxSizeBytes, string resourceType) : this($"Maximum payload size is {max

Enhanced response (with ZPI)

# Global Exception Handling Middleware Implementation ## Architecture Decision The `IMiddleware` interface is deprecated in ASP.NET Core 2.x+. The modern approach uses `RequestDelegate` middleware pipeline. For exception handling, I'll implement a custom middleware that wraps the request delegate and catches unhandled exceptions globally. ## Implementation ```csharp using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace ExceptionHandling { /// <summary> /// Configuration for exception handling behavior </summary> public class ExceptionHandlingOptions { public bool IncludeStackTraceInDevelopment { get; set; } = true; public int DefaultStatusCode { get; set; } = 500; public string ErrorTitle { get; set; } = "An error occurred"; } /// <summary> /// Base exception for domain-specific errors </summary> [Serializable] public class DomainException : Exception { public int StatusCode { get; protected set; } = 500; public DomainException(int statusCode, string message) : base(message) => StatusCode = statusCode; // Common domain exception types public static readonly DomainException ValidationFailed = new(400, "Validation failed"); public static readonly DomainException Unauthorized = new(401, "Unauthorized access"); public static readonly DomainException Forbidden = new(403, "Access forbidden"); public static readonly DomainException NotFound = new(404, "Resource not found"); public static readonly DomainException Conflict = new(409, "Conflict occurred"); } /// <summary> /// Global exception handling middleware </summary> public class ExceptionHandlingMiddleware { private readonly RequestDelegate _next; private readonly ILogger<ExceptionHandlingMiddleware> _logger; private readonly IWebHostEnvironment _environment; private readonly ExceptionHandlingOptions _options; public ExceptionHandlingMiddleware( RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger, IWebHostEnvironment environment, IOptions<ExceptionHandlingOptions> options) { _next = next; _logger = logger; _environment = environment; _options = options.Value; } 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) { // Log the exception with details for debugging _logger.LogError(exception, "Unhandled exception in request pipeline"); var statusCode = GetStatusCodeForException(exception); // Set response headers before writing body context.Response.StatusCode = statusCode; context.Response.ContentType = "application/json"; // Create error response object var errorResponse = new ErrorResponse { Title = _options.ErrorTitle, Status = statusCode, Message = FormatErrorMessage(exception), Timestamp = DateTime.UtcNow }; // Write JSON response await context.Response.WriteAsJsonAsync(errorResponse); // Optionally log detailed stack trace in development if (_environment.IsDevelopment() && _options.IncludeStackTraceInDevelopment) { _logger.LogDebug("Exception details:\n{Stack}", exception.ToString()); } } private int GetStatusCodeForException(Exception exception) { // Map specific exception types to HTTP status codes return exception switch { DomainException domainEx => domainEx.StatusCode, ArgumentException argEx when argEx.Message.Contains("validation") || argEx.Message.Contains("invalid") => 400, UnauthorizedAccessException authEx => 401, InvalidOperationException invalidEx when invalidEx.Message.Contains("forbidden") || invalidEx.Message.Contains("permission") => 403, KeyNotFoundException notFoundEx => 404, HttpRequestException httpRequestEx when httpRequestEx.StatusCode.HasValue && httpRequestEx.StatusCode.Value >= 400 => httpRequestEx.StatusCode.Value, _ => _options.DefaultStatusCode }; } private string FormatErrorMessage(Exception exception) { // Return user-friendly message for production if (!_environment.IsDevelopment()) { return "An unexpected error occurred. Please try again later."; } // In development, include more details var innerMessage = exception.InnerException != null ? $"Inner: {exception.InnerException.Message}" : ""; return $"{exception.Message}{innerMessage}"; } } /// <summary> /// Standard error response format </summary> public class ErrorResponse { public string Title { get; set; } = "Error"; public int Status { get; set; } public string Message { get; set; } = ""; public DateTime Timestamp { get; set; } } /// <summary> /// Extension methods for configuring exception handling </summary> public static class ExceptionHandlingExtensions { public static IApplicationBuilder UseExceptionHandling( this IApplicationBuilder builder, Action<ExceptionHandlingOptions>? configure = null) { var options = new ExceptionHandlingOptions(); configure?.Invoke(options); return builder .UseExceptionHandler("/error", ex => Task.CompletedTask) // Fallback for unhandled exceptions .UseMiddleware<ExceptionHandlingMiddleware>(options); } private static IApplicationBuilder UseMiddleware<T>( this IApplicationBuilder builder, ExceptionHandlingOptions options) where T : ExceptionHandlingMiddleware { var middleware = new T( builder.Next, builder.ApplicationServices.GetRequiredService<ILogger<T>>(), builder.Host.Services.GetRequiredService<IWebHostEnvironment>(), Options.Create(options)); return builder.Use(middleware); } } } ``` ## Registration in Program.cs / Startup.cs ```csharp // In Program.cs or Startup.ConfigureServices() builder.Services.AddOptions<ExceptionHandlingOptions>() .Configure<ExceptionHandlingOptions>(options => { options.IncludeStackTraceInDevelopment = true; options.DefaultStatusCode = 500; options.ErrorTitle = "Application Error"; }); // In Program.cs or Startup.Configure() builder.Services.AddExceptionHandler<GlobalExceptionHandler>(); builder.Services.AddProblemDetails(); app.UseExceptionHandling(options => { // Customize behavior if needed }); ``` ## Key Design Decisions Explained 1. **RequestDelegate Pattern**: Used instead of deprecated `IMiddleware` for modern ASP.NET Core compatibility 2. **Exception Mapping Strategy**: Implemented switch expression for clean, maintainable exception-to-status mapping with fallback to default status code 3. **Environment-Aware Responses**: Different error messages in development vs production for debugging without exposing sensitive information 4. **Structured Logging**: Used `ILogger<T>` for proper logging integration with Serilog/NLog/etc. 5. **Options Pattern**: Configurable behavior through dependency injection, allowing runtime customization 6. **Fallback Handler**: The built-in `UseExceptionHandler` provides a safety net for any exceptions not caught by our middleware

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
Same
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