using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using System; using System.ComponentModel.DataAnnotations; using Timeline.Helpers; 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, IStringLocalizerFactory localizerFactory, 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 { [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "")] public bool Validate(object? value, IStringLocalizerFactory localizerFactory, out string message) { if (value == null) { var localizer = localizerFactory.Create("Models.Validation.Validator"); message = localizer["ValidatorMessageNull"]; return false; } if (value is T v) { return DoValidate(v, localizerFactory, out message); } else { var localizer = localizerFactory.Create("Models.Validation.Validator"); message = localizer["ValidatorMessageBadType", typeof(T).FullName]; return false; } } [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods")] protected static string GetSuccessMessage(IStringLocalizerFactory factory) { var localizer = factory.Create("Models.Validation.Validator"); return localizer["ValidatorMessageSuccess"]; } protected abstract bool DoValidate(T value, IStringLocalizerFactory localizerFactory, 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( 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 localizerFactory = validationContext.GetRequiredService(); if (_validator.Validate(value, localizerFactory, out var message)) return ValidationResult.Success; else return new ValidationResult(message); } } }