Validation Pattern¶
Request validation using FluentValidation.
Overview¶
All incoming requests are validated via ASP.NET Core auto-validation before reaching the controller:
Request → ASP.NET Auto-Validation → Controller → MiniatR → Handler
↓
Validation errors? → 400 Bad Request
Validator Structure¶
public sealed class CreateMarkerRequestValidator
: AbstractValidator<CreateMarkerRequest>
{
public CreateMarkerRequestValidator()
{
RuleFor(x => x.Title)
.NotEmpty()
.MaximumLength(200);
RuleFor(x => x.Latitude)
.InclusiveBetween(-90, 90);
RuleFor(x => x.Longitude)
.InclusiveBetween(-180, 180);
RuleFor(x => x.Rating)
.InclusiveBetween(1, 5)
.When(x => x.Rating.HasValue);
}
}
Common Rules¶
| Rule | Use For |
|---|---|
NotEmpty() | Required strings, collections |
NotNull() | Required reference types |
MaximumLength(n) | String length limits |
InclusiveBetween(a, b) | Numeric ranges |
EmailAddress() | Email format |
Must(predicate) | Custom validation |
Conditional Validation¶
Custom Rules¶
RuleFor(x => x.CategoryId)
.MustAsync(async (id, ct) =>
await context.Categories.AnyAsync(c => c.CategoryId == id, ct))
.WithMessage("Category does not exist");
Base Validators & Composition¶
Reusable validation rules can be applied via inheritance or composition:
| Base Class | Interface | Purpose |
|---|---|---|
CaptchaRequestValidator<T> | ICaptchaRequest | CAPTCHA token validation |
PaginatedRequestValidator<T> | IPaginatedRequest | PageNumber/PageSize validation |
Option 1: Inheritance (when no other base class is needed)
public sealed class GetActionHistoryRequestValidator
: PaginatedRequestValidator<GetActionHistoryRequest>
{
public GetActionHistoryRequestValidator()
{
// Pagination rules inherited automatically
RuleFor(x => x.DateFrom)
.LessThanOrEqualTo(x => x.DateTo)
.When(x => x.DateFrom.HasValue && x.DateTo.HasValue);
}
}
Option 2: Composition (when you need another base class)
public sealed class MyRequestValidator : SomeOtherBaseValidator<MyRequest>
{
public MyRequestValidator()
{
// Add pagination rules via extension method
this.AddPaginationRules();
// Add other rules
RuleFor(x => x.Name).NotEmpty();
}
}
Error Response¶
Validation errors return 400 with field-specific messages:
{
"code": "VALIDATION_ERROR",
"errors": {
"title": ["Title is required"],
"latitude": ["Latitude must be between -90 and 90"]
}
}
Registration¶
Validators are auto-registered via assembly scanning with ASP.NET Core integration:
services.AddValidatorsFromAssemblyContaining<LoginRequestValidator>(includeInternalTypes: true);
services.AddFluentValidationAutoValidation();
Related¶
- CQRS — Command/Query pattern
- Error Codes — Error response format