using System; using System.Linq; using System.Collections.Concurrent; using System.Collections.Generic; using Unosquare.Swan.Abstractions; namespace Unosquare.Swan.Components { /// /// 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) { ObjectValidationResult errorList = new ObjectValidationResult(); _ = this.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 Boolean IsValid(T obj) => this.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(!this._predicates.TryGetValue(typeof(T), out List> existing)) { existing = new List>(); this._predicates[typeof(T)] = existing; } existing.Add(Tuple.Create((Delegate)predicate, message)); } private Boolean ValidateObject(T obj, Boolean returnOnError = true, Action action = null) { if(Equals(obj, null)) { throw new ArgumentNullException(nameof(obj)); } if(this._predicates.ContainsKey(typeof(T))) { foreach(Tuple validation in this._predicates[typeof(T)]) { if((Boolean)validation.Item1.DynamicInvoke(obj)) { continue; } action?.Invoke(String.Empty, validation.Item2); if(returnOnError) { return false; } } } Dictionary> properties = Runtime.AttributeCache.RetrieveFromType(typeof(IValidator)); foreach(KeyValuePair> prop in properties) { foreach(Object attribute in prop.Value) { IValidator 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 Boolean IsValid => !this.Errors.Any(); /// /// Adds an error with a specified property name. /// /// The property name. /// The error message. public void Add(String propertyName, String errorMessage) => this.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; } } } }