using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using System; using System.ComponentModel.DataAnnotations; using Timeline.Helpers; namespace Timeline.Models.Validation { /// /// Generate a message from a localizer factory. /// If localizerFactory is null, it should return a neutral-cultural message. /// /// The localizer factory. Could be null. /// The message. public delegate string ValidationMessageGenerator(IStringLocalizerFactory? localizerFactory); /// /// A validator to validate value. /// public interface IValidator { /// /// Validate given value. /// /// The value to validate. /// Validation success or not and the message generator. (bool, ValidationMessageGenerator) 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, ValidationMessageGenerator) Validate(object? value) { if (value == null) { return (false, factory => factory?.Create("Models.Validation.Validator")?["ValidatorMessageNull"] ?? Resources.Models.Validation.Validator.InvariantValidatorMessageNull ); } if (value is T v) { return DoValidate(v); } else { return (false, factory => factory?.Create("Models.Validation.Validator")?["ValidatorMessageBadType", typeof(T).FullName] ?? Resources.Models.Validation.Validator.InvariantValidatorMessageBadType); } } protected static ValidationMessageGenerator SuccessMessageGenerator { get; } = factory => factory?.Create("Models.Validation.Validator")?["ValidatorMessageSuccess"] ?? Resources.Models.Validation.Validator.InvariantValidatorMessageSuccess; protected abstract (bool, ValidationMessageGenerator) 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( Resources.Models.Validation.Validator.ValidateWithAttributeNotValidator, nameof(validatorType)); try { _validator = (Activator.CreateInstance(validatorType) as IValidator)!; } catch (Exception e) { throw new ArgumentException( Resources.Models.Validation.Validator.ValidateWithAttributeCreateFail, e); } } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var (result, messageGenerator) = _validator.Validate(value); if (result) { return ValidationResult.Success; } else { var localizerFactory = validationContext.GetRequiredService(); return new ValidationResult(messageGenerator(localizerFactory)); } } } }