namespace Unosquare.Swan.Components { using System; using System.Linq; using System.Collections.Concurrent; using System.Collections.Generic; using Abstractions; /// /// Represents an object validator. /// /// /// The following code describes how to perform a simple object validation. /// /// using Unosquare.Swan.Components; /// /// class Example /// { /// public static void Main() /// { /// // create an instance of ObjectValidator /// var obj = new ObjectValidator(); /// /// // Add a validation to the 'Simple' class with a custom error message /// obj.AddValidator<Simple>(x => /// !string.IsNullOrEmpty(x.Name), "Name must not be empty"); /// /// // check if object is valid /// var res = obj.IsValid(new Simple { Name = "Name" }); /// } /// /// class Simple /// { /// public string Name { get; set; } /// } /// } /// /// /// The following code shows of to validate an object with a custom validator and some attributes using the Runtime ObjectValidator singleton. /// /// using Unosquare.Swan.Components; /// /// class Example /// { /// public static void Main() /// { /// // create an instance of ObjectValidator /// Runtime.ObjectValidator /// .AddValidator<Simple>(x => /// !x.Name.Equals("Name"), "Name must not be 'Name'"); /// /// // validate object /// var res = Runtime.ObjectValidator /// .Validate(new Simple{ Name = "name", Number = 5, Email ="email@mail.com"}) /// } /// /// class Simple /// { /// [NotNull] /// public string Name { get; set; } /// /// [Range(1, 10)] /// public int Number { get; set; } /// /// [Email] /// public string Email { get; set; } /// } /// } /// /// public class ObjectValidator { private readonly ConcurrentDictionary>> _predicates = new ConcurrentDictionary>>(); /// /// Validates an object given the specified validators and attributes. /// /// The type of the object. /// The object. /// A validation result. public ObjectValidationResult Validate(T obj) { var errorList = new ObjectValidationResult(); ValidateObject(obj, false, errorList.Add); return errorList; } /// /// Validates an object given the specified validators and attributes. /// /// The type. /// The object. /// /// true if the specified object is valid; otherwise, false. /// /// obj. public bool IsValid(T obj) => ValidateObject(obj); /// /// Adds a validator to a specific class. /// /// The type of the object. /// The predicate that will be evaluated. /// The message. /// /// predicate /// or /// message. /// public void AddValidator(Predicate predicate, string message) where T : class { if (predicate == null) throw new ArgumentNullException(nameof(predicate)); if (string.IsNullOrEmpty(message)) throw new ArgumentNullException(message); if (!_predicates.TryGetValue(typeof(T), out var existing)) { existing = new List>(); _predicates[typeof(T)] = existing; } existing.Add(Tuple.Create((Delegate) predicate, message)); } private bool ValidateObject(T obj, bool returnOnError = true, Action action = null) { if (Equals(obj, null)) throw new ArgumentNullException(nameof(obj)); if (_predicates.ContainsKey(typeof(T))) { foreach (var validation in _predicates[typeof(T)]) { if ((bool) validation.Item1.DynamicInvoke(obj)) continue; action?.Invoke(string.Empty, validation.Item2); if (returnOnError) return false; } } var properties = Runtime.AttributeCache.RetrieveFromType(typeof(IValidator)); foreach (var prop in properties) { foreach (var attribute in prop.Value) { var val = (IValidator) attribute; if (val.IsValid(prop.Key.GetValue(obj, null))) continue; action?.Invoke(prop.Key.Name, val.ErrorMessage); if (returnOnError) return false; } } return true; } } /// /// Defines a validation result containing all validation errors and their properties. /// public class ObjectValidationResult { /// /// A list of errors. /// public List Errors { get; set; } = new List(); /// /// true if there are no errors; otherwise, false. /// public bool IsValid => !Errors.Any(); /// /// Adds an error with a specified property name. /// /// The property name. /// The error message. public void Add(string propertyName, string errorMessage) => Errors.Add(new ValidationError {ErrorMessage = errorMessage, PropertyName = propertyName}); /// /// Defines a validation error. /// public class ValidationError { /// /// The property name. /// public string PropertyName { get; set; } /// /// The message error. /// public string ErrorMessage { get; set; } } } }