Custom Validators

Adding custom validators to ValidationAspects is very simple. Validators can either be implemented as validation functions (lambda syntax) or as classes derived from ValidationAspects.Sdk.IValidatorFactory. Validator factories are required for validation that is designed to be applied using attributes. When developing validation attributes you can create a custom validation factory class or use the InlineValidationFactory to simply use a validation function. Ideally you would also implement a custom exception (with custom message formatting) where appropriate.

For specific validation on a model you may prefer to write a validation function - it's a lot less work. Create validation factories and partnering attributes and exceptions for more general use validation such as what the Built-in Validators target.

Validation Functions

Validation functions can be declared in code using lambda syntax.
// PropertyInfo.AddValidation<TValue>(Action<TValue, object>)
typeof(Address).GetProperty("ZipCode").AddValidation<string>((v, c) => 
  { if (!IsKnownZip(v)) { throw new ValidationException("ZipCode is unknown"); } } );
The validation function is the same format as used by validation factories and is described below.

Validator Factory

ValidationAspects uses factories to create validators. Factories are required to build validators for validation target types (Property types, Parameter types etc). This simplest approach to writing a factory is to derive from ValidationAspects.Sdk.ValidatorFactoryBase<TValue> which restricts validation to typeof(TValue) or from ValidationAspects.Sdk.NumericValidatorFactoryBase which restricts validation to numeric types. If these base classes are too restrictive you can implement ValidationAspects.Sdk.IValidatorFactory directly. View the Built-in Validators such as the NotEqual validator factory for examples of how to do this.

Examples:
public class NotNullOrEmpty : ValidatorFactoryBase<string>
{
  protected override Action<string, IValidationContext> ValidateAction
  {
    get { return (v, c) => { if (string.IsNullOrEmpty(v)) throw new ValidateNotNullOrEmptyException(); }; }
  }
}
You need to return an Action - this is the validator. When invoked, the action will be passed v, the value to validate, and c, the validation context. The context provides information on the Instance/Type/Property/Method/Parameter being validated.

public class OverdraftValidatorFactory : ValidatorFactoryBase<decimal>
{
  public decimal Limit { get; set; }

  protected override Action<decimal, IValidationContext> ValidateAction
  {
    get
    {
      return (v, c) =>
        {
          Account account = c.Instance as Account;
          if (account == null)
            return;
          if (account.Balance - v < (Limit * -1))
            throw new ValidationException(string.Format("Cannot withdraw {0}. Your overdraft is {1}", v, Limit));
        };
    }
  }
}
The above example demonstrates how the context c can be used by a validator which needs to query the state of the supplied instance. This particular example maybe somewhat controversial as an overdraft facility should probably be a feature of the Account model and not validation, but it does demonstrate how to build "business logic" style validation.

Your validator factory will need to have a parameterless constructor to be referenced in xaml configuration.

Attributes

Example:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public sealed class MyValidationAttribute : ValidatorAttribute
{
  public override IValidatorFactory Factory { get { return new MyValidation(); } }
}

Exceptions

If providing a custom exception, it must derive from ValidationAspects.ValidationException. To support custom message formatting as provided by the framework you need to implement the following pattern and implement an extension method for ValidationAspects.IFormatExceptionMessage so that exception message customization is centrally located.
Examples:
public class ValidateNotNullException : ValidationException
{
  public static Func<ValidateNotNullException, string> MessageFormat { get; set; }

  static ValidateNotNullException()
  {
    ResetMessageFormat();
  }

  public static void ResetMessageFormat()
  {
    MessageFormat = e => "Value must not be null.";
  }

  public override string Message
  {
    get { return MessageFormat(this); }
  }
}
public static void NotNull(this IFormatExceptionMessage formatExceptionMessage, 
                                     Func<ValidateNotNullException, string> formatMessage)
{
  ValidateNotNullException.MessageFormat = formatMessage;
}
Format.ExceptionMessage.LessThan(e => string.Format("{0} must be less than {1}", e.Value, e.Limit) );

Last edited Feb 3, 2009 at 7:35 PM by mikesaunders, version 12

Comments

No comments yet.