Earn More by Sharing What You Love
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!

Tim Corey
43m 58s
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.
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.
Tim uses a WinForms demo application where a user can input:
Although it's a UI demo, the validation principles apply equally well to ASP.NET Core, API testing, and even console applications.
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:
Tim explores options for configuring validators:
He notes that data annotations are limited and often not suitable when working with external libraries or when needing more custom validation logic.
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:
Tim's setup also works for those who need support for older runtimes, including FluentValidation 11, which supports .NET Core 3.1 and earlier.
Tim demonstrates building strongly typed validation rules by creating a new class:
public class PersonValidator : AbstractValidator<Person>Public Class PersonValidator
Inherits AbstractValidator(Of Person)This class contains all the validation logic for the Person model. Using the fluent interface, validation rules are defined inside the constructor.
Tim writes a rule using a lambda expression:
RuleFor(p => p.FirstName).NotEmpty();RuleFor(Function(p) p.FirstName).NotEmpty()He uses a var validator to validate the Person object:
var validator = new PersonValidator();
ValidationResult results = validator.Validate(person);Dim validator As New PersonValidator()
Dim results As ValidationResult = validator.Validate(person)He then loops through any validation failures to display user-friendly messages in a list box.
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(Function(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.
Tim implements a custom validator using a method called:
private bool BeAValidName(string name)Private Function BeAValidName(name As String) As BooleanThis 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 Function BeAValidPostcode(postcode As String) As Boolean
' Add custom logic here to specify a valid postcode format
End FunctionYou could then use it in a validator like:
RuleFor(c => c.Postcode).Must(BeAValidPostcode)
.WithMessage("Please specify a valid postcode");RuleFor(Function(c) c.Postcode).Must(AddressOf BeAValidPostcode) _
.WithMessage("Please specify a valid postcode")This is common in commercial projects requiring public class CustomerValidator or other domain-specific validators.
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.
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.
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.
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 Function BeAValidAge(dob As DateTime) As Boolean
Dim currentYear = DateTime.Now.Year
Dim dobYear = dob.Year
Return dobYear <= currentYear AndAlso dobYear > (currentYear - 120)
End FunctionUsed like this:
RuleFor(p => p.DateOfBirth)
.Must(BeAValidAge)
.WithMessage("Invalid {PropertyName}");RuleFor(Function(p) p.DateOfBirth) _
.Must(AddressOf BeAValidAge) _
.WithMessage("Invalid {PropertyName}")This is a good pattern for validating temporal data like dates or expiration windows.
Tim concludes by summarizing key advantages of FluentValidation:
He encourages viewers to explore the FluentValidation documentation for advanced usage, including nested rules, email property validation, and more.
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:
FluentValidation is a must-have in your .NET toolkit. For more details please watch the full video and subscribe to 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.
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!
Join our newsletter, you’ll get exclusive access on article updates. We value your privacy