CodeWinden.CQRS.FluentValidation 1.1.0

dotnet add package CodeWinden.CQRS.FluentValidation --version 1.1.0
                    
NuGet\Install-Package CodeWinden.CQRS.FluentValidation -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CodeWinden.CQRS.FluentValidation" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CodeWinden.CQRS.FluentValidation" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="CodeWinden.CQRS.FluentValidation" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CodeWinden.CQRS.FluentValidation --version 1.1.0
                    
#r "nuget: CodeWinden.CQRS.FluentValidation, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CodeWinden.CQRS.FluentValidation@1.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CodeWinden.CQRS.FluentValidation&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=CodeWinden.CQRS.FluentValidation&version=1.1.0
                    
Install as a Cake Tool

CodeWinden.CQRS.FluentValidation

Automatic validation extension for CodeWinden.CQRS using FluentValidation to validate commands and queries before handler execution.

Features

  • Automatic validation - Validates commands and queries before handlers execute
  • Decorator-based - Integrates seamlessly into the CQRS pipeline
  • Zero configuration - Automatic validator discovery and registration
  • Fail-fast behavior - Stops execution immediately on validation failure
  • Supports all patterns - Works with commands and queries with/without return values

Installation

dotnet add package CodeWinden.CQRS.FluentValidation

Quick Start

using CodeWinden.CQRS;
using FluentValidation;
using Microsoft.Extensions.DependencyInjection;

// 1. Register CQRS with FluentValidation
var services = new ServiceCollection();
services.AddCQRS(options => options
    .AddHandlersFromAssemblyContaining<Program>()
    .AddFluentValidation<Program>()  // Scans assembly for validators
);

// 2. Define a command
public record CreateUserCommand : ICommand<int>
{
    public required string Name { get; init; }
    public required string Email { get; init; }
}

// 3. Create a validator
public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
    public CreateUserCommandValidator()
    {
        RuleFor(x => x.Name).NotEmpty().MaximumLength(100);
        RuleFor(x => x.Email).NotEmpty().EmailAddress();
    }
}

// 4. Create the handler
public class CreateUserCommandHandler : ICommandHandler<CreateUserCommand, int>
{
    public async Task<int> Handle(CreateUserCommand command, CancellationToken cancellationToken)
    {
        // Validation runs automatically before this executes
        return await SaveUserToDatabase(command);
    }
}

// 5. Execute - validation happens automatically
var cqrs = serviceProvider.GetRequiredService<ICQRSService>();
try
{
    var userId = await cqrs.ExecuteCommand(
        new CreateUserCommand { Name = "John Doe", Email = "john@example.com" }
    );
}
catch (ValidationException ex)
{
    foreach (var error in ex.Errors)
    {
        Console.WriteLine($"{error.PropertyName}: {error.ErrorMessage}");
    }
}

Usage

Setting Up Validation

Add FluentValidation to your CQRS configuration:

services.AddCQRS(options => options
    .AddHandlersFromAssemblyContaining<Program>()
    .AddFluentValidation<Program>()
);

This automatically registers validation decorators and scans the assembly for all AbstractValidator<T> implementations.

Creating Validators

Create validators using FluentValidation's syntax. See the FluentValidation documentation for complete validation rule options.

Command validator

public class UpdateOrderCommandValidator : AbstractValidator<UpdateOrderCommand>
{
    public UpdateOrderCommandValidator()
    {
        RuleFor(x => x.OrderId).GreaterThan(0);
        RuleFor(x => x.Amount).GreaterThan(0);
        RuleFor(x => x.Status).NotEmpty();
    }
}

Query validator

public class GetOrdersByDateRangeQueryValidator : AbstractValidator<GetOrdersByDateRangeQuery>
{
    public GetOrdersByDateRangeQueryValidator()
    {
        RuleFor(x => x.StartDate).LessThan(x => x.EndDate);
        RuleFor(x => x.PageSize).InclusiveBetween(1, 100);
    }
}

Validator with dependencies

public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
    public CreateUserCommandValidator(IUserRepository repository)
    {
        RuleFor(x => x.Email)
            .NotEmpty()
            .EmailAddress()
            .MustAsync(async (email, ct) => !await repository.EmailExistsAsync(email))
            .WithMessage("Email already in use");
    }
}

Error Handling

FluentValidation throws a ValidationException when validation fails:

try
{
    await cqrs.ExecuteCommand(command);
}
catch (ValidationException ex)
{
    foreach (var error in ex.Errors)
    {
        Console.WriteLine($"{error.PropertyName}: {error.ErrorMessage}");
    }
}

API error response example

[HttpPost("users")]
public async Task<IActionResult> CreateUser([FromBody] CreateUserCommand command)
{
    try
    {
        var userId = await _cqrs.ExecuteCommand(command);
        return Ok(new { UserId = userId });
    }
    catch (ValidationException ex)
    {
        var errors = ex.Errors
            .GroupBy(e => e.PropertyName)
            .ToDictionary(g => g.Key, g => g.Select(e => e.ErrorMessage).ToArray());
        return BadRequest(new { Errors = errors });
    }
}

How It Works

This extension adds validation decorators to the CQRS pipeline that:

  1. Intercept command/query execution before reaching the handler
  2. Run all registered validators for the command/query type
  3. Collect validation errors from all validators
  4. Throw ValidationException if any validation fails
  5. Allow execution to continue to the handler only if validation succeeds

Multiple validators for the same command/query type will all execute, and validation fails if any validator finds errors.

API Reference

Method/Decorator Description
AddFluentValidation<TAssembly>() Adds validation to CQRS pipeline and scans assembly for validators
FluentValidationCommandHandlerDecorator<TCommand> Validates commands without return value
FluentValidationCommandWithResultHandlerDecorator<TCommand, TResult> Validates commands with return value
FluentValidationQueryHandlerDecorator<TQuery, TResult> Validates queries with parameters

Best Practices

Keep validators focused on input validation

// ✅ Validate input format and constraints
RuleFor(x => x.Email).NotEmpty().EmailAddress();
RuleFor(x => x.Price).GreaterThan(0);

// ❌ Don't perform business operations
RuleFor(x => x).Must(x => SaveToDatabase(x));

Use meaningful error messages

RuleFor(x => x.Email)
    .EmailAddress()
    .WithMessage("Please provide a valid email address");

Validators support dependency injection

public class UpdateUserCommandValidator : AbstractValidator<UpdateUserCommand>
{
    public UpdateUserCommandValidator(IUserRepository repository)
    {
        // Use injected dependencies for async validation
    }
}

Note: Validators are registered with Scoped lifetime by default.

Tip: For detailed validator creation, conditional validation, custom validators, and more, see the FluentValidation documentation.

License

MIT License - see LICENSE for details

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 227 12/23/2025
1.0.1 209 12/23/2025
1.0.0 203 12/22/2025