PouryaKit
Guides

Validate a command or query

Check a command's or query's input in a FluentValidation validator beside it, and get every failure back as Errors before the handler runs.

What you build

One validator per command or query that needs input checks: an internal FluentValidation AbstractValidator<T> beside it in the Feature. Every failure of every validator comes back as one failed Result before the handler runs, each an Error of kind validation with its code and the property it names as its target. Nothing throws.

Shape here, business rules in the aggregate

A validator checks the shape of the input: present, long enough, in range. A rule that depends on state, such as a name already in use or a sale that has ended, belongs in the aggregate, through CheckRule, or in a unique index. See Business rules.

When to use it

For any command or query whose input can be wrong. A command or query with no validator has nothing checked in that step.

Steps

Write the validator beside the command

Check each member the handler relies on. A typed ID's constructor throws on an empty Guid, so check an id before the handler wraps it.

CreateProductCommandValidator.cs
internal sealed class CreateProductCommandValidator : AbstractValidator<CreateProductCommand>
{
    public CreateProductCommandValidator()
    {
        RuleFor(command => command.ProductId).NotEmpty();
        RuleFor(command => command.CategoryId).NotEmpty();
        RuleFor(command => command.Name).NotEmpty().MaximumLength(200);
    }
}

Give a failure your own code where a caller needs one

FluentValidation's error code becomes the Error's code. Set your own with WithErrorCode; Validation names the code a failure without one gets.

Validate a Page request the same way

A query that reads a Page checks its request against its declaration with AllowedBy, in the same validator step:

ListProductsQueryValidator.cs
internal sealed class ListProductsQueryValidator : AbstractValidator<ListProductsQuery>
{
    public ListProductsQueryValidator() =>
        RuleFor(query => query.Page)
            .Cascade(CascadeMode.Stop)
            .NotNull()
            .AllowedBy(ListProductsQuery.Paging);
}

Register the Module's validators

The Module's Composition Root calls AddPouryaKitValidation(), which finds every concrete validator in the Module's assembly. An abstract base validator is skipped on purpose, and so is an open generic one: derive a concrete validator from it.

What the Kit guarantees

GuaranteeStated in
Every failure as one failed Result, with its code and targetValidation
A validator only readsCode that only reads
What a failed validation answers over HTTPProblem responses

Failure messages

Next steps

On this page