AdaskoTheBeAsT.FluentValidation.MediatR
14.0.0
dotnet add package AdaskoTheBeAsT.FluentValidation.MediatR --version 14.0.0
NuGet\Install-Package AdaskoTheBeAsT.FluentValidation.MediatR -Version 14.0.0
<PackageReference Include="AdaskoTheBeAsT.FluentValidation.MediatR" Version="14.0.0" />
<PackageVersion Include="AdaskoTheBeAsT.FluentValidation.MediatR" Version="14.0.0" />
<PackageReference Include="AdaskoTheBeAsT.FluentValidation.MediatR" />
paket add AdaskoTheBeAsT.FluentValidation.MediatR --version 14.0.0
#r "nuget: AdaskoTheBeAsT.FluentValidation.MediatR, 14.0.0"
#:package AdaskoTheBeAsT.FluentValidation.MediatR@14.0.0
#addin nuget:?package=AdaskoTheBeAsT.FluentValidation.MediatR&version=14.0.0
#tool nuget:?package=AdaskoTheBeAsT.FluentValidation.MediatR&version=14.0.0
AdaskoTheBeAsT.FluentValidation.MediatR
Seamless FluentValidation integration for MediatR pipeline - automatic request validation before your handlers execute.
๐ Why This Library?
Stop writing validation code in every MediatR handler! This library automatically validates all your MediatR requests using FluentValidation before they reach your handlers.
Benefits:
- โ Automatic validation - Set it up once, validate everywhere
- โ Clean handlers - Keep business logic separate from validation
- โ Fail fast - Invalid requests never reach your handlers
- โ DRY principle - No boilerplate validation code in handlers
- โ Streaming support - Works with both regular and streaming MediatR requests
- โ Flexible - Single validator or multiple validators per request
๐ฆ Installation
dotnet add package AdaskoTheBeAsT.FluentValidation.MediatR
Supported frameworks:
- .NET 10.0
- .NET 9.0
- .NET 8.0
Dependencies:
- MediatR 14.2.0+
- FluentValidation 12.1.0+
โก Quick Start
Step 1: Create a Request and Validator
using FluentValidation;
using MediatR;
// Your MediatR request
public class CreateUserCommand : IRequest<UserResponse>
{
public string Username { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
// Your FluentValidation validator
public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
public CreateUserCommandValidator()
{
RuleFor(x => x.Username)
.NotEmpty()
.MinimumLength(3)
.MaximumLength(50);
RuleFor(x => x.Email)
.NotEmpty()
.EmailAddress();
RuleFor(x => x.Age)
.GreaterThanOrEqualTo(18)
.WithMessage("User must be at least 18 years old");
}
}
Step 2: Register with SimpleInjector
using AdaskoTheBeAsT.FluentValidation.MediatR;
using AdaskoTheBeAsT.FluentValidation.SimpleInjector;
using AdaskoTheBeAsT.MediatR.SimpleInjector;
using SimpleInjector;
var container = new Container();
var assemblies = new[] { typeof(Program).Assembly };
// Register FluentValidation validators
container.AddFluentValidation(cfg =>
{
cfg.WithAssembliesToScan(assemblies);
cfg.AsScoped();
cfg.RegisterAsSingleValidator(); // Default: one validator per request type
});
// Register MediatR with validation pipeline
container.AddMediatR(cfg =>
{
cfg.WithAssembliesToScan(assemblies);
cfg.UsingBuiltinPipelineProcessorBehaviors(true);
cfg.UsingPipelineProcessorBehaviors(typeof(FluentValidationPipelineBehavior<,>));
cfg.UsingStreamPipelineBehaviors(typeof(FluentValidationStreamPipelineBehavior<,>));
});
Step 3: Use It!
var mediator = container.GetInstance<IMediator>();
var command = new CreateUserCommand
{
Username = "john_doe",
Email = "john@example.com",
Age = 25
};
try
{
// Validation happens automatically before handler execution
var response = await mediator.Send(command);
Console.WriteLine($"User created: {response.Id}");
}
catch (ValidationException ex)
{
// Handle validation failures
foreach (var error in ex.Errors)
{
Console.WriteLine($"{error.PropertyName}: {error.ErrorMessage}");
}
}
๐ Detailed Usage
Approach 1: Single Validator (Recommended)
Use when: You have one validator per request type, or you want to combine multiple validators into one.
container.AddFluentValidation(cfg =>
{
cfg.WithAssembliesToScan(assemblies);
cfg.AsScoped();
cfg.RegisterAsSingleValidator(); // Default - can be omitted
});
container.AddMediatR(cfg =>
{
cfg.WithAssembliesToScan(assemblies);
cfg.UsingBuiltinPipelineProcessorBehaviors(true);
cfg.UsingPipelineProcessorBehaviors(typeof(FluentValidationPipelineBehavior<,>));
cfg.UsingStreamPipelineBehaviors(typeof(FluentValidationStreamPipelineBehavior<,>));
});
Combining multiple validators:
If you need multiple validation rule sets, create a composite validator:
public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
public CreateUserCommandValidator()
{
// Include rules from other validators
Include(new UserNameValidationRules());
Include(new EmailValidationRules());
Include(new AgeValidationRules());
}
}
// Mark sub-validators to prevent duplicate registration
[SkipValidatorRegistration]
public class UserNameValidationRules : AbstractValidator<CreateUserCommand>
{
public UserNameValidationRules()
{
RuleFor(x => x.Username).NotEmpty().MinimumLength(3);
}
}
[SkipValidatorRegistration]
public class EmailValidationRules : AbstractValidator<CreateUserCommand>
{
public EmailValidationRules()
{
RuleFor(x => x.Email).NotEmpty().EmailAddress();
}
}
๐ Learn more about including rules
Approach 2: Multiple Validators (Collection)
Use when: You want to maintain separate, independent validators for the same request type.
container.AddFluentValidation(cfg =>
{
cfg.WithAssembliesToScan(assemblies);
cfg.AsScoped();
cfg.RegisterAsValidatorCollection(); // Register multiple validators
});
container.AddMediatR(cfg =>
{
cfg.WithAssembliesToScan(assemblies);
cfg.UsingBuiltinPipelineProcessorBehaviors(true);
cfg.UsingPipelineProcessorBehaviors(typeof(FluentValidationCollectionPipelineBehavior<,>));
cfg.UsingStreamPipelineBehaviors(typeof(FluentValidationCollectionStreamPipelineBehavior<,>));
});
Example with multiple independent validators:
// Validator 1: Business rules
public class CreateUserBusinessRulesValidator : AbstractValidator<CreateUserCommand>
{
public CreateUserBusinessRulesValidator()
{
RuleFor(x => x.Age).GreaterThanOrEqualTo(18);
}
}
// Validator 2: Data format rules
public class CreateUserDataValidator : AbstractValidator<CreateUserCommand>
{
public CreateUserDataValidator()
{
RuleFor(x => x.Username).NotEmpty().Matches("^[a-zA-Z0-9_]+$");
RuleFor(x => x.Email).EmailAddress();
}
}
// Both validators run, all errors from both are collected
โ ๏ธ Note: All validators run in parallel, and all validation errors from all validators are collected and thrown together.
Streaming Requests
Works seamlessly with MediatR streaming requests:
public class StreamDataQuery : IStreamRequest<DataChunk>
{
public string Filter { get; set; }
public int PageSize { get; set; }
}
public class StreamDataQueryValidator : AbstractValidator<StreamDataQuery>
{
public StreamDataQueryValidator()
{
RuleFor(x => x.Filter).NotEmpty();
RuleFor(x => x.PageSize).InclusiveBetween(1, 100);
}
}
// Validator runs BEFORE streaming starts
await foreach (var chunk in mediator.CreateStream(new StreamDataQuery { Filter = "test", PageSize = 50 }))
{
Console.WriteLine(chunk);
}
๐ง Configuration Options
Validator Lifetime
container.AddFluentValidation(cfg =>
{
cfg.WithAssembliesToScan(assemblies);
cfg.AsScoped(); // Scoped (default, recommended)
// cfg.AsSingleton(); // Singleton (if validators are stateless)
// cfg.AsTransient(); // Transient (new instance each time)
});
Assembly Scanning
// Scan multiple assemblies
var assemblies = new[]
{
typeof(Program).Assembly,
typeof(CreateUserCommand).Assembly,
typeof(OrderModule).Assembly
};
container.AddFluentValidation(cfg =>
{
cfg.WithAssembliesToScan(assemblies);
});
๐ฏ When Validation Occurs
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 1. Client sends request โ
โ mediator.Send(command) โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 2. MediatR Pipeline: Validation Behavior โ
โ โ FluentValidationPipelineBehavior runs โ
โ โ All validators execute โ
โ โ Errors collected โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโดโโโโโโโโโโ
โ โ
Invalid Valid
โ โ
โผ โผ
โโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ValidationEx- โ โ 3. Your Handler Executes โ
โ ception thrown โ โ Handler<TRequest, TResponse>โ
โ โ โ โ Request is guaranteed โ
โ Handler never โ โ to be valid โ
โ executes โ โ โ
โโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ Troubleshooting
Validation Not Running
Problem: Validators are not executing.
Solutions:
- โ Ensure validators are in scanned assemblies
- โ
Verify pipeline behavior is registered:
UsingPipelineProcessorBehaviors(typeof(FluentValidationPipelineBehavior<,>)) - โ
Check validator implements
AbstractValidator<TRequest>orIValidator<TRequest> - โ
Ensure validator is not decorated with
[SkipValidatorRegistration]
No Validators Found
// โ Wrong - assembly not included
cfg.WithAssembliesToScan(Array.Empty<Assembly>());
// โ
Correct - include assemblies with validators
cfg.WithAssembliesToScan(new[] { typeof(CreateUserCommandValidator).Assembly });
ValidationException Not Caught
using FluentValidation; // โ
Make sure to use this namespace
try
{
await mediator.Send(command);
}
catch (ValidationException ex) // FluentValidation.ValidationException
{
foreach (var error in ex.Errors)
{
Console.WriteLine($"{error.PropertyName}: {error.ErrorMessage}");
}
}
Multiple Validators Only Showing Some Errors
If using collection approach, ensure you're using the Collection pipeline behaviors:
// โ Wrong - only uses first validator
cfg.UsingPipelineProcessorBehaviors(typeof(FluentValidationPipelineBehavior<,>));
// โ
Correct - uses all validators
cfg.UsingPipelineProcessorBehaviors(typeof(FluentValidationCollectionPipelineBehavior<,>));
๐๏ธ Architecture
MediatR Request โ Validation Pipeline Behavior โ Handler
โ
IValidator<TRequest>
โ
Validation passes?
โ โ
Yes No
โ โ
Call next() Throw ValidationException
โ
Handler executes
๐ค Related Packages
This library works great with:
- AdaskoTheBeAsT.FluentValidation.SimpleInjector - FluentValidation registration for SimpleInjector
- AdaskoTheBeAsT.MediatR.SimpleInjector - MediatR registration for SimpleInjector
๐ Best Practices
- Use Single Validator approach when possible - simpler and more maintainable
- Fail fast - Put basic validation rules (NotEmpty, format checks) first
- Keep validators focused - One validator per request, or use
Include()to compose - Use meaningful error messages - Help your API consumers understand what went wrong
- Register validators as Scoped - Allows injecting scoped dependencies (e.g., DbContext)
- Test validators independently - Unit test validators separately from handlers
// Example: Testing validators
[Fact]
public void Should_Have_Error_When_Username_Is_Empty()
{
var validator = new CreateUserCommandValidator();
var command = new CreateUserCommand { Username = "" };
var result = validator.Validate(command);
Assert.False(result.IsValid);
Assert.Contains(result.Errors, e => e.PropertyName == nameof(CreateUserCommand.Username));
}
๐งช Running Tests Locally
dotnet test
The test suite needs a MediatR license key. MediatR resolves it from the environment
(MEDIATR_LICENSE_KEY, then the shared LUCKYPENNY_LICENSE_KEY) unless a key is passed explicitly
via WithLicenseKey(...). In CI the value comes from the MEDIATR_LICENSE_KEY GitHub secret
(see .github/workflows/ci.yml).
To supply it locally, copy the template and fill in your key:
cp .env.example .env
MEDIATR_LICENSE_KEY=your-license-key
.env is git ignored, so the key never lands in the repository. A module initializer in the test
project (TestEnvironment) uses dotenv.net to probe
upwards from the test output directory for the nearest .env and exports every KEY=VALUE entry
into the test process before the first mediator is resolved. Variables that are already set in the
environment are never overwritten, so CI values always win over the file.
This works the same for dotnet test and for the test runners in Visual Studio, Rider, and VS Code.
If you prefer not to keep a file in the working tree, set a user-level environment variable instead
(setx MEDIATR_LICENSE_KEY "your-license-key" on Windows) and restart your IDE or shell.
๐ค Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
๐ค Author
Adam "AdaskoTheBeAsT" Pluciลski
- GitHub: @AdaskoTheBeAsT
- NuGet: AdaskoTheBeAsT packages
โญ Show Your Support
If this library helps you, please give it a โญ on GitHub!
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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. |
-
net10.0
- FluentValidation (>= 12.1.1 && < 13.0.0)
- MediatR (>= 14.2.0 && < 15.0.0)
-
net8.0
- FluentValidation (>= 12.1.1 && < 13.0.0)
- MediatR (>= 14.2.0 && < 15.0.0)
-
net9.0
- FluentValidation (>= 12.1.1 && < 13.0.0)
- MediatR (>= 14.2.0 && < 15.0.0)
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 |
|---|---|---|
| 14.0.0 | 108 | 8/2/2026 |
| 13.0.0 | 455 | 11/12/2025 |
| 12.0.0 | 492 | 1/4/2025 |
| 11.2.0 | 448 | 8/18/2024 |
| 11.0.2 | 324 | 5/27/2024 |
| 11.0.1 | 397 | 1/27/2024 |
| 11.0.0 | 350 | 12/1/2023 |
| 10.2.0 | 381 | 7/16/2023 |
| 10.1.0 | 367 | 5/4/2023 |
| 10.0.0 | 447 | 2/17/2023 |
| 9.1.0 | 516 | 1/22/2023 |
| 9.0.0 | 602 | 11/13/2022 |
| 8.0.0 | 610 | 10/17/2022 |
| 7.2.1 | 664 | 9/25/2022 |
| 7.2.0 | 641 | 9/7/2022 |
| 7.1.0 | 668 | 7/5/2022 |
| 7.0.0 | 674 | 5/14/2022 |
| 6.0.0 | 559 | 1/11/2022 |
| 5.3.3 | 509 | 1/2/2022 |
| 5.3.2 | 604 | 10/30/2021 |
- MediatR 14.2.0
- FluentValidation 12.1.1