From ffd76303712240358e0f395a26778db1e1963ff9 Mon Sep 17 00:00:00 2001 From: Dav Evans Date: Mon, 28 Jul 2014 22:06:28 +1000 Subject: [PATCH] Add common validation --- src/Church/Church.Common/Church.Common.csproj | 3 + .../Validation/ValidateChildAttribute.cs | 23 ++++ .../Validation/ValidationResults.cs | 17 +++ .../Church.Common/Validation/Validator.cs | 120 ++++++++++++++++++ 4 files changed, 163 insertions(+) create mode 100644 src/Church/Church.Common/Validation/ValidateChildAttribute.cs create mode 100644 src/Church/Church.Common/Validation/ValidationResults.cs create mode 100644 src/Church/Church.Common/Validation/Validator.cs diff --git a/src/Church/Church.Common/Church.Common.csproj b/src/Church/Church.Common/Church.Common.csproj index a689a75..053e82e 100644 --- a/src/Church/Church.Common/Church.Common.csproj +++ b/src/Church/Church.Common/Church.Common.csproj @@ -59,6 +59,9 @@ + + + diff --git a/src/Church/Church.Common/Validation/ValidateChildAttribute.cs b/src/Church/Church.Common/Validation/ValidateChildAttribute.cs new file mode 100644 index 0000000..166e295 --- /dev/null +++ b/src/Church/Church.Common/Validation/ValidateChildAttribute.cs @@ -0,0 +1,23 @@ +using System; + +namespace Church.Common.Validation +{ + [AttributeUsage(AttributeTargets.Property)] + public class ValidateChildAttribute : Attribute + { + public bool Required { get; set; } + public string ErrorMessage { get; set; } + + public ValidateChildAttribute(bool required, string errorMessage) + { + Required = required; + ErrorMessage = errorMessage; + } + + public ValidateChildAttribute() + : this(true, "Is required.") + { + + } + } +} \ No newline at end of file diff --git a/src/Church/Church.Common/Validation/ValidationResults.cs b/src/Church/Church.Common/Validation/ValidationResults.cs new file mode 100644 index 0000000..9cd43ae --- /dev/null +++ b/src/Church/Church.Common/Validation/ValidationResults.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; + +namespace Church.Common.Validation +{ + public class ValidationResults + { + public bool IsValid { get; set; } + public IList Errors { get; set; } + + public IEnumerable ErrorStrings + { + get { return Errors != null ? Errors.Select(x => x.ErrorMessage).ToList() : Enumerable.Empty(); } + } + } +} diff --git a/src/Church/Church.Common/Validation/Validator.cs b/src/Church/Church.Common/Validation/Validator.cs new file mode 100644 index 0000000..926a6e5 --- /dev/null +++ b/src/Church/Church.Common/Validation/Validator.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; + +namespace Church.Common.Validation +{ + public static class Validator + { + + private class ValidationProperty + { + public bool IsClassValidator; + public PropertyInfo PropertyInfo; + public ValidateChildAttribute Child; + public ValidationAttribute[] Validations; + } + + private static readonly ConcurrentDictionary> Cache = new ConcurrentDictionary>(); + public static bool Validate(object toValidate, IList results) + { + var ctx = new ValidationContext(toValidate); + return Validate(toValidate, ctx, results); + } + + public static ValidationResults Validate(object o) + { + var validationResults = new ValidationResults(); + var list = validationResults.Errors = new List(); + validationResults.IsValid = Validate(o, list); + return validationResults; + } + + private static ValidationAttribute[] GetPropertyValidator(PropertyInfo property) + { + return (ValidationAttribute[])property.GetCustomAttributes(typeof(ValidationAttribute), true); + } + + public static bool Validate(object instance, ValidationContext validationContext, IList validationResults) + { + bool isError = false; + List validationProperties = null; + + var type = instance.GetType(); + + if (!Cache.TryGetValue(type, out validationProperties)) + { + //not found in cache + var properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + validationProperties = properties.Select(p => new ValidationProperty + { + PropertyInfo = p, + Child = (ValidateChildAttribute)p.GetCustomAttributes(typeof(ValidateChildAttribute), true).FirstOrDefault(), + Validations = GetPropertyValidator(p) + }).ToList(); + + + var classLevelValidators = (ValidationAttribute[])type.GetCustomAttributes(typeof (ValidationAttribute), true); + if (classLevelValidators.Any()) + { + validationProperties.Add(new ValidationProperty + { + IsClassValidator = true, + PropertyInfo = null, + Child = null, + Validations = classLevelValidators + }); + } + + Cache.TryAdd(type, validationProperties.Any() ? validationProperties : null); + } + + foreach (var validationProperty in validationProperties) + { + object objectValue = validationProperty.IsClassValidator + ? instance + : validationProperty.PropertyInfo.GetValue(instance, null); + + if (validationProperty.Validations != null) + { + foreach (var validation in validationProperty.Validations) + { + var result = validation.GetValidationResult(objectValue, validationContext); + if (result != null) + { + isError = true; + validationResults.Add(result); + break; + } + } + } + + if (validationProperty.Child != null) + { + if (objectValue != null && validationProperty.Child.Required) + { + isError = true; + validationResults.Add(new ValidationResult(validationProperty.Child.ErrorMessage)); + }else if (objectValue != null) + { + + validationContext = new ValidationContext(objectValue, null, null); + var innerIsValid = Validate(objectValue, validationContext, validationResults); + if (!innerIsValid) + { + isError = true; + } + } + } + } + + return !isError; + } + + + } +}