patterncsharpdotnetTip
FluentValidation: centralizing validation logic outside controllers
Viewed 0 times
FluentValidation AbstractValidatorAddValidatorsFromAssemblyvalidation minimal apiRuleFor FluentValidationcross-property validation
Problem
Data annotation attributes scattered across DTO classes mix validation rules with model definitions. Complex cross-property rules are difficult to express with attributes alone.
Solution
Define a validator class per DTO and register with DI:
public class CreateOrderValidator : AbstractValidator<CreateOrderRequest>
{
public CreateOrderValidator()
{
RuleFor(x => x.CustomerId)
.NotEmpty().WithMessage("Customer is required");
RuleFor(x => x.Items)
.NotEmpty().WithMessage("Order must have at least one item")
.ForEach(item => item
.ChildRules(r => r.RuleFor(i => i.Quantity).GreaterThan(0)));
RuleFor(x => x.DeliveryDate)
.GreaterThan(DateTime.UtcNow).When(x => x.DeliveryDate.HasValue);
}
}
// Registration (auto-register all validators in assembly)
builder.Services.AddValidatorsFromAssemblyContaining<CreateOrderValidator>();
// Manual validation in minimal API
app.MapPost("/orders", async (
CreateOrderRequest req,
IValidator<CreateOrderRequest> validator) =>
{
var result = await validator.ValidateAsync(req);
if (!result.IsValid)
return Results.ValidationProblem(result.ToDictionary());
// ...
});Why
FluentValidation uses a fluent DSL to compose rules. Rules are testable independently with TestValidate(). Validators can have dependencies injected (e.g., database checks for uniqueness).
Gotchas
- Automatic validation with MVC requires AddFluentValidationAutoValidation() — not enabled by default
- Minimal APIs don't integrate automatically — call validator explicitly or use an IEndpointFilter
- When is chained with Then by default — use Otherwise() or separate rules for or-logic
Revisions (0)
No revisions yet.