How to make validations in C# easier with FluentValidation

by Kenji Elzerman
FluentValidation With C# And .NET - Kens Learning Curve

A while ago, I wrote about using fluent assertions. This technique allows for more fluent, understandable, and expressive assertion writing. Let me introduce you to FluentValidation, which follows the same concept as fluent assertions but for validations and rules. In this FluentValidation in C# tutorial, I will show you how easy it is to validate objects.

Goals

In this article, I am going to explain to you the basics of FluentValidation in C#. After reading and following this tutorial you will

  • Understand the idea behind validating objects and FluentValidations in C#
  • Can create a simple validation with the RuleOf method
  • Can read the results from the validation
  • Can create a custom validator with an extension
  • Can set up dependency injection for FluentValidation

Validating Objects

There are many ways of validating objects and data in C#. Let’s take the following object as our prime example:

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public int Rating { get; set; }
}

A movie always needs a title and a rating. If we have a method that would save a movie, with this object Movie, we should validate if these fields are correct. The most obvious way is this:

public void Create(Movie movie)
{
    if(string.IsNullOrEmpty(movie.Title))
    {
        throw new ArgumentException("Title is required.");
    }

    if(movie.Rating < 0 || movie.Rating > 5)
    {
        throw new ArgumentException("The rating can't be less than 0 or more than 5.");
    }

    if(movie.Description.Length > 5000)
    {
        throw new ArgumentException("The description is to long.");
    }

    dataContext.Movies.Add(movie);
    dataContext.SaveChanges();
}

If you have more of these methods that use the same validations you could end up with duplicate code. Another issue could be the text. I have written de exception messages myself and could have typos (we all make them, there is no shame in it).

Yes, you could also use the [Required] attribute, but that doesn’t work if you work with console apps, WinForms, or just a backend. You will need to kick off those data annotations by hand.

FluentValidation makes this a little easier by creating a single validation class per object.

Rules Are Rules

Although this might sound like something from our day-to-day lives, this is actually how FluentValidation works. You create a class that drives from a generic class, which receives a type. Then you create a constructor and in this constructor, you define all kinds of rules for said generic type.

There are many types of rules you can apply. You can, for example, check if something is not empty, is null (or not), has a minimum length or a maximum length, and much more.

That’s the theory part.

Installation Of FluentValidation In C#

Before we can do anything with FluentValidation in C# we must install it. It’s a simple NuGet package, so this won’t be too hard. Install the package:

PM> Install-Package FluentValidation

This will give you all the classes, methods, and objects you need.

Create A Validation Class

The first step is, like most things in C#, to create a class. Give it a good, clean name with Validator behind it. So my class will get the name MovieValidator.

It will inherit from the class AbstractValidator, which is generic. You fill in the object you want to validate. In my case, that is the object Movie.

public class MovieValidator: AbstractValidator<Movie>
{

}

Adding Rules

Now it’s time to add some rules. At the beginning of this article, I listed a few rules for the Movie object. Let’s translate them to FluentValidation. Rules are created in the constructor, so it’s a good idea to create one now.

Let’s start with the title; It’s not allowed to enter an empty title (string.IsNullOrEmpty()). FluentValidation in C# uses lambda expressions to point out which property we want to validate. This lambda expression is put in the method RuleFor. We then can determine which validator we want to use. For example:

public class MovieValidator: AbstractValidator<Movie>
{
    public MovieValidator()
    {
        RuleFor(m => m.Title).NotEmpty();
    }
}

This code clearly shows that the property Title is validated on emptiness. If the title is empty, a warning is saved in the results. Let’s add the other rules.

public class MovieValidator: AbstractValidator<Movie>
{
    public MovieValidator()
    {
        RuleFor(m => m.Title).NotEmpty();
        RuleFor(m => m.Rating).ExclusiveBetween(1, 5);
        RuleFor(m => m.Description).MaximumLength(5000);
    }
}

Using The Validation Class

All we have to do now is use the validation class with the rules. We need to initialize the validation class, call the Validate method with the movie object, and process the result. Easy, right?

public void Create(Movie movie)
{
    MovieValidator validator = new();
    ValidationResult result = validator.Validate(movie);

    if (!result.IsValid)
    {
        foreach (var error in result.Errors)
        {
            Console.WriteLine($"Error on the property {error.PropertyName}: {error.ErrorMessage}");
        }
    }
    else
    {
        dataContext.Movies.Add(movie);
        dataContext.SaveChanges();
    }

}

If there are validation errors I print them to the console output. Of course, in a real situation, you want to write them to a log or throw an exception.

Using Different Message

If you’re unhappy with FluentValidation’s default message, you can customize it. Simply use the WithMessage method.

I am quite happy with the message FluentValidation gives me on the Title property. But the Description could be a bit better. Let’s change that.

public class MovieValidator: AbstractValidator<Movie>
{
    public MovieValidator()
    {
        RuleFor(m => m.Title).NotEmpty();
        RuleFor(m => m.Rating).ExclusiveBetween(1, 5);
        RuleFor(m => m.Description).MaximumLength(5000).WithMessage("The number of characters can't be more than 5000. Please check the length.");
    }
}

You can also add an error code to the rule, which could be easier when handling validation errors. In larger projects, I use a resource file with codes and the description. When a validation error occurs I look up the error code and display the message. The big advantage of this is that you can use multilanguage and none-developers can change the text.

To use error codes, just add the WithErrorCode method behind the rule:

public class MovieValidator: AbstractValidator<Movie>
{
    public MovieValidator()
    {
        RuleFor(m => m.Title).NotEmpty();
        RuleFor(m => m.Rating).ExclusiveBetween(1, 5);
        RuleFor(m => m.Description).MaximumLength(5000)
            .WithMessage("The number of characters can't be more than 5000. Please check the length.")
            .WithErrorCode("707");
    }
}

Custom Validators

As usual, you will encounter a situation where you can’t use the built-in validators that FluentValidation in C# gives you. Otherwise, it would be way too easy. Luckily you can create your own validator, which is nothing more than a method that returns a boolean.

To demonstrate this I have added a new property to the Movie class: ManagementEmail. This is a string and should contain an e-mail address. To validate this I will be using FluentValidation.

Yes, there is a built-in method you can use to validate an e-mail address, but I want to show you a simple example of how to create your own validators

I added the following rule in my validation class:

RuleFor(x => x.ManagementEmail)
    .Must(IsEmail)
    .WithMessage("This is not a valid e-mail address.");

The Must method executes a lambda expression or you can call a method, which needs to have the same parameter type as the property you are validating.

The IsEmail is a method that I have created in the same class:

private bool IsEmail(string value)
{
    if (string.IsNullOrEmpty(value))
        return false;

    string emailPattern = @"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$";

    return Regex.IsMatch(value, emailPattern);
}

First I check if the value contains a value. If not, I return false. If it does contain a value and it matches the regular expression, it will return true.

When the method returns false, FluentValidation will return the message (This is not a valid e-mail address.) to the results.

Custom Validator Extension

We could go a step further and create an extension for this situation. You will need a static class and a static method. This method must return an IRuleBuilderOptions<T, string>, where the string represents the e-mail address.

public static class CustomEmailValidator
{
    public static IRuleBuilderOptions<T, string> IsValidEmail<T>(this IRuleBuilder<T, string> ruleBuilder)
    {
        return ruleBuilder.Must(IsEmail).WithMessage("This is not a valid e-mail address.");
    }

    private static bool IsEmail(string value)
    {
        if (string.IsNullOrEmpty(value))
            return false;

        string emailPattern = @"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$";

        return Regex.IsMatch(value, emailPattern);
    }
}

This looks a lot like we did before, but now in a static class and method. Now you can use the IsValidEmail method in the rules:

public MovieValidator()
{
    RuleFor(m => m.Title).NotEmpty();
    RuleFor(m => m.Rating).ExclusiveBetween(1, 5);
    RuleFor(m => m.Description).MaximumLength(5000)
        .WithMessage("The number of characters can't be more than 5000. Please check the length.")
        .WithErrorCode("707");

    RuleFor(x => x.ManagementEmail).IsValidEmail();
}

You can reuse this extension method on other validation classes. This might not be the most exciting example, but it does show you how you can achieve it.

Conditions

Ah, yes! What would we, developers, be without conditions?! We can use different sets of rules in validation classes to determine different outcomes.

If we look at the movies: It would be really awkward to set a rating if the movie hasn’t come out yet. We should validate if the rating is 0 when the release date is higher than the current date.

So I added a new property to my movie class; ReleaseDate. And it’s a DateTime type. So, when the value of ReleaseDate is higher than today it should not validate the rating. Otherwise, it should.

public MovieValidator()
{
    RuleFor(m => m.Title).NotEmpty();
    RuleFor(m => m.Description).MaximumLength(5000)
        .WithMessage("The number of characters can't be more than 5000. Please check the length.")
        .WithErrorCode("707");

    RuleFor(x => x.ManagementEmail).IsValidEmail();

    When(x => x.ReleaseDate > DateTime.Now, () =>
    {
        RuleFor(m => m.Rating).Empty().WithMessage("The movie hasn't come out yet, so you can rate it.");
    }).Otherwise(() =>
    {
        RuleFor(m => m.Rating).ExclusiveBetween(1, 5);
    });
}

The When is nothing more than an if-statement. It’s either true or false. When it’s true it will check if the rating is empty (0). When this is the case a validation error will be added to the results.

If the movie is released it will go to the Otherwise and validate if the rating is between 1 and 5.

Using Dependency Injection

I am mostly working on web APIs and dependency injection is a must. Initializing a class for validation is not done. Luckily FluentValidation also has a way to inject the validators into the classes where you need them.

To demonstrate this to you I have created a simple .NET 7 API. I also created a .NET 7 class library that contains all the logic to handle and execute the validations; the classes and methods I have shown you earlier in this article. This way the console application in previous examples still works.

Dependency injection in an API isn’t really hard. All you have to do is use an interface that is connected to a class and tell the API to use and initialize that particular class.

But uhm… I didn’t create an interface for the validator class… No need because FluentValidation in C# has its own interface, which is IValidator<T>, where T is the object type we want to validate. The derived class AbstractValidator<Movie> from earlier has a derived class IValidator<T> and we can use this interface for dependency injection.

If you put that together in the Program.cs of the API:

builder.Services.AddScoped<IValidator<Movie>, MovieValidator>();

Now you can inject the IValidator<Movie> into your classes and use the validator(s) when you need them.

Conclusion On FluentValidation In C#

If you have some complex validation going on in your code it might be worth looking into FluentValidation in C#. It’s easy to use, quick to set up, and not hard to expand when needed. In a few minutes, you have the validation on an object you need.

There are way more possibilities than I have mentioned. Sure, I could type (half) a bible on this topic, but I think I will lose you as soon as you see the size of the article. Know what FluentValidation is and what it does, and work from there.

If you have a small validation, like you need to validate one property on an object, just use a simple if-statement. Don’t make it harder than it is. FluentValidation in C# is great if your if-statements are growing out of proportion and make more sense of large pieces of code.

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.

Table Of Contents
Kens Learning Curve