Error Handling¶
Structured error handling from exceptions to client responses.
Overview¶
flowchart TB
A[Exception Thrown] --> B[GlobalExceptionHandler]
B --> C{Exception Type?}
C -->|Validation| D[400 Bad Request]
C -->|MissingData| E[404 Not Found]
C -->|Auth| F[401 Unauthorized]
C -->|Forbidden| G[403 Forbidden]
C -->|Concurrency| H[409 Conflict]
C -->|Other| I[500 Internal Error]
D --> J[Problem Details Response]
E --> J
F --> J
G --> J
H --> J
I --> J Exception Types¶
Domain Exceptions¶
| Exception | HTTP Status | When to Use |
|---|---|---|
ValidationException | 400 | Invalid input |
MissingDataException | 404 | Entity not found |
AuthException | 401 | Not authenticated |
ForbiddenException | 403 | Unauthorized action |
ConcurrencyException | 409 | Optimistic locking conflict |
TooManyRequestsException | 429 | Rate limit exceeded |
Database Exceptions¶
| Exception | HTTP Status | When to Use |
|---|---|---|
DbIndexViolationException | 409 | Unique constraint |
DbForeignKeyViolationException | 409 | FK constraint |
DbCheckConstraintViolationException | 409 | Check constraint |
DbNotNullViolationException | 400 | Required field |
Throwing Exceptions¶
public async Task<MarkerResponse> Handle(GetMarkerQuery request, ...)
{
var marker = await context.Markers
.FirstOrDefaultAsync(m => m.MarkerId == request.MarkerId);
if (marker is null)
{
throw new MissingDataException(ErrorCodes.MarkerNotFound);
}
return marker.ToResponse();
}
With field-level errors:
throw new ValidationException(new Dictionary<string, string[]>
{
["email"] = [ErrorCodes.EmailAlreadyExists]
});
Error Response Format¶
All errors return consistent JSON:
{
"code": "VALIDATION_ERROR",
"message": "One or more validation errors occurred",
"errors": {
"title": ["REQUIRED"],
"latitude": ["INVALID_RANGE"]
},
"correlationId": "abc-123-def"
}
| Field | Description |
|---|---|
code | Error code constant |
message | Human-readable message |
errors | Field-specific errors (validation) |
correlationId | Request tracking ID |
Error Codes¶
Defined in ErrorCodes.cs:
public static class ErrorCodes
{
public const string ValidationError = "VALIDATION_ERROR";
public const string NotFound = "NOT_FOUND";
public const string Forbidden = "FORBIDDEN";
public const string Conflict = "CONFLICT";
public const string MarkerNotFound = "MARKER_NOT_FOUND";
public const string EmailAlreadyExists = "EMAIL_ALREADY_EXISTS";
public const string InvalidCredentials = "INVALID_CREDENTIALS";
}
See Error Codes Reference for complete list.
Global Exception Handler¶
Exception mapping in middleware:
var (statusCode, detail) = ex switch
{
ValidationException => (400, ex.Message),
MissingDataException => (404, ex.Message),
AuthException => (401, ex.Message),
ForbiddenException => (403, ex.Message),
TooManyRequestsException => (429, ex.Message),
ConcurrencyException => (409, "A conflict occurred. Please retry."),
DbIndexViolationException => (409, "Duplicate entry."),
_ => (500, "An unexpected error occurred.")
};
Uses RFC 7807 Problem Details format for responses.
Logging¶
- 4xx errors: Logged as warnings (expected)
- 5xx errors: Logged as errors with stack trace
- Correlation ID included in all logs
Related¶
- Error Codes — Complete error code list
- Middleware — Exception handler middleware
- Validation — Request validation