Built-in FluentValidation Validators
In this post, I'll focus on the built-in validators available in FluentValidation, which are essential for effective data validation in .net applications. Each validator has its specific applications and behaves differently depending on the data type it is applied to. Understanding these differences is key to creating effective and reliable validation rules. This post will describe the built-in validators. Examples are also included to better understand the validators' operation.
NotEmpty Validator
The validator is used to check whether the passed property is not empty. Its behavior changes depending on the data type it is applied to. For strings (string), NotEmpty ensures that the string is not empty, meaning it does not contain only whitespace, is not an empty string, or is not null. This is important in applications where non-empty text data is required, such as a username or email address. For numeric types, NotEmpty checks whether the value is not equal to zero, which is useful for verifying values where zero may be treated as an incorrect or missing value. For the DateTime type, this validator ensures that the date is not equal to the default value (DateTime.MinValue), which can be useful for checking if a date has been correctly set. For collections, the validator checks if it contains any element.
Let's assume we have a class representing an order form where we want to ensure that some fields are not empty:
public class OrderForm
{
public string ProductId { get; set; }
public string CustomerName { get; set; }
}
public class OrderFormValidator : AbstractValidator<OrderForm>
{
public OrderFormValidator()
{
RuleFor(order => order.ProductId).NotEmpty()
.WithMessage("Product ID must not be empty.");
RuleFor(order => order.CustomerName).NotEmpty()
.WithMessage("Customer name must not be empty.");
}
}
In this example, the NotEmpty validator is used to check whether ProductId (product identifier) and CustomerName (customer name) are not empty. This means that these fields cannot be null, nor consist of an empty string. If any of these fields are empty, the validation will fail, and the user will be informed about the need to fill these fields.
Empty Validator
The Empty validator in FluentValidation acts oppositely to the NotEmpty validator. Its task is to check whether a given field or property is "empty". In the context of different data types, "lack of value" can be interpreted in various ways. For reference types, like strings (string), empty means null or a string of zero length (""), for collections — lack of elements, and for value types, like DateTime or numeric types, "lack of value" typically refers to their default values (default(T)), e.g., DateTime.MinValue for DateTime or 0 for numeric types.
Using the Empty validator is particularly useful in scenarios where certain fields are expected not to be set or filled — for example, when you want to ensure that a user has intentionally left certain optional fields without entered values.
Let's assume we have a User model that has an optional field MiddleName, and we want to verify that this field is empty:
public class User
{
public string MiddleName { get; set; }
}
public class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(user => user.MiddleName)
.Empty().WithMessage("The middle name must be empty.");
}
}
In this case, the validation will succeed only if MiddleName is null or is an empty string. If MiddleName contains any text, the validation will not pass.
Using Empty for the DateTime type is less common, as usually, a date is not expected to be "empty" (i.e., DateTime.MinValue). However, if such a scenario were to occur, the Empty validator would check if the date equals DateTime.MinValue.
NotNull Validator
The NotNull validator is simple but essential. It checks whether a given value is not null. This is a fundamental validation for all data types, ensuring that a field has not been omitted or incorrectly initialized. This is particularly important in environments where null can cause exceptions or errors in the application's logic.
Let's assume we have a class representing customer contact information where we want to ensure that some key fields are not empty:
public class CustomerContactInfo
{
public string Name { get; set; }
public string Email { get; set; }
}
public class CustomerContactInfoValidator : AbstractValidator<CustomerContactInfo>
{
public CustomerContactInfoValidator()
{
RuleFor(customer => customer.Name).NotNull()
.WithMessage("Name cannot be null.");
RuleFor(customer => customer.Email).NotNull()
.WithMessage("Email cannot be null.");
}
}
In this example, the NotNull validator is used to check whether Name and Email are not null. If any of these fields are null, the validation will fail, and the user will be informed about the need to fill these fields.
Length Validator
This validator is used to control the length of strings. It allows specifying the minimum and maximum allowable length of a string, which is crucial in many contexts, for example, in validating passwords, where a password that is too short or too long can be unsafe or impractical.
Let's assume we have a class representing a product review where we want to ensure that the review content is not too short or too long:
public class ProductReview
{
public string ReviewContent { get; set; }
}
public class ProductReviewValidator : AbstractValidator<ProductReview>
{
public ProductReviewValidator()
{
RuleFor(review => review.ReviewContent).Length(50, 500)
.WithMessage("Review content must be between 50 and 500 characters.");
}
}
In this example, the Length validator is used to check whether ReviewContent (review content) is between 50 and 500 characters long. If the length of the review content is shorter than 50 characters or longer than 500 characters, the validation will fail, and the user will be informed that their review does not meet the length requirements.
MaxLength Validator
The MaxLength validator is used to determine the maximum allowable length of a string. This is particularly important in contexts where input data must be limited to a certain length, for example, in user comments, product descriptions, or blog posts. It ensures that input data does not exceed a specified limit, which can be important for both usability and system performance.
Let's assume we have a class representing a user comment on a website where we want to limit the length of this comment:
public class Comment
{
public string Content { get; set; }
}
public class CommentValidator : AbstractValidator<Comment>
{
public CommentValidator()
{
RuleFor(comment => comment.Content).MaxLength(200)
.WithMessage("Comment must be no more than 200 characters long.");
}
}
In this example, the MaxLength validator is used to check whether Content (comment content) does not exceed 200 characters. If the length of the comment is longer than 200 characters, the validation will fail, and the user will be informed that their comment is too long.
MinLength Validator
The opposite of MaxLength, the MinLength validator checks whether the length of a string is at least equal to a specified minimum value. This is useful in situations where input data must meet minimum length requirements, for example, the minimum length of a password in security systems. It helps ensure that input data is sufficiently comprehensive to meet specific criteria.
Let's assume we have a class representing a system user where we want to ensure that the provided password meets a minimum length:
public class User
{
public string Password { get; set; }
}
public class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(user => user.Password).MinLength(8)
.WithMessage("Password must be at least 8 characters long.");
}
}
In this example, the MinLength validator is used to check whether Password is at least 8 characters long. If the length of the password is shorter than 8 characters, the validation will fail, and the user will be informed about the need to enter a longer password.
LessThan Validator
The LessThan validator is used to compare values to ensure that a given value is less than a specified reference point. This is useful in many scenarios, such as limiting the maximum value for certain types of numeric data, for example, age, price, or other numerical values. It allows for easy establishment of upper limits and is invaluable in controlling data ranges.
Let's assume we have a class representing information about a person's age where we want to ensure that the person is younger than a specified age:
public class Person
{
public int Age { get; set; }
}
public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
RuleFor(person => person.Age).LessThan(18)
.WithMessage("Person must be younger than 18 years old.");
}
}
In this example, the LessThan validator is used to check whether Age (person's age) is less than 18. If the person's age is 18 or greater, the validation will fail, and the user will receive an appropriate error message.
GreaterThan Validator
The GreaterThan validator works in the opposite way to LessThan, checking whether a given value is greater than a specified reference point. This is essential in situations where a minimum threshold is required, for example, a minimum order amount, minimum age, or other numerical values. This validator allows for the establishment of lower bounds for data, which is important in many business applications and data management systems.
Let's assume we have a class representing order details where we want to ensure that the quantity of ordered products is greater than the minimum allowed amount:
public class OrderDetails
{
public int MinimumOrderQuantity { get; set; } = 5;
public int OrderedQuantity { get; set; }
}
public class OrderDetailsValidator : AbstractValidator<OrderDetails>
{
public OrderDetailsValidator()
{
RuleFor(order => order.OrderedQuantity).GreaterThan(order => order.MinimumOrderQuantity)
.WithMessage("Ordered quantity must be greater than the minimum order quantity.");
}
}
In this example, the GreaterThan validator is used to check whether OrderedQuantity (the quantity of ordered products) is greater than MinimumOrderQuantity (the minimum quantity for an order). If the quantity of ordered products is less than or equal to the minimum quantity, the validation will fail, and the user will receive an appropriate error message.
GreaterThanOrEqual Validator
This validator is used for comparing values. GreaterThanOrEqual checks whether a given value is greater than or equal to a specified reference point. It is extremely useful in validating ranges, for example, when determining the minimum and maximum price of a product, boundary dates in reservations, or age restrictions.
Let's assume we have a class representing product details where we want to ensure that the quantity of the product in stock is greater than or equal to the minimum quantity required to maintain stock:
public class Product
{
public int StockQuantity { get; set; }
}
public class ProductValidator : AbstractValidator<Product>
{
public ProductValidator()
{
RuleFor(product => product.StockQuantity).GreaterThanOrEqual(10)
.WithMessage("Stock quantity must be greater than or equal to 10.");
}
}
In this example, the GreaterThanOrEqual validator is used to check whether StockQuantity (the quantity of the product in stock) is greater than or equal to 10. If the quantity in stock is less than 10, the validation will fail, and the user will be informed that the quantity of the product in stock is insufficient.
LessThanOrEqual
This validator is used for comparing values. It checks whether the value of a given field or property is less than or equal to a specified value. This validator is useful in various scenarios, for example, in validating dates, limits, age, etc., where we need to ensure that the value does not exceed a certain maximum.
Let's assume we are creating a reservation system and want to ensure that the reservation date is no later than the current date. We will use the LessThanOrEqual validator to check if the reservation date meets this criterion:
using FluentValidation;
using System;
public class Reservation
{
public DateTime ReservationDate { get; set; }
}
public class ReservationValidator : AbstractValidator<Reservation>
{
public ReservationValidator()
{
RuleFor(reservation => reservation.ReservationDate)
.LessThanOrEqualTo(DateTime.Now).WithMessage("The reservation date must be today's date or earlier.");
}
}
In this example, ReservationValidator uses LessThanOrEqualTo to compare ReservationDate with DateTime.Now, i.e., the current date. If the reservation date is in the future relative to the current date, the validation will fail, displaying an appropriate message.
Email Validator
The Email validator is used to check whether a given string is a valid email address. This is key in many applications where email contact with the user is required. This validator checks whether the email address format conforms to standard conventions, which helps in eliminating data entry errors and facilitates communication.
Let's assume we have a class representing user contact information where we want to check if the provided email address is valid:
public class ContactInfo
{
public string Email { get; set; }
}
public class ContactInfoValidator : AbstractValidator<ContactInfo>
{
public ContactInfoValidator()
{
RuleFor(contact => contact.Email).EmailAddress()
.WithMessage("Invalid email address.");
}
}
In this example, the EmailAddress validator is used to check whether Email is a valid email address. If the email address is not valid, the validation will fail, and the user will be informed of the error.
Regular Expression Validator
It allows defining custom validation rules using regular expressions. This is an extremely powerful tool that allows customizing validation to the specific needs of the application. It can be used to check whether input data matches a specific pattern, for example, a SKU code in an online store or the format of a phone number.
Let's assume we have a class representing user data where we want to check if the user's phone number matches a specific format:
public class User
{
public string PhoneNumber { get; set; }
}
public class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
var phoneRegex = @"^\+48\d{9}$";
RuleFor(user => user.PhoneNumber).Matches(phoneRegex)
.WithMessage("Phone number must be in the format +48123456789.");
}
}
In this example, the Matches validator is used to check whether PhoneNumber matches the pattern of a Polish mobile phone number. If the phone number does not match this pattern, the validation will fail, and the user will be informed of the incorrect phone number format.
NotEqual Validator
The NotEqual validator is used to ensure that two fields or values are not equal to each other. This is particularly useful in situations where two values should not be identical, for example, when a password and its confirmation must be different from the username. This validator allows specifying that a given value cannot be equal to a specified reference point, which helps in preventing data conflicts or in some cases, enhances security.
Let's assume we have a class representing a user registration form where we want to ensure that the username and password are not the same:
public class UserRegistration
{
public string Username { get; set; }
public string Password { get; set; }
}
public class UserRegistrationValidator : AbstractValidator<UserRegistration>
{
public UserRegistrationValidator()
{
RuleFor(registration => registration.Password).NotEqual(registration => registration.Username)
.WithMessage("The password cannot be the same as the username.");
}
}
In this example, the NotEqual validator is used to check whether Password is not the same as Username. If the password and username are identical, the validation will fail, and the user will be informed that they must choose a different password.
Equal Validator
The opposite of NotEqual, the Equal validator checks whether two values are equal to each other. This is necessary in many scenarios, for example, when verifying that a password entered twice is the same. It can also be used to ensure consistency between different data fields, for example, in situations where the start and end date of an event should be the same, or when confirming an email address. This validator allows for easy verification of input data conformity, which is key in maintaining data integrity.
Let's assume we have a class representing a password change form where the user must enter a new password and confirm it:
public class PasswordChangeForm
{
public string NewPassword { get; set; }
public string ConfirmPassword { get; set; }
}
public class PasswordChangeFormValidator : AbstractValidator<PasswordChangeForm>
{
public PasswordChangeFormValidator()
{
RuleFor(form => form.ConfirmPassword).Equal(form => form.NewPassword)
.WithMessage("The confirmation password does not match the new password.");
}
}
In this example, the Equal validator is used to check whether the value of ConfirmPassword is equal to NewPassword. If the passwords are not identical, the validation will fail, and the user will be informed of the mismatch.
Must Validator
The Must validator allows for the creation of custom validation rules using lambda expressions or delegated methods. It is a flexible tool that enables the definition of precise validation conditions that may not be directly available through other built-in validators. It gives developers the freedom to create specific, contextual validation rules that best fit their unique application requirements.
Let's assume we have a class representing user data where we want to apply custom validation to the email address:
public class User
{
public string Email { get; set; }
}
public class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(user => user.Email).Must(BeAValidEmail)
.WithMessage("Invalid email address.");
}
private bool BeAValidEmail(string email)
{
return email.EndsWith("@example.com");
}
}
In this example, Must is used in conjunction with the BeAValidEmail method, which defines custom validation logic for the email address. If the email address does not meet the criteria defined in BeAValidEmail (in this case, it must end with "@example.com"), the validation will fail, and the user will be informed of the invalid email address.
CreditCard Validator
This specialized validator is used to check whether a given string is a valid credit card number. It uses various algorithms to verify the format and correctness of card numbers, which is essential in financial applications and e-commerce to ensure that the provided card information is correct before processing a transaction.
Let's assume we have a class representing payment details where we want to ensure that the provided credit card number is valid:
public class PaymentDetails
{
public string CreditCardNumber { get; set; }
}
public class PaymentDetailsValidator : AbstractValidator<PaymentDetails>
{
public PaymentDetailsValidator()
{
RuleFor(payment => payment.CreditCardNumber).CreditCard()
.WithMessage("Invalid credit card number.");
}
}
In this example, the CreditCard validator is used to check whether CreditCardNumber is a valid credit card number. This validator uses algorithms, such as the Luhn algorithm, to verify the correctness of the card number. If the card number is incorrect, the validation will fail, and the user will be informed of the error.
IsInEnum Validator
This validator is used to verify whether a given value belongs to a specified set of enum (enumeration) values. This is useful in situations where user input or values from external data sources must be restricted to a specified set of predefined options. It ensures data consistency and prevents errors resulting from unexpected values.
Let's assume we have an enumeration type representing order status and a class that uses this enumeration type:
public enum OrderStatus
{
Pending,
Shipped,
Delivered,
Cancelled
}
public class Order
{
public OrderStatus Status { get; set; }
}
public class OrderValidator : AbstractValidator<Order>
{
public OrderValidator()
{
RuleFor(order => order.Status).IsInEnum()
.WithMessage("Invalid order status.");
}
}
In this example, IsInEnum is used to check whether Status (order status) in the Order class corresponds to one of the values defined in OrderStatus. If the provided Status value does not match any of the values in OrderStatus, the validation will fail, and the user will be informed of the invalid order status.
IsEnumName Validator
This validator differs from the usual Enum validator because, instead of checking the numerical values of the enumeration, it focuses on verifying the names of those values. This is particularly useful when the expected input is the names of enum elements, not their numeric values. This allows for easy checking whether a given string matches one of the names in a defined set of enums, which is useful in cases where input data is provided in text form, for example, in configuration files or user interfaces.
Let's define an enumeration type and then a class that will contain a property with a color name. Then we define a validator for this class:
public enum Color
{
Red,
Green,
Blue
}
public class Product
{
public string ColorName { get; set; }
}
public class ProductValidator : AbstractValidator<Product>
{
public ProductValidator()
{
RuleFor(product => product.ColorName).IsEnumName(typeof(Color), caseSensitive: false)
.WithMessage("Color name must be a valid name in the Color enum.");
}
}
In this example, IsEnumName checks whether the ColorName property value in the Product class is a valid name in the Color enumeration type. The caseSensitive: false parameter indicates that the validation does not consider case sensitivity, which is useful when input data may be entered in various formats. Thus, if ColorName is "red", "Red", or "RED", the validator will consider it a valid value.
ExclusiveBetween Validator
It is used to specify that a given value must be within a specified range but excluding the boundary values. For example, if we specify a range from 1 to 10, the validator will accept values from 2 to 9. This is useful in situations where a clear limitation to a middle range is needed, for example, in defining acceptable risk levels or other values that should not reach their extreme points.
Let's assume we have a class representing information about an age group for a certain activity where we want to ensure that the participant's age falls within a specified range but does not include the boundary values:
public class ActivityParticipant
{
public int Age { get; set; }
}
public class ActivityParticipantValidator : AbstractValidator<ActivityParticipant>
{
public ActivityParticipantValidator()
{
RuleFor(participant => participant.Age).ExclusiveBetween(18, 60)
.WithMessage("Age must be more than 18 and less than 60.");
}
}
In this example, the ExclusiveBetween validator is used to check whether Age (participant's age) is greater than 18 but less than 60. This means that ages from 19 to 59 are accepted. If the participant's age is equal to 18 or 60, or exceeds these values, the validation will fail, and the user will be informed that their age does not fit within the allowed range.
InclusiveBetween Validator
The validator works similarly to ExclusiveBetween, but in this case, the boundary values are included in the acceptable range. For example, for a range from 1 to 10, the validator will accept all values from 1 to 10 inclusive. This is useful in most cases where value restrictions are required, but the boundary values are still acceptable, like in age or budget restrictions.
Let's assume we have a class representing product information where we want to ensure that the product price falls within an acceptable price range:
public class Product
{
public decimal Price { get; set; }
}
public class ProductValidator : AbstractValidator<Product>
{
public ProductValidator()
{
RuleFor(product => product.Price).InclusiveBetween(10.00m, 100.00m)
.WithMessage("Price must be between 10.00 and 100.00, inclusive.");
}
}
In this example, the InclusiveBetween validator is used to check whether Price (product price) falls within the range of 10.00 to 100.00, including these boundary values. If the product price is lower than 10.00 or higher than 100.00, the validation will fail, and the user will be informed that the price does not fit within the allowed range.
PrecisionScale Validator
It is used for validating floating-point numbers, where not only the value but also the precision, i.e., the number of digits after the decimal point, is important. It allows specifying the maximum number of whole digits and the maximum number of digits after the decimal point. This is crucial in financial and engineering applications where the accuracy of numerical values is significant, for example, when determining pricing rates, engineering measurements, or scientific calculations. It enables precise data management, ensuring that numbers do not exceed a specified precision and scale, which is key for ensuring accuracy and consistency of data.
Let's assume we have a class representing financial transactions where we want to precisely control the format of the transaction amount:
public class FinancialTransaction
{
public decimal TransactionAmount { get; set; }
}
public class FinancialTransactionValidator : AbstractValidator<FinancialTransaction>
{
public FinancialTransactionValidator()
{
RuleFor(transaction => transaction.TransactionAmount).PrecisionScale(2, 5)
.WithMessage("Transaction amount must have no more than 2 decimal places and total digits must not exceed 5.");
}
}
In this example, the ScalePrecision validator is used to check whether TransactionAmount has no more than two digits after the decimal point and in total does not exceed 5 digits. This means the maximum value TransactionAmount can take is 999.99. This is useful in financial systems where precision and format of amounts are crucial for accounting and reporting purposes. It ensures that transaction amounts are always represented with the correct precision, which is important for the accuracy of financial calculations and avoiding rounding errors.
Each of these validators offers specific capabilities and is tailored to particular validation requirements. They enable the creation of more detailed and effective validation rules, which translates to greater reliability and accuracy of applications. Utilizing these tools in programming practice allows for better control and management of input data, which is extremely important in many aspects of modern software.
See you in the next posts!