using System; using System.ComponentModel.DataAnnotations; namespace Timeline.Models.Validation { /// /// A validator to validate value. /// public interface IValidator { /// /// Validate given value. /// /// The value to validate. /// Validation success or not and message. (bool, string) Validate(object? value); } public static class ValidatorExtensions { public static bool Validate(this IValidator validator, object? value, out string message) { if (validator == null) throw new ArgumentNullException(nameof(validator)); var (r, m) = validator.Validate(value); message = m; return r; } } /// /// 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, it will pass or fail depending on . /// If value is not null and not of type /// it will fail and not call . /// /// is true by default. /// /// If you want some other behaviours, write the validator from scratch. /// public abstract class Validator : IValidator { protected bool PermitNull { get; set; } = true; public (bool, string) Validate(object? value) { if (value is null) { if (PermitNull) return (true, GetSuccessMessage()); else return (false, Resource.CantBeNull); } if (value is T v) { return DoValidate(v); } else { return (false, string.Format(Resource.NotOfType, typeof(T).Name)); } } protected static string GetSuccessMessage() => Resource.ValidationPassed; protected abstract (bool, string) DoValidate(T value); } [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(Resource.ValidateWithAttributeExceptionNotValidator, nameof(validatorType)); try { _validator = (Activator.CreateInstance(validatorType) as IValidator)!; } catch (Exception e) { throw new ArgumentException(Resource.ValidateWithAttributeExceptionCreateFail, e); } } protected override ValidationResult IsValid(object? value, ValidationContext validationContext) { var (result, message) = _validator.Validate(value); if (result) { return ValidationResult.Success!; } else { return new ValidationResult(message); } } } }