Fluent Validation in C# - The Powerful Yet Easy Data Validation Tool
Data validation is one of the pillars of reliable software development, and in modern .NET applications, you need a robust, maintainable, and scalable way to handle it. That's where FluentValidation, a popular .NET library for building strongly typed validation rules, comes in.
In his in-depth video tutorial “Fluent Validation in C# – The Powerful Yet Easy Data Validation Tool,” Tim Corey takes viewers through the process of using FluentValidation step by step. In this article, we’ll follow Tim’s walkthrough, summarizing key points and code examples while integrating relevant concepts like custom validators, chaining validators, ASP.NET integration, and support for older runtimes such as .NET Core 3.1 and .NET Standard 2.0.
Introduction: Why FluentValidation?
Tim opens the video by explaining how data validation often becomes repetitive and messy. For instance, copying and pasting similar validation rules across different parts of a project violates the DRY (Don't Repeat Yourself) principle. Instead, he introduces FluentValidation—a .NET validation library that is free, powerful, and works even on models you don’t own, making it ideal for commercial projects.
Tim emphasizes the importance of practicing what you learn and points viewers to his Weekly Challenge series to build skills.
Demo App Overview
Tim uses a WinForms demo application where a user can input:
First Name
Last Name
Account Balance
- Date of Birth
Although it's a UI demo, the validation principles apply equally well to ASP.NET Core, API testing, and even console applications.
The Danger of Trusting User Input
At this point, Tim reminds developers: "Never trust the user." Input can often be unpredictable, such as typing "ten" for age instead of 10. Validating that input is essential before saving to the database.
He outlines sample validation rules:
First and last names should not be empty.
Account balance should follow financial rules, such as a minimum requirement for financial sponsorship.
- Date of birth should not be in the future or over 120 years old.
Where Should Validation Logic Go?
Tim explores options for configuring validators:
Inside the UI form
In the model class using data annotations
- In a separate validation class using FluentValidation
He notes that data annotations are limited and often not suitable when working with external libraries or when needing more custom validation logic.
Installing FluentValidation
Using Visual Studio, Tim adds FluentValidation to his project via NuGet. He installs version 8.1.0 but notes that FluentValidation is cross-platform and compatible with:
.NET Standard 2.0
.NET Core
ASP.NET
WPF
Xamarin
- And more
Tim’s setup also works for those who need support for older runtimes, including FluentValidation 11, which supports .NET Core 3.1 and earlier.
Creating a Validator Class
Tim demonstrates building strongly typed validation rules by creating a new class:
public class PersonValidator : AbstractValidator<Person>
public class PersonValidator : AbstractValidator<Person>
This class contains all the validation logic for the Person model. Using the fluent interface, validation rules are defined inside the constructor.
First Validation Rule: First Name
Tim writes a rule using a lambda expression:
RuleFor(p => p.FirstName).NotEmpty();
RuleFor(p => p.FirstName).NotEmpty();
He uses a var validator to validate the Person object:
var validator = new PersonValidator();
ValidationResult results = validator.Validate(person);
var validator = new PersonValidator();
ValidationResult results = validator.Validate(person);
He then loops through any validation failures to display user-friendly messages in a list box.
String Length and Custom Messages
Tim expands the validation rule:
RuleFor(p => p.FirstName)
.NotEmpty().WithMessage("First name is empty")
.Length(2, 50).WithMessage("Length of first name is invalid");
RuleFor(p => p.FirstName)
.NotEmpty().WithMessage("First name is empty")
.Length(2, 50).WithMessage("Length of first name is invalid");
Using chaining validators, this rule ensures the name is neither empty nor too short/long. Tim introduces Cascade(CascadeMode.Stop) to stop validation at the first failure.
Custom Validation: Valid Characters in Names
Tim implements a custom validator using a method called:
private bool BeAValidName(string name)
private bool BeAValidName(string name)
This strips out spaces and dashes and ensures the string only contains Unicode letters, enabling support for international characters.
The custom rule is applied like this:
.Must(BeAValidName).WithMessage("{PropertyName} contains invalid characters");
.Must(BeAValidName).WithMessage("{PropertyName} contains invalid characters");
This method structure is perfect for adapting to other fields—such as a custom postcode validating logic function:
private bool BeAValidPostcode(string postcode)
{
// Add custom logic here to specify a valid postcode format
}
private bool BeAValidPostcode(string postcode)
{
// Add custom logic here to specify a valid postcode format
}
You could then use it in a validator like:
RuleFor(c => c.Postcode).Must(BeAValidPostcode)
.WithMessage("Please specify a valid postcode");
RuleFor(c => c.Postcode).Must(BeAValidPostcode)
.WithMessage("Please specify a valid postcode");
This is common in commercial projects requiring public class CustomerValidator or other domain-specific validators.
Using Built-in Variables in Error Messages
Tim shows how to dynamically enhance messages with placeholders like:
{PropertyName}
{TotalLength}
- {MinLength} and {MaxLength}
This results in contextual error messages like:
"Length of First Name is invalid (was 105)"
"Length of First Name is invalid (was 105)"
This makes it easier for users to fix input errors.
Last Name and Localization
Tim copies the FirstName validation logic for LastName, thanks to reusable formatting with {PropertyName}. He also mentions WithLocalizedMessage() for ASP.NET or global applications that need multi-language support.
Important: CascadeMode Is Rule-Specific
Tim clarifies that CascadeMode.Stop applies to individual rules, not globally across the entire model. If both FirstName and LastName are empty, both rules will trigger—even if CascadeMode is set.
Date of Birth Validation
Next, Tim adds a rule to ensure the date of birth is realistic:
private bool BeAValidAge(DateTime dob)
{
var currentYear = DateTime.Now.Year;
var dobYear = dob.Year;
return dobYear <= currentYear && dobYear > (currentYear - 120);
}
private bool BeAValidAge(DateTime dob)
{
var currentYear = DateTime.Now.Year;
var dobYear = dob.Year;
return dobYear <= currentYear && dobYear > (currentYear - 120);
}
Used like this:
RuleFor(p => p.DateOfBirth)
.Must(BeAValidAge)
.WithMessage("Invalid {PropertyName}");
RuleFor(p => p.DateOfBirth)
.Must(BeAValidAge)
.WithMessage("Invalid {PropertyName}");
This is a good pattern for validating temporal data like dates or expiration windows.
Final Thoughts and Recommendations
Tim concludes by summarizing key advantages of FluentValidation:
Centralized validation logic
Easy to create custom validators
Compatible with both .NET 5 and newer and older runtimes
Supports complex models, lists, and async rules
- Ideal for both hobbyists and commercial projects
He encourages viewers to explore the FluentValidation documentation for advanced usage, including nested rules, email property validation, and more.
Conclusion
FluentValidation empowers .NET developers to build strongly typed validation rules that are reusable, expressive, and maintainable. Whether you're developing with .NET Core, .NET 8, or maintaining legacy systems on .NET Core 3.1, this library makes data validation a breeze.
With features like:
The fluent interface for building rules
Support for custom postcode validating logic
Easy integration with Visual Studio
Compatibility with API testing, WinForms, and ASP.NET
- Robust handling of validation failures
FluentValidation is a must-have in your .NET toolkit. For more details please watch the full video and subscribe Tim's Channel for more insightful videos on C#.
Tip: If you're new to using FluentValidation, try implementing your own CustomerValidator with rules for properties like public string Name, string Postcode, and more. Test it with a mock API or UI form to get hands-on experience.