using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using System; using System.ComponentModel.DataAnnotations; using Timeline.Helpers; using static Timeline.Resources.Models.Validation.Validator; 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); } /// /// 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, string) Validate(object? value) { if (value == null) { return (false, ValidatorMessageNull); } if (value is T v) { return DoValidate(v); } else { return (false, ValidatorMessageBadType); } } protected static string GetSuccessMessage() => ValidatorMessageSuccess; 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(ValidateWithAttributeExceptionNotValidator, nameof(validatorType)); try { _validator = (Activator.CreateInstance(validatorType) as IValidator)!; } catch (Exception e) { throw new ArgumentException(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); } } } }