C# FluentValidation
last modified October 15, 2026
This article is a short introduction to data validation in C# using the FluentValidation library.
FluentValidation is a validation library for .NET. It uses a fluent
interface and lambda expressions for building strongly-typed validation rules.
$ dotnet add package FluentValidation
We add the package to the project. The examples were tested with FluentValidation 12.
C# FluentValidation simple example
The following is a simple example that uses FluentValidation.
using FluentValidation;
var validator = new UserValidator();
var u1 = new User("John Doe", "gardener");
var u2 = new User("Roger Roe", "");
var res = validator.Validate(u1);
if (res.IsValid)
{
Console.WriteLine("instance is valid");
}
else
{
Console.WriteLine("instance not valid");
}
var res2 = validator.Validate(u2);
if (res2.IsValid)
{
Console.WriteLine("instance is valid");
}
else
{
Console.WriteLine("instance not valid");
}
class User
{
public string Name;
public string Occupation;
public User(string Name, string Occupation) =>
(this.Name, this.Occupation) = (Name, Occupation);
public override string ToString() =>
$"User {{ {this.Name} {this.Occupation} }}";
}
class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(user => user.Name).NotEmpty();
RuleFor(user => user.Occupation).NotEmpty();
}
}
In the program, we have a User class. With our validation code
we ensure that the user properties are not empty.
class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(user => user.Name).NotEmpty();
RuleFor(user => user.Occupation).NotEmpty();
}
}
A validator class inherits from AbstractValidator<T>. It defines
rules for the members of the validated type with RuleFor. In our
case, we use the built-in NotEmpty rule.
var validator = new UserValidator();
We create an instance of the UserValidator.
var u1 = new User("John Doe", "gardener");
var u2 = new User("Roger Roe", "");
We create two user objects.
if (res.IsValid)
{
Console.WriteLine("instance is valid");
}
else
{
Console.WriteLine("instance not valid");
}
The Validate method returns a ValidationResult. We
check the validity of an object with its IsValid property.
$ dotnet run instance is valid instance not valid
C# FluentValidation errors
The validation result contains the Errors property which contains
the validation error messages.
using FluentValidation;
var validator = new UserValidator();
var u1 = new User("John Doe", "gardener");
var u2 = new User("Roger Roe", "");
var res = validator.Validate(u1);
if (res.IsValid)
{
Console.WriteLine("instance is valid");
}
else
{
Console.WriteLine("instance not valid");
ShowErrors(res);
}
var res2 = validator.Validate(u2);
if (res2.IsValid)
{
Console.WriteLine("instance is valid");
}
else
{
Console.WriteLine("instance not valid");
ShowErrors(res2);
}
void ShowErrors(FluentValidation.Results.ValidationResult res)
{
foreach (var e in res.Errors)
{
Console.WriteLine(e);
}
}
class User
{
public string Name;
public string Occupation;
public User(string Name, string Occupation) =>
(this.Name, this.Occupation) = (Name, Occupation);
public override string ToString() =>
$"User {{ {this.Name} {this.Occupation} }}";
}
class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(user => user.Name).NotEmpty();
RuleFor(user => user.Occupation).NotEmpty();
}
}
In this program, we also display the error messages.
void ShowErrors(FluentValidation.Results.ValidationResult res)
{
foreach (var e in res.Errors)
{
Console.WriteLine(e);
}
}
We define the ShowErrors function which goes over the
Errors property and displays the errors to the console.
$ dotnet run instance is valid instance not valid 'Occupation' must not be empty.
C# FluentValidation custom error messages
Custom error messages can be provided with WithMessage.
using FluentValidation;
var validator = new UserValidator();
var u = new User("Roger Roe", "");
var res = validator.Validate(u);
if (res.IsValid)
{
Console.WriteLine("instance is valid");
}
else
{
Console.WriteLine("instance not valid");
ShowErrors(res);
}
void ShowErrors(FluentValidation.Results.ValidationResult res)
{
foreach (var e in res.Errors)
{
Console.WriteLine(e);
}
}
class User
{
public string Name;
public string Occupation;
public User(string Name, string Occupation) =>
(this.Name, this.Occupation) = (Name, Occupation);
public override string ToString() =>
$"User {{ {this.Name} {this.Occupation} }}";
}
class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(user => user.Name).NotEmpty()
.WithMessage("Validation failed: Name field must not be empty");
RuleFor(user => user.Occupation).NotEmpty()
.WithMessage("Validation failed: Occupation field must not be empty");
}
}
In the program, we add custom error messages.
RuleFor(user => user.Name).NotEmpty()
.WithMessage("Validation failed: Name field must not be empty");
RuleFor(user => user.Occupation).NotEmpty()
.WithMessage("Validation failed: Occupation field must not be empty");
Right after the NotEmpty rule, we call the WithMessage
method with the custom error message.
$ dotnet run instance not valid Validation failed: Occupation field must not be empty
C# FluentValidation chaining rules
The validation rules can be chained.
using FluentValidation;
var validator = new UserValidator();
var u1 = new User();
ValidateUser(u1);
Console.WriteLine("--------------------");
var u2 = new User();
u2.Name = "John Doe";
ValidateUser(u2);
Console.WriteLine("--------------------");
var u3 = new User();
u3.Name = "John Doe";
u3.Occupation = "gar";
ValidateUser(u3);
Console.WriteLine("--------------------");
var u4 = new User();
u4.Name = "John Doe";
u4.Occupation = "gardener";
ValidateUser(u4);
void ValidateUser(User u)
{
var res = validator.Validate(u);
if (res.IsValid)
{
Console.WriteLine("instance is valid");
}
else
{
Console.WriteLine("instance not valid");
ShowErrors(res);
}
}
void ShowErrors(FluentValidation.Results.ValidationResult res)
{
foreach (var e in res.Errors)
{
Console.WriteLine(e);
}
}
class User
{
public string? Name { get; set; }
public string? Occupation { get; set; }
public override string ToString() =>
$"User {{ {this.Name} {this.Occupation} }}";
}
class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(user => user.Name).NotEmpty().MinimumLength(2);
RuleFor(user => user.Occupation).NotEmpty().MinimumLength(5);
}
}
In the program, we use two rules for our properties: NotEmpty and
MinimumLength.
class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(user => user.Name).NotEmpty().MinimumLength(2);
RuleFor(user => user.Occupation).NotEmpty().MinimumLength(5);
}
}
In our validation class we assume that a name has at least two letters and an occupation five.
$ dotnet run instance not valid 'Name' must not be empty. 'Occupation' must not be empty. -------------------- instance not valid 'Occupation' must not be empty. -------------------- instance not valid The length of 'Occupation' must be at least 5 characters. You entered 3 characters. -------------------- instance is valid
C# FluentValidation built-in and custom validators
FluentValidation contains many built-in validators, such as
EmailAddress, InclusiveBetween, or
MaximumLength. Custom rules can be defined with the
Must method.
using FluentValidation;
var validator = new CustomerValidator();
var c1 = new Customer("John Doe", "john.doe@example.com", 34, "SK");
var c2 = new Customer("Roger Roe", "roger.roe", 15, "XX");
Validate(c1);
Validate(c2);
void Validate(Customer c)
{
var res = validator.Validate(c);
Console.WriteLine($"{c.Name}: {(res.IsValid ? "valid" : "not valid")}");
foreach (var e in res.Errors)
{
Console.WriteLine($" {e.PropertyName}: {e.ErrorMessage}");
}
}
record Customer(string Name, string Email, int Age, string Country);
class CustomerValidator : AbstractValidator<Customer>
{
private static readonly string[] countries = ["SK", "CZ", "HU", "PL", "AT"];
public CustomerValidator()
{
RuleFor(c => c.Name).NotEmpty().MaximumLength(50);
RuleFor(c => c.Email).EmailAddress();
RuleFor(c => c.Age).InclusiveBetween(18, 120);
RuleFor(c => c.Country).Must(c => countries.Contains(c))
.WithMessage("'{PropertyName}' must be one of: " + string.Join(", ", countries));
}
}
We validate a Customer record. Validators work with records the
same way as with classes.
RuleFor(c => c.Country).Must(c => countries.Contains(c))
.WithMessage("'{PropertyName}' must be one of: " + string.Join(", ", countries));
The Must method takes a predicate; the validation fails if the
predicate returns false. The {PropertyName} placeholder in the
message is replaced with the name of the property.
Console.WriteLine($" {e.PropertyName}: {e.ErrorMessage}");
Each error contains the name of the property and the error message.
$ dotnet run John Doe: valid Roger Roe: not valid Email: 'Email' is not a valid email address. Age: 'Age' must be between 18 and 120. You entered 15. Country: 'Country' must be one of: SK, CZ, HU, PL, AT
C# FluentValidation ValidateAndThrow
The ValidateAndThrow method throws a
ValidationException if the validation fails.
using FluentValidation;
var validator = new UserValidator();
var u = new User("Roger Roe", "");
try
{
validator.ValidateAndThrow(u);
}
catch (ValidationException e)
{
foreach (var err in e.Errors)
{
Console.WriteLine(err.ErrorMessage);
}
}
record User(string Name, string Occupation);
class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(user => user.Name).NotEmpty();
RuleFor(user => user.Occupation).NotEmpty();
}
}
The Errors property of the exception contains the validation
errors.
$ dotnet run 'Occupation' must not be empty.
Source
FluentValidation documentation
This article was a quick introduction to the C# FluentValidation library.
Author
List all C# tutorials.