Login using System.Noticeably.Different.WebSite;
February 05, 2012 NoticeablyDifferent
Search for
 
Decorating Types For Validation 
The goal of this sample is to resolve the issues outlined with the various validation styles I have used and reviewed. This model is a hybrid of the various types and, I believe, brings together all the advantages of each style.
 
Child types inherit the validation of its parent and need only implement validation for its own types. Validation rules can be generalized and re-used. Validation messages can be retrieved from resource files, thus making this solution localizable.
 
Update: Added type conversion operators to Validatable.
 
public interface IValidatable {
    void Validate();
}
 
[Serializable]
public sealed class Validatable<T> : IValidatable {
    ValidationHandler<T> validationRule = null;
 
    public Validatable() : this(default(T), null) { }
    public Validatable(T value) : this(value, null) { }
    public Validatable(ValidationHandler<T> validationRule) : this(default(T), validationRule) { }
    public Validatable(T value, ValidationHandler<T> validationRule) {
        Value = value;
        this.validationRule = validationRule;
    }
 
    public ValidationHandler<T> ValidationRule {
        set { validationRule = value; }
    }
 
    public T Value { get; set; }
 
    public void Validate() {
        if (validationRule != null) {
          validationRule(value);
        }
    }
 
    public static implicit operator Validatable<T>(T value) {
        return new Validatable(value);
    }
 
    public static implicit operator T(Validatable<T> value) {
      return value.Value;
    }
 
    public override string ToString() {
        return value.ToString();
    }
}
 
public delegate void ValidationHandler<T>(T value);
 
public class ValidationResult {
    readonly IDictionary<object, IList<ValidationViolation>> itemValidationViolations;
    readonly List<ValidationViolation> violations = new List<ValidationViolation>();
 
    public ValidationResult(IDictionary<object, IList<ValidationViolation>> itemValidationViolations) {
        if (itemValidationViolations == null) throw new ArgumentNullException("itemValidationViolations");
        this.itemValidationViolations = new Dictionary<object, IList<ValidationViolation>>(itemValidationViolations);
        foreach (var listOfViolations in itemValidationViolations.Values) {
            foreach (var violation in listOfViolations) {
                violations.Add(violation);
            }
        }
    }
 
    public bool IsValid {
        get { return violations.Count == 0; }
    }
 
    public IList<ValidationViolation> Violations {
        get { return violations.AsReadOnly(); }
    }
 
    public IDictionary<object, IList<ValidationViolation>> ViolationsByItem {
        get { return itemValidationViolations; }
    }
}
 
public class ValidationViolation {
    readonly string fieldName, explanation;
 
    public ValidationViolation(string fieldName, string explanation) {
        if (String.IsNullOrEmpty(fieldName)) throw new ArgumentNullException("fieldName");
        if (String.IsNullOrEmpty(explanation)) throw new ArgumentNullException("explanation");
            this.fieldName = fieldName;
            this.explanation = explanation;
    }
 
    public string FieldName {
        get { return fieldName; }
    }
 
    public string Explanation {
        get { return explanation; }
    }
}
 
[Serializable]
public class ValidationViolationException : Exception {
    public ValidationViolationException() { }
    public ValidationViolationException(string message) : base(message) { }
    public ValidationViolationException(string message, Exception innerException) : base(message, innerException) { }
    public ValidationViolationException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { }
}
 
public static class Validator {
    public static ValidationResult Validate(params object[] items) {
        return Validate(new List<object>(items));
    }
 
    public static ValidationResult Validate(IList<object> items) {
        IDictionary<object, IList<ValidationViolation>> objectValidationViolations = new Dictionary<object, IList<ValidationViolation>>();
        foreach (var item in items) {
                objectValidationViolations.Add(item, Validate(item));
        }
        return new ValidationResult(objectValidationViolations);
    }
 
    public static IList<ValidationViolation> Validate(object item) {
        IList<ValidationViolation> validationViolations = new List<ValidationViolation>();
        foreach (var property in item.GetType().GetProperties()) {
            if (property.CanRead && property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Validatable<>)) {
                try {
                    ((IValidatable)property.GetValue(item, null)).Validate();
                } catch (ValidationViolationException exception) {
                    validationViolations.Add(new ValidationViolation(property.Name, exception.Message));
                }
            }
        }
        return validationViolations;
    }
}