Create your own validator

Basic interfaces and abstract classes

There are 2 interfaces to implement to create a custom validator:
  1. IValidator: basic interface for a validator
public interface IValidator
{
    bool IsValid(object value);
    bool IsValid(object value, ValidationContext validationContext);

    IEnumerable<ValidationResult> GetValidationResult(object value);
    IEnumerable<ValidationResult> GetValidationResult(object value, ValidationContext validationContext);
}
  1. IValidator<T>: a strongly type validator that is similar to the above one
public interface IValidator<in T> : IValidator
{
    bool IsValid(T value);
    bool IsValid(T value, ValidationContext validationContext);

    IEnumerable<ValidationResult> GetValidationResult(T value);
    IEnumerable<ValidationResult> GetValidationResult(T value, ValidationContext validationContext);
}

Certainly, we don't have to implement all these 8 methods just to make a validator. There are some abstract base classes corresponding to these interfaces to inherit from:

public abstract class BaseValidator : IValidator
{
    public bool IsValid(object value)
    {
        return IsValid(value, null);
    }

    public virtual bool IsValid(object value, ValidationContext validationContext)
    {
        var result = GetValidationResult(value, validationContext);
        return result == null || result.Count() == 0;
    }

    public IEnumerable<ValidationResult> GetValidationResult(object value)
    {
        return GetValidationResult(value, null);
    }

    public abstract IEnumerable<ValidationResult> GetValidationResult(object value, ValidationContext validationContext);
}

public abstract class BaseValidator<T> : BaseValidator, IValidator<T>
{
    public sealed override IEnumerable<ValidationResult> GetValidationResult(object value, ValidationContext validationContext)
    {
        return value != null 
                     ? GetValidationResult((T)value, validationContext) 
                     : GetValidationResult(default(T), validationContext); // That mean T is a reference type
    }

    public bool IsValid(T value)
    {
        return IsValid(value, null);
    }

    public virtual bool IsValid(T value, ValidationContext validationContext)
    {
        var result = GetValidationResult(value, validationContext);
        return result == null || result.Count() == 0;
    }

    public IEnumerable<ValidationResult> GetValidationResult(T value)
    {
        return GetValidationResult(value, null);
    }

    public abstract IEnumerable<ValidationResult> GetValidationResult(T value, ValidationContext validationContext);
}

You can inherit from the normal or generic interface depend on your need, but normally, you just need to override method GetValidationResult to make the custom validator work.

INegatableValidator<T> and BaseNegatableValidator<T>

In order to make your custom validator work with Not(), it's required to make your custom validdator implement interface INegatableValidator<T>. You can simply implement that interface or just inherit from the base class BaseNegatableValidator<T>.
Here is an example:
public class IsNullValidator<T> : BaseNegatableValidator<T> where T : class
{
    public override bool IsValid(T value, ValidationContext validationContext)
    {
        return value == null;
    }

    public override IEnumerable<ValidationResult> GetErrors(T value, ValidationContext validationContext)
    {
        yield return new ValidationResult
        {
            Message = "@PropertyName must be null."
        };
    }

    public override IEnumerable<ValidationResult> GetNegatableErrors(T value, ValidationContext validationContext)
    {
        yield return new ValidationResult
        {
            Message = "@PropertyName must not be null."
        };
    }
}

From the example, instead of implementing method GetValidationResult, we need to override 3 methods: IsValid, GetErrors and GetNegatableErrors.

Other built-in abstract classes


Example:
public sealed class LessThanValidator<T, TProperty> : ComparisonValidator<T, TProperty> where TProperty : IComparable
{
    internal const string Message = "@PropertyName must be less than @ComparisonValue.";

    public LessThanValidator(TProperty value)
        : base(value, Message, GreaterThanOrEqualValidator<T, TProperty>.Message)
    {
    }

    public LessThanValidator(Expression<Func<T, TProperty>> expression)
        : base(expression, Message, GreaterThanOrEqualValidator<T, TProperty>.Message)
    {
    }

    public override bool IsValid(TProperty value, ValidationContext validationContext)
    {
        if (_value == null)
        {
            return value != null && ((IComparable)value).CompareTo(_value) < 0;
        }
        return ((IComparable)_value).CompareTo(value) > 0;
    }
}

Make your custom validator visible in the chain

We'll discuss about the chain and rule builder in other topic. So, at this point, after having a new validator class implemented, we just need to add a new extension method like the example below to make the validator rule visible to use:
public static IPostInitFluentValidationBuilder<T, string> PersonTitle<T>(this IPreInitFluentValidationBuilder<T, string> validationBuilder)
{
    return validationBuilder.Allow("MR", "MS", "MRS", "DR", "PROF", "REV", "OTHER")
                            .WithMessage("@PropertyName is not a valid Person title. Valid titles: MR, MS, MRS, DR, PROF, REV, OTHER.") as IPostInitFluentValidationBuilder<T, string>;

}