NetEvolve.Pulse.FluentValidation automatically validates commands and queries before they reach their handlers using FluentValidation validators registered in the DI container — centralizing validation at the pipeline boundary without duplicating logic inside individual handlers.
- Automatic validation: Resolves all
IValidator<TRequest>instances and executes them before the handler runs. - Failure aggregation: Collects failures from all validators and throws a single
ValidationExceptionif any exist. - Pass-through when no validators: Requests with no registered validators flow to the handler unchanged — no errors, no configuration needed.
- Multiple validators supported: All registered validators for a given request type are executed and their failures are merged.
- Idempotent registration: Calling
AddFluentValidation()multiple times registers the interceptor only once. - Scoped lifetime: The interceptor resolves validators from the current DI scope on every invocation.
Install-Package NetEvolve.Pulse.FluentValidationdotnet add package NetEvolve.Pulse.FluentValidation<PackageReference Include="NetEvolve.Pulse.FluentValidation" Version="x.x.x" />Register the interceptor at startup:
services.AddPulse(c => c.AddFluentValidation());Register your FluentValidation validators:
// Register individually
services.AddScoped<IValidator<CreateOrderCommand>, CreateOrderCommandValidator>();
// Or use FluentValidation's assembly scanning
services.AddValidatorsFromAssembly(typeof(CreateOrderCommand).Assembly);using FluentValidation;
using NetEvolve.Pulse.Extensibility;
public record CreateOrderCommand(string ProductId, int Quantity) : ICommand<OrderResult>
{
public string? CorrelationId { get; set; }
}
public class CreateOrderCommandValidator : AbstractValidator<CreateOrderCommand>
{
public CreateOrderCommandValidator()
{
RuleFor(x => x.ProductId).NotEmpty().WithMessage("ProductId must not be empty.");
RuleFor(x => x.Quantity).GreaterThan(0).WithMessage("Quantity must be greater than zero.");
}
}When CreateOrderCommand is dispatched with an empty ProductId or a non-positive Quantity, a ValidationException is thrown before the handler executes.
public record GetOrderQuery(string OrderId) : IQuery<OrderResult>
{
public string? CorrelationId { get; set; }
}
public class GetOrderQueryValidator : AbstractValidator<GetOrderQuery>
{
public GetOrderQueryValidator() =>
RuleFor(x => x.OrderId).NotEmpty().WithMessage("OrderId must not be empty.");
}When multiple IValidator<TRequest> implementations are registered for the same request type, all are executed and their failures are aggregated into one ValidationException:
services.AddScoped<IValidator<CreateOrderCommand>, CreateOrderCommandValidator>();
services.AddScoped<IValidator<CreateOrderCommand>, CreateOrderBusinessRulesValidator>();| Scenario | Behavior |
|---|---|
| No validators registered | Request passes through to the handler unchanged |
| Validators registered, valid input | Request passes through to the handler |
| Validators registered, invalid input | ValidationException thrown before the handler executes |
| Multiple validators, one or more fail | All validators run; all failures are aggregated into one ValidationException |
- .NET 8.0, .NET 9.0, or .NET 10.0
FluentValidation12.0 or laterNetEvolve.Pulse.ExtensibilityforIRequestInterceptor<,>andIMediatorBuilder
Contributions are welcome! Please read the Contributing Guidelines before submitting a pull request.
- Issues: Report bugs or request features on GitHub Issues
- Documentation: Read the full documentation at https://github.com/dailydevops/pulse
This project is licensed under the MIT License — see the LICENSE file for details.
- NetEvolve.Pulse - Core CQRS mediator
- NetEvolve.Pulse.Polly - Polly v8 resilience interceptors
- NetEvolve.Pulse.HttpCorrelation - HTTP correlation ID propagation
- NetEvolve.Pulse.Extensibility - Extensibility contracts
- NetEvolve.Pulse.EntityFramework - Entity Framework Core outbox persistence
- NetEvolve.Pulse.SqlServer - SQL Server ADO.NET outbox persistence
Note
Made with ❤️ by the NetEvolve Team