FluentValidation inline validate
Tags: csharp, dotnetcore, coding
Published: 2024-06-30 19:08:46

## 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](https://docs.fluentvalidation.net/en/latest/) library to validate data for basic solutions using FluentValidation inline validation while for complex validation see [the following article](https://dev.to/karenpayneoregon/fluentvalidation-tips-c-3olf) which demonstrates in both desktop and web projects.

> **Note**
> There is no formal documentation for inline validation. See [source code](https://github.com/FluentValidation/FluentValidation/blob/main/src/FluentValidation/InlineValidator.cs).

Secondary items, using a json file to dynamically read value for validating class/model properties and using custom extensions methods for extending FluentValidation.


![Happy face](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zhzvsr6uutsars3e8hqb.png)

For those who are use to conventional validation, inline does not change this as shown below.

{% cta https://github.com/karenpayneoregon/fluent-validation-tips/tree/master/InlineValidationSample1 %} Sample project {% endcta %}

```csharp
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

```csharp
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.

```csharp
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](https://dev.to/karenpayneoregon/fluentvalidation-tips-c-3olf) and is intended for normal style validation while inline is more quick and dirty, smaller project validation.

```csharp
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; }
}
```

```csharp
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

```csharp
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.

```csharp
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.

```csharp
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.

```csharp
public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }
}
```

In Windows forms, use the error provider.


![Windows forms sample](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ylpoysbh77ex3alzular.png)

### Blazor

See the following [documentation](https://docs.fluentvalidation.net/en/latest/blazor.html) for how to validate.