This project is read-only.

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

  • ComparisonValidator<T>: is another abstract class that implemented interface INegatableValidator<T>. Implement this class when you want to create validators for comparison like Equal/NotEqual, GreaterThan/LessThan, etc. The built-in comparison validators are quite enough for all cases but in case you want to add more, this is the point to start.

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;
    }
}
  • ConditionalValidator<T, TProperty>: this is a custom validator that wraps another validator inside and exposes a condition to determine whether to perform validation on the embedded validator. This class is basically created to implement rule When() in the chain.
  • EventValidator<T, TProperty>: this is another custom validator that also wraps another validator inside and exposes 2 events: BeforeValidation and AfterValidation. It allows we add custom logic to intercept the validation rules, modify ValidationContext before validation and modify the validation results after validation.

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>;

}

Last edited Dec 10, 2011 at 11:12 AM by nvthoai, version 3

Comments

No comments yet.