How FluentValidation can simplify maintenance and improve code reusability

In this blog post I will share what advantages the use of FluentValidation can bring  compared to DataAnnotation validation. Also I will provide a few examples of custom validators for validating file extension and mime-type, which are not built-in into the library.

FluentValidation is a .Net library that uses a fluent interface and lambda expressions for building validation rules. You can find well-maintained documentation here, meanwhile I will focus on explaining what are the reasons to use it, especially for larger systems that require more validation logic to be implemented.

Why to use FluentValidation?

1. Separation of concerns.

With FluentValidation it is possible to move validation logic into a distinct section, that will address a validation concern. It will be separate from business logic, which increases modularity.

Modularity copes with complexity, makes code easier to design, maintain and reuse. FluentValidation allows to remove validation from models and gives it their own classes. Validation rules are decoupled from models.

2. Conditional validation

Conditional validation is a great benefit here. It is quite often that we need to conditionally validate fields depending on various flags (e.g. if other fields are filled in, if checkbox is checked, etc.). DataAnnotation validation does not provide out of the box functionality to do conditional validation. In this case, it will be necessary to create custom validation attributes. On the other hand, with FluentValidation it can be done easily and there is no need to do any customization.

There are When and Unless methods that can be used to control when the rule should execute. You can find some nice examples in the documentation.

3. Unit testing is much easier

FluentValidation comes with two extension methods to simplify writing unit tests for validators.

4. FluentValidation makes models cleaner, easier to read; whereas, when validation is implemented by adding Data Annotations to model classes, readability of those models can become a matter of great concern. Below you can see an example of just a small part of a model with Data Annotations.


[Display(Name = "FirstNameLabel")]
[Required(ErrorMessage = ErrorMessages.Validation.Required)]
[RegularExpression(RegularExpressions.NoSpecialCharacters, ErrorMessage = ErrorMessages.Validation.SpecialCharactersNotAllowed)]
[StringLength(80, MinimumLength = 1, ErrorMessage = ErrorMessages.Validation.InvalidStringLength)]
public string FirstName { get; set; }

[Display(Name = "LastNameLabel")]
[Required(ErrorMessage = ErrorMessages.Validation.Required)]
[RegularExpression(RegularExpressions.NoSpecialCharacters, ErrorMessage = ErrorMessages.Validation.SpecialCharactersNotAllowed)]
[StringLength(120, MinimumLength = 1, ErrorMessage = ErrorMessages.Validation.InvalidStringLength)]
public string LastName { get; set; }

[Display(Name = "BirthCityLabel")]
[Required(ErrorMessage = ErrorMessages.Validation.Required)]
[RegularExpression(RegularExpressions.Regex, ErrorMessage = ErrorMessages.Validation.SpecialCharactersNotAllowed)]
[StringLength(90, ErrorMessage = ErrorMessages.Validation.InvalidStringLength)]
public string BirthCity { get; set; }

[Display(Name = "BirthCountryLabel")]
[Required(ErrorMessage = ErrorMessages.Validation.Required)]
[RegularExpression(RegularExpressions.Regex, ErrorMessage = ErrorMessages.Validation.SpecialCharactersNotAllowed)]
public string BirthCountry { get; set; }

In comparison with Data Annotations, when using FluentValidation, this model will be clean and decoupled from validation rules.

5. Easier to implement custom validation rules

FluentValidation comes with Predicate Validator that passes the value of the specified property into a delegate that can perform custom validation logic on the value. This is done by calling the Must  or the Custom  methods. Usually that will be enough to write your custom validation logic, though there can be cases when custom logic is complex and custom implementation of the PropertyValidator class needs to be written. Still it is simple and I would like to show an example of how to validate file extension and mime type by writing reusable Property Validators.

Even though in order to find out mime type or file extension, it will be better to read raw contents of a file and examine magic numbers (file signature) by using, for example, urlmon.dll, I will provide a simpler example instead, where we will read request content headers.

You would need to create a class and inherit it from the abstract PropertyValidator. For mime type, we read Content-Type header Headers.ContentType.MediaType and check if such property value is included into a predefined HashSet or List of allowed mime types.

public class MimeTypeValidator : PropertyValidator
{
    private readonly IList allowedMimeTypes;

    public MimeTypeValidator(IList allowedMimeTypes)
        : base("{PropertyValue} is not an allowed mime type")
    {
        this.allowedMimeTypes = allowedMimeTypes;
    }

    protected override bool IsValid(PropertyValidatorContext context)
    {
        if (context.PropertyValue == null) return false;
        if (this.allowedMimeTypes.Any(x => x.Equals((context.PropertyValue as string), StringComparison.OrdinalIgnoreCase))) return true;
        return false;
    }
}

For file extension validation, you can read Headers.ContentDisposition.FileName and get file extension from there by using, for example, Path.GetExtension method. Custom PropertyValidator for this will look very similar to the one above.

It is convenient to wrap the validator in an extension method as shown below:

public static IRuleBuilderOptions HasAllowedMimeType(
this IRuleBuilder ruleBuilder, IList allowedMimeTypes)
{
    return ruleBuilder.SetValidator(new MimeTypesValidator(allowedMimeTypes));
}

You can call your custom validator for HttpContent​ collection elements in a following way:

RuleFor(x => x.HttpContent).NestedRules(c =>
{
    c.RuleFor(f => f.Headers.ContentType.MediaType)
    .HasAllowedMimeType(allowedMimeTypes)
    .WithMessage(ErrorMessages.IncorrectMimeType);

    c.RuleFor(f => f.Headers.ContentDisposition.FileName)
    .HasAllowedExtension(allowedFileExtensions)
    .WithMessage(ErrorMessages.IncorrectFileExtension);
});

TL;DR

If a system has a lot of validation logic, especially custom validation, then FluentValidation can help separate validation concern, make it more flexible, maintainable and reusable.

Useful links:

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s