Validation is a crucial aspect of any application, ensuring that data conforms to expected formats and business rules before proceeding further. In my projects, I consistently rely on FluentValidation due to the remarkable convenience it brings. This powerful library provides a clean, fluent API for building validation rules, making it an invaluable tool for efficient, maintainable code.
In this blog post, I’ll share the FluentValidation features I commonly use in my development work. These practical examples can serve as a great starting point for you to explore the FluentValidation API and incorporate it into your own projects.Environment Set up
Environment
To begin, you’ll need to set up your project by creating a Web API and installing the FluentValidation framework. I’m using Visual Studio Code on my Mac, but don’t worry—it works perfectly fine on other operating systems too.
Step 1: Create a New Project
First, run the following commands to create a new project:
mkdir FluentValidationDemo
cd FluentValidationDemo
dotnet new webapi
Once the project setup is complete, open it in Visual Studio Code using:
code .
Your project will now open in Visual Studio Code.
Step 2: Install FluentValidation
Next, install the FluentValidation library. Open the integrated terminal in Visual Studio Code and execute this command:
dotnet add package FluentValidation
After running the command, your terminal should display output confirming the installation of the FluentValidation package.
Ready to Use FluentValidation
With the setup complete, your environment is now ready to use FluentValidation.
FluentValidation: Validators and Rules
FluentValidation has two key components that you need to understand:
- Validator. The Validator is the entry point of FluentValidation. It determines where the validation will be applied, serving as the foundation for setting up validation logic.
- Rules. Rules define the actual validation logic. This is where you specify the conditions and constraints that the data must meet.
Below is the skeletal structure of FluentValidation:
public class AppUserDTO
{
public string Username { get; set; }
}
public class AppUserValidator : AbstractValidator<AppUserDTO>
{
public AppUserValidator()
{
RuleFor(user => user.Username)
.NotEmpty()
.WithMessage("Username is required.");
}
}
The AppUserDTO class acts as a Data Transfer Object or DTO. The validation logic for this DTO is implemented in the AppUserValidator class, which inherits from AbstractValidator<AppUserDTO>. This inheritance provides the foundation to define validation rules for the properties of AppUserDTO.
In short, the Validator serves as the entry point for FluentValidation, specifying which class or model needs validation. The Rules are the individual conditions or constraints applied to the properties of the model, ensuring that the data meets the required standards before proceeding. Together, they create a powerful and declarative way to handle validation in your applications.
Commonly Used FluentValidation Rules
Let’s break down some of the most commonly used validation rules in FluentValidation, which are essential for many applications.
One of the most frequently used validations is ensuring that required fields are not left empty. For example, the following code checks if the Username
property has been supplied. If it is missing, FluentValidation will return an error with the message “Username is required.” which is defined using the WithMessage
extension method.
RuleFor(user => user.Username)
.NotEmpty()
.WithMessage("Username is required.");
This rule is straightforward and ensures that users provide essential data like usernames, passwords, or IDs.
FluentValidation also provides built-in rules, such as validating email addresses. The EmailAddress method is specifically designed to check if a property contains a valid email format. Additionally, you can apply multiple validation rules to the same property. For example:
RuleFor(x => x.EmailAddress)
.NotEmpty().WithMessage("Email is required.")
.EmailAddress().WithMessage("Invalid email format.");
This sample also show how FluentValidation allows you to combine multiple rules seamlessly.
Sometimes, a property might be optional, but if a value is provided, it must meet specific criteria. FluentValidation makes this easy using conditional validation with the When
method. Here’s an example for an optional email address:
RuleFor(user => user.OptionalEmail)
.EmailAddress().When(user => !string.IsNullOrEmpty(user.OptionalEmail))
.WithMessage("Invalid email format.");
If the OptionalEmail field is left empty, FluentValidation does not apply any validation rules, allowing it to remain optional. However, if a value is provided, it must meet the criteria of a valid email address. This approach is particularly useful for handling optional fields, ensuring that data integrity is maintained only when the user supplies a value, while avoiding unnecessary errors for fields that are intentionally left blank.
One of the things I often use in FluentValidation is regular expressions for validating patterns. For example, when I need to enforce strong password policies in my projects, I use a rule that checks for at least one uppercase letter, one lowercase letter, one number, one special character, and a minimum length of 8 characters. Here’s a sample I frequently use:
RuleFor(user => user.Password)
.NotEmpty().WithMessage("Password is required.")
.Matches(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$")
.WithMessage("Password must be at least 8 characters long, and include an uppercase letter, a lowercase letter, a number, and a special character.");
Sometimes, the built-in rules aren’t enough, and I need to add custom logic to my validation. In these cases, I use the .Must()
method to create rules based on specific requirements. For example, I often check if an email address belongs to allowed domains. Here’s what it looks like in my code:
RuleFor(user => user.Email)
.Must(IsEmailAllowed).WithMessage("This email domain is not allowed.");
private bool IsEmailAllowed(string email)
{
var allowedDomains = new[] { "example.com", "mydomain.com" };
var domain = email?.Split('@').Last();
return allowedDomains.Contains(domain);
}
Another rule I often use is range validation with InclusiveBetween
. This is great for numeric fields like age or prices. For example, I recently used this to ensure that a user’s age falls between 18 and 60:
RuleFor(user => user.Age)
.InclusiveBetween(18, 60)
.WithMessage("Age must be between 18 and 60.");
There are many more rules and features in FluentValidation that you can explore. Covering all of them in a single blog post would make it too lengthy, so for now, I’ve shared some of the most commonly used ones to get you started. Stay tuned for my next blog, where I’ll dive into the more advanced rules and features of FluentValidation.