BLOG

Validation - part 3

This is part 3 of the series, you can find the other parts here:

Introduction

In the first two parts we defined a basic OOP structure to validate data objects, and in this part we’re going to digress slightly into a plugin, service based validation infrastructure.

As mentioned in the first part, a static class doing validations is not very modular, and I would definitely advise against it. So how do you setup a modular validation engine?

Separation of validations

First of all, we’ll separate the validation logic from the object being validated. This makes sense, because in this model we don’t necessarily know which validations apply to all the types. Product owners or customers may need specific dynamic validations for our model, and modularizing the validation logic is a good way to do that. We’ll keep the IValidatable<T> interface, because it is handy to have, and it can automatically be translated to our new interface:

public interface IValidator<T>
{
    List<string> Validate(T item);
}

We need to pass in the instance to validate, because it is not an interface we implement on the class itself.

Translating an IValidatable<T> to an IValidator<T> is straightforward:

public class DefaultValidator<T> : IValidator<T>
    where T : IValidatable<T>
{
    public List<string> Validate(T item)
        => item.Validate();
}

Context

Okay, so how do we address all these validators? We could again do something with reflection and find all IValidator<T> implementations, but I think a more modular solution can be built by explicit registration of all IValidator<T> instances. A collection of these instances can be made into some kind of validation context. This context is then used to do all actual validations.

public interface IValidationContext
{
    List<string> Validate<T>(T item);
}

With the use of a helper interface IValidator as an untyped base for all IValidator<T> interfaces we can implement a very naive version for this interface as follows:

public interface IValidator 
{
    List<string> Validate(object item);
}
public interface IValidator<T> : IValidation
{
    List<string> Validate(T item);
}

public class ValidationContext : IValidationContext
{
    private readonly IEnumerable<IValidator> validators;
    
    public ValidationContext(IEnumerable<IValidator> validators){
        this.validators = validators;
    }

    public List<string> Validate<T>(T item)
        => (from v in validators.OfType<IValidator<T>>()
            from msg in v.Validate(item)
            select msg).ToList(); 
}