When: Input validation fails (FluentValidation)Status Code:400 Bad RequestHandler:
private async Task HandleValidationException(HttpContext httpContext, Exception ex){ var exception = (ValidationException)ex; httpContext.Response.StatusCode = StatusCodes.Status400BadRequest; await httpContext.Response.WriteAsJsonAsync(new ValidationProblemDetails(exception.Errors) { Status = StatusCodes.Status400BadRequest, Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1" });}
Example Response:
{ "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "One or more validation errors occurred.", "status": 400, "errors": { "Email": [ "Email is required", "Invalid email format" ], "Password": [ "Password must be at least 8 characters" ] }}
When: Requested resource doesn’t existStatus Code:404 Not FoundHandler:
private async Task HandleNotFoundException(HttpContext httpContext, Exception ex){ var exception = (NotFoundException)ex; httpContext.Response.StatusCode = StatusCodes.Status404NotFound; await httpContext.Response.WriteAsJsonAsync(new ProblemDetails() { Status = StatusCodes.Status404NotFound, Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4", Title = "The specified resource was not found.", Detail = exception.Message });}
Example Response:
{ "type": "https://tools.ietf.org/html/rfc7231#section-6.5.4", "title": "The specified resource was not found.", "status": 404, "detail": "User with ID '12345' was not found."}
public class GetSensitiveDataQueryHandler : IRequestHandler<GetSensitiveDataQuery, SensitiveDataDto>{ private readonly IUser _currentUser; private readonly IApplicationDbContext _context; public async Task<SensitiveDataDto> Handle( GetSensitiveDataQuery request, CancellationToken cancellationToken) { // Check if user has required permission if (!_currentUser.HasPermission("data.view_sensitive")) { throw new ForbiddenAccessException(); } // Proceed with query // ... }}
Validation exceptions are thrown automatically by the ValidationBehaviour when FluentValidation rules fail:
// This validator will cause ValidationException if rules failpublic class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>{ public CreateUserCommandValidator() { RuleFor(x => x.Email) .NotEmpty().WithMessage("Email is required") .EmailAddress().WithMessage("Invalid email format"); }}
You don’t throw ValidationException manually - the MediatR pipeline handles it automatically when validators fail.
// Add global exception handling (RFC 7807)builder.Services.AddProblemDetails();// ... build app ...app.UseExceptionHandler(); // Must be called after building app
POST /api/PermissionsContent-Type: application/json{ "name": "", "module": ""}
Response: 400 Bad Request
{ "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "One or more validation errors occurred.", "status": 400, "errors": { "Name": ["El nombre del permiso es requerido"], "Module": ["El módulo es requerido"] }}
GET /api/Permissions/999Authorization: Bearer {token}
Response: 404 Not Found
{ "type": "https://tools.ietf.org/html/rfc7231#section-6.5.4", "title": "The specified resource was not found.", "status": 404, "detail": "Permission with ID '999' was not found."}
public class DuplicatePermissionException : Exception{ public DuplicatePermissionException(string permissionName) : base($"Permission '{permissionName}' already exists.") { }}