|
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.
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();
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();
if (String.IsNullOrEmpty(explanation)) throw new ArgumentNullException();
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;
}
}
|