-
Notifications
You must be signed in to change notification settings - Fork 15
Description
Description
Implement a custom middleware to handle exceptions globally in the application. The middleware should catch unhandled exceptions, log the error details, and return a standardized error response. The response format should follow RFC 7807 (Problem Details for HTTP APIs), ensuring consistency and better error representation in API responses.
Acceptance Criteria
- Create a middleware component to handle global exceptions.
- Return standardized error responses in RFC 7807 format including
type,title,status,detail, andinstance(optional). - Ensure the middleware is registered in the application pipeline.
- Add unit tests to validate the middleware behavior.
Implementation Plan
Scope Decisions
Following RFC 7807 (Problem Details for HTTP APIs) with these implementation choices:
-
RFC 7807 Compliance: Use standard
ProblemDetailsmodel fromMicrosoft.AspNetCore.Mvc(built-in to .NET 8)type: URI identifying error categorytitle: Short, human-readable summarystatus: HTTP status codedetail: Human-readable error descriptioninstance: Request path or trace ID for correlation
-
Error Detail Level: Include stack traces in
detailfield only in Development environment (IHostEnvironment.IsDevelopment())- Development: Full exception details and stack trace for debugging
- Production: Generic error message without sensitive information
-
Validation Error Handling: Keep existing FluentValidation error responses in controllers separate
- Middleware focuses on unhandled exceptions only
- Structured validation errors continue using current pattern (maintains backward compatibility)
Implementation Steps
1. Create Middleware/ExceptionMiddleware.cs
- Wrap
next(context)in try-catch block - Map exception types to appropriate HTTP status codes:
ValidationException→ 400 Bad RequestDbUpdateConcurrencyException→ 409 ConflictOperationCanceledException→ 408 Request TimeoutArgumentException/InvalidOperationException→ 400 Bad Request- Generic
Exception→ 500 Internal Server Error
- Return
ProblemDetailswithapplication/problem+jsoncontent type - Log exceptions using
ILoggerwith structured logging (Serilog) - Include request ID/trace ID in
instancefield for correlation
2. Create Extensions/MiddlewareExtensions.cs
- Add
UseExceptionHandling(this WebApplication app)extension method - Follow existing project pattern from
ServiceCollectionExtensions.cs
3. Update Program.cs
- Register middleware using
app.UseExceptionHandling() - Pipeline placement: After
UseSerilogRequestLogging()but before other middleware - Current order will be: Serilog → Exception Handling → HTTPS Redirection → Health Checks → Rate Limiter → Controllers
4. Create test/.../Unit/ExceptionMiddlewareTests.cs
- Test middleware catches unhandled exceptions
- Verify response format matches RFC 7807 (all required fields present)
- Validate HTTP status code mapping for different exception types
- Confirm structured logging occurs with exception details
- Verify
Content-Typeheader isapplication/problem+json - Test Development vs Production detail level differences
Technical Details
No additional dependencies needed — All required components are built-in:
Microsoft.AspNetCore.Mvc.ProblemDetails(ASP.NET Core 8.0)Microsoft.AspNetCore.Httpfor middlewareSystem.Text.Jsonfor serializationSerilog(already configured)
Benefits
- ✅ Centralized exception handling
- ✅ Standards-compliant error responses (RFC 7807)
- ✅ Consistent API error format
- ✅ Enhanced observability with structured logging
- ✅ Request traceability via instance/trace IDs
- ✅ Environment-aware detail level (security)
- ✅ Zero breaking changes to existing validation handling
Suggested Approach
- Create
ExceptionMiddleware.cs
public class ExceptionMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try { await next(context); }
catch (Exception ex)
{
context.Response.ContentType = "application/problem+json";
var problem = new ProblemDetails
{
Title = "An error occurred",
Detail = ex.Message,
Status = (int)HttpStatusCode.InternalServerError
};
await context.Response.WriteAsync(JsonSerializer.Serialize(problem));
}
}
}- Register in
Program.cs:
app.UseMiddleware<ExceptionMiddleware>();