using FluentValidation; using FluentValidation.Results; using System.Collections.Concurrent; namespace zero.Validation; public class ZeroMergedValidator : IZeroMergedValidator { protected IEnumerable> Validators { get; private set; } ConcurrentDictionary>> TypeCache = new(); public ZeroMergedValidator(IEnumerable> validators) { Validators = validators; } /// public async Task ValidateAsync(T model) { ValidationResult result = new(); foreach (IValidator validator in ResolveFor(model)) { ValidationResult innerResult = await validator.ValidateAsync(model); result.Errors.AddRange(innerResult.Errors); } return result; } /// public IEnumerable> ResolveFor(T model) { Type type = model.GetType(); if (!TypeCache.TryGetValue(type, out IEnumerable> validators)) { validators = Validators.Where(validator => CanHandle(validator, type)).ToList(); TypeCache.TryAdd(type, validators); } return validators; } /// /// Checks whether a certain validator can handle the give type /// bool CanHandle(IValidator validator, Type modelType) { Type validatorType = validator.GetType(); Type typeToFind = typeof(ZeroValidator<,>); Type findValidatorBase(Type type) { if (type.BaseType == null) { return null; } if (type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() == typeToFind) { return type.BaseType; } return findValidatorBase(type.BaseType); } Type zeroValidatorType = findValidatorBase(validatorType); if (zeroValidatorType == null) { return false; } Type implementationType = zeroValidatorType.GenericTypeArguments[1]; return implementationType.IsAssignableFrom(modelType); } } public interface IZeroMergedValidator { /// /// Get all validators which can run for the given model /// IEnumerable> ResolveFor(T model); /// /// Validates a model by using all registered ZeroValidators for this entity type /// Task ValidateAsync(T model); }