using System; using System.ComponentModel.DataAnnotations; namespace Timeline.Models.Validation { /// /// A validator to validate value. /// See . /// public interface IValidator { /// /// Validate given value. /// /// The value to validate. /// The validation message. /// True if validation passed. Otherwise false. bool Validate(object value, out string message); } public static class ValidationConstants { public const string SuccessMessage = "Validation succeeded."; } /// /// Convenient base class for validator. /// /// The type of accepted value. /// /// Subclass should override to do the real validation. /// This class will check the nullity and type of value. If value is null or not of type /// it will return false and not call . /// /// If you want some other behaviours, write the validator from scratch. /// public abstract class Validator : IValidator { public bool Validate(object value, out string message) { if (value == null) { message = "Value is null."; return false; } if (value is T v) { return DoValidate(v, out message); } else { message = $"Value is not of type {typeof(T).Name}"; return false; } } protected abstract bool DoValidate(T value, out string message); } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] public class ValidateWithAttribute : ValidationAttribute { private readonly IValidator _validator; /// /// Create with a given validator. /// /// The validator used to validate. public ValidateWithAttribute(IValidator validator) { _validator = validator ?? throw new ArgumentNullException(nameof(validator)); } /// /// Create the validator with default constructor. /// /// The type of the validator. public ValidateWithAttribute(Type validatorType) { if (validatorType == null) throw new ArgumentNullException(nameof(validatorType)); if (!typeof(IValidator).IsAssignableFrom(validatorType)) throw new ArgumentException("Given type is not assignable to IValidator.", nameof(validatorType)); try { _validator = Activator.CreateInstance(validatorType) as IValidator; } catch (Exception e) { throw new ArgumentException("Failed to create a validator instance from default constructor. See inner exception.", e); } } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { if (_validator.Validate(value, out var message)) return ValidationResult.Success; else return new ValidationResult(string.Format("Field {0} is bad. {1}", validationContext.DisplayName, message)); } } }