Learn FluentValidation inline validation
One of the most important tasks in software development is to ensure that data saved meets requirements for business solutions. Learn how to use FluentValidation library to validate data for basic solutions using FluentValidation inline validation while for complex validation see the following article which demonstrates in both desktop and web projects.
Note
There is no formal documentation for inline validation. See source code.
Secondary items, using a json file to dynamically read value for validating class/model properties and using custom extensions methods for extending FluentValidation.
For those who are use to conventional validation, inline does not change this as shown below.
var validator = Person.Validator.Validate(person);
Why using Windows Forms?
First off, the code presented will work in other project types, cross-platform, console and web. The reason for using Windows Forms is it is easy to learn from and work except on a Mac.
Background
The task in this case is to validate a Person class, ensure that the Title property is valid against a list, FirstName and LastName are not empty, Gender is valid against a list and BirthDate fails in a specific range.
When a developer writes code against business specifications there would be no reason for validating Title and Gender against list but in the real world the human factor means code is prone to mistakes or not reading an adhering to specifications which is where validation is needed.
Models
public enum Gender
{
Male,
Female,
NotSet
}
- Validator is for inline validation
- The use of an interface, with one model it is not helpful but when dealing with more models and countless projects it becomes more important.
- Change notification is not always needed yet for some this may be new.
public class Person : IHuman, INotifyPropertyChanged
{
#region Properties
private int _id;
private string _firstName;
private string _lastName;
private string _title;
private DateOnly _birthDate;
private Gender _gender;
public int Id
{
get => _id;
set
{
if (value == _id) return;
_id = value;
OnPropertyChanged();
}
}
public string FirstName
{
get => _firstName;
set
{
if (value == _firstName) return;
_firstName = value;
OnPropertyChanged();
}
}
public string LastName
{
get => _lastName;
set
{
if (value == _lastName) return;
_lastName = value;
OnPropertyChanged();
}
}
public string Title
{
get => _title;
set
{
if (value == _title) return;
_title = value;
OnPropertyChanged();
}
}
public DateOnly BirthDate
{
get => _birthDate;
set
{
if (value.Equals(_birthDate)) return;
_birthDate = value;
OnPropertyChanged();
}
}
public Gender Gender
{
get => _gender;
set
{
if (value == _gender) return;
_gender = value;
OnPropertyChanged();
}
}
#endregion
public static readonly InlineValidator<Person> Validator = new()
{
// validate against the Titles from Validation.json using ValidatorExtensions.In extension method
v => v.RuleFor(x => x.Title)
.In(Titles),
v => v.RuleFor(x => x.FirstName)
.NotEmpty()
.WithMessage("{PropertyName} is required"),
v => v.RuleFor(x => x.LastName)
.NotEmpty()
.WithMessage("{PropertyName} is required"),
// validate against the genders from Validation.json
v => v.RuleFor(x => x.Gender)
.NotNull()
.In(GenderTypes),
// validate against the BirthDateRule extension method
v => v.RuleFor(x => x.BirthDate)
.BirthDateRule()
};
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Inline validation
Normally validation is written in a separate class than the business class/model for separation of concerns along with in some cases placing the validation code in a class project for reuse.
The only reason for using inline validation is for smaller projects and for learning purposes.
Usually this is how full validation is configured. The code below was used in this article and is intended for normal style validation while inline is more quick and dirty, smaller project validation.
public class Person
{
public string UserName { get; set; }
public string EmailAddress { get; set; }
public string Password { get; set; }
public string PasswordConfirmation { get; set; }
public string PhoneNumber { get; set; }
}
public class UserNameValidator : AbstractValidator<Person>
{
public UserNameValidator()
{
RuleFor(person => person.UserName)
.NotEmpty()
.MinimumLength(3);
}
}
public class PasswordValidator : AbstractValidator<Person>
{
public PasswordValidator()
{
RuleFor(person => person.Password.Length)
.GreaterThan(7);
RuleFor(person => person.Password)
.Equal(p => p.PasswordConfirmation)
.WithState(x => StatusCodes.PasswordsMisMatch);
}
}
public class EmailAddressValidator : AbstractValidator<Person>
{
public EmailAddressValidator()
{
RuleFor(person => person.EmailAddress)
.Must((person, b) => new EmailAddressAttribute().IsValid(person.EmailAddress));
}
}
public class PhoneNumberValidator : AbstractValidator<Person>
{
public PhoneNumberValidator()
{
RuleFor(person => person.PhoneNumber)
.MatchPhoneNumber();
}
}
public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
Include(new UserNameValidator());
Include(new PasswordValidator());
Include(new EmailAddressValidator());
Include(new PhoneNumberValidator());
}
}
Implement in a project
Create a model/class
public class Customers
{
public int CustomerIdentifier { get; set; }
public string CompanyName { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public string Phone { get; set; }
}
Add the InlineValidator as follows.
public class Customers
{
public int CustomerIdentifier { get; set; }
public string CompanyName { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public static readonly InlineValidator<Customers> Validator = new()
{
v => v.RuleFor(x => x.CompanyName)
.NotEmpty()
.WithMessage("{PropertyName} is required"),
v => v.RuleFor(x => x.Street)
.NotEmpty()
.WithMessage("{PropertyName} is required"),
v => v.RuleFor(x => x.City)
.NotEmpty()
.WithMessage("{PropertyName} is required"),
v => v.RuleFor(x => x.PostalCode)
.NotEmpty()
.WithMessage("{PropertyName} is required"),
};
}
Validate the model.
With a instance, in this case Customers call the validator.
Customers customers = new Customers();
var customerValidator = Customers.Validator.Validate(customers);
if (customerValidator.IsValid)
{
// good to go
}
else
{
// validation failed
}
In a ASP.NET Core project (see example in source repository) if validation fails return the page.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
}
In Windows forms, use the error provider.
Blazor
See the following documentation for how to validate.