Validators
Validators use FluentValidation, are grouped by module namespace, and are registered by the generator.
Validators use FluentValidation and are grouped by namespace under their module. Place a validator beside the handler it validates and the generator registers it.
using FluentValidation;
namespace MyApp.Application.Modules.Clients.Handlers.CreateClient;
public sealed class CreateClientValidator : AbstractValidator<CreateClient.Command> {
public CreateClientValidator() {
RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
}
}When [assembly: UseElarion] or [assembly: GenerateModuleValidators] is present, Elarion emits
Add{ModuleName}Validators() into the module namespace, which the module's ConfigureServices
calls.
How validators run
Registration alone does not enforce validation. Validators are invoked by a validation decorator
that you add to a pipeline. The decorator resolves all
IValidator<TRequest> instances for a request, runs them, and converts any failures into a
Result<T>.Failure(AppError.Validation(...)) before the handler executes:
public sealed class ValidationDecorator<TRequest, TResponse>(
IHandler<TRequest, TResponse> inner,
IEnumerable<IValidator<TRequest>> validators
) : IHandler<TRequest, TResponse> {
public async ValueTask<TResponse> HandleAsync(TRequest request, CancellationToken ct) {
var failures = new List<string>();
foreach (var validator in validators) {
var result = await validator.ValidateAsync(request, ct);
failures.AddRange(result.Errors.Select(e => e.ErrorMessage));
}
if (failures.Count == 0) {
return await inner.HandleAsync(request, ct);
}
return ResultFactory.Failure<TResponse>(
AppError.Validation(string.Join("; ", failures), failures));
}
}This keeps validation declarative (rules next to the command) and composable (one decorator applies
it across every handler in a pipeline). The structured AppError.Validation(message, errors)
overload carries the individual messages for the transport to surface.
Conventions
- The validator inherits
AbstractValidator<T>whereTis the request type. - It lives under the module namespace so the module owns its registration.
If a validator is outside the module namespace or does not inherit AbstractValidator<T>, it is not
registered. See Troubleshooting.