【问题标题】:Generic Validator for Model T Using Fluent Validation?使用 Fluent Validation 的 Model T 通用验证器?
【发布时间】:2018-03-05 10:46:15
【问题描述】:

我昨天刚接触到 Fluent Validation,我觉得它很酷。我已经尝试过了,它有效。但是我的应用程序目前有几个模型,我必须承认为每个模型编写验证器是有压力的。是否可以用泛型编写它并找到一种方法来验证每个模型?

这就是我的验证器当前的编写方式。但是我不知道怎么用泛型写。

EmployeeValidator.cs

public class EmployeeValidator : AbstractValidator<EmployeeViewModel>
{
    private readonly ValidationEntitySettingServices _validationEntitySettingService;

    public EmployeeValidator(ValidationEntitySettingServices validationEntitySettingService)
    {
        _validationEntitySettingService = validationEntitySettingService;

        RuleFor(x => x.LastName).NotEmpty().When(x => IsPropertyRequired(x.LastName)).WithMessage("Last Name is a required field!");
        RuleFor(x => x.FirstName).NotEmpty().When(x => IsPropertyRequired(x.FirstName)).WithMessage("First Name is a required field!");
        RuleFor(x => x.MiddleName).NotEmpty().When(x => IsPropertyRequired(x.MiddleName)).WithMessage("Middle Name is a required field!");
    }

    public bool IsPropertyRequired(string propertyName)
    {
        var empValidationSetting = _validationEntitySettingService.GetIncluding("Employee");
        if (empValidationSetting != null)
        {
            return empValidationSetting.ValidationEntityProperties.Any(p => p.PropertyName.Equals(propertyName) && p.IsRequired);
        }
        else
            return false;
    }
}

提前致谢。

【问题讨论】:

    标签: c# generics fluentvalidation


    【解决方案1】:

    我认为将验证设为通用是没有意义的,因为您的所有模型都可能具有不同的属性和需要以不同方式验证的属性类型。但是,您可以通过添加基本验证类来使您在此处拥有的内容更加通用,例如:

    public abstract class BaseValidator<T> : AbstractValidator<T>
    {
        private readonly ValidationEntitySettingServices _validationEntitySettingService;
    
        public BaseValidator(ValidationEntitySettingServices validationEntitySettingService)
        {
            _validationEntitySettingService = validationEntitySettingService;
            AutoApplyEmptyRules();
        }
    
        private string ViewModelName
        {
            get { return GetType().Name.Replace("Validator", string.Empty); }
        }
    
        // no longer needed
        //public bool IsPropertyRequired(string propertyName)
        //{
        //    var validationSetting = _validationEntitySettingService.GetIncluding(ViewModelName);
        //    if (validationSetting != null)
        //    {
        //        return validationSetting.ValidationEntityProperties.Any(p => p.PropertyName.Equals(propertyName) && p.IsRequired);
        //    }
        //    else
        //        return false;
        //}
    
        protected void AddEmptyRuleFor<TProperty>(Expression<Func<T, TProperty>> expression, string message)
        {
            //RuleFor(expression).NotEmpty().When(x => IsPropertyRequired(((MemberExpression)expression.Body).Name)).WithMessage(message);
            RuleFor(expression).NotEmpty().WithMessage(message);
        }
    
        private void AddEmptyRuleForProperty(PropertyInfo property)
        {
            MethodInfo methodInfo = GetType().GetMethod("AddEmptyRuleFor");
            Type[] argumentTypes = new Type[] { typeof(T), property.PropertyType };
            MethodInfo genericMethod = methodInfo.MakeGenericMethod(argumentTypes);
            object propertyExpression = ExpressionHelper.CreateMemberExpressionForProperty<T>(property);
            genericMethod.Invoke(this, new object[] { propertyExpression, $"{propertyInfo.Name} is a required field!" });
        }
    
        private PropertyInfo[] GetRequiredProperties()
        {
            var validationSetting = _validationEntitySettingService.GetIncluding(ViewModelName);
            if (validationSetting != null)
            {
                return validationSetting.ValidationEntityProperties.Where(p => p.IsRequired);
            }
            else
                return null;
        }
    
        private void AutoApplyEmptyRules()
        {
            PropertyInfo[] properties = GetRequiredProperties();
            if (properties == null)
                return;
            foreach (PropertyInfo propertyInfo in properties)
            {
                AddEmptyRuleForProperty(property);
            }
        }
    }
    

    这里的主要要求是 AddEmptyRuleForProperty 方法,它将通过基于 PropertyType 构造方法来调用通用的 AddEmptyRuleFor 方法。

    然后你可以继承这个类并使用泛型方法应用规则:

    public class EmployeeValidator : BaseValidator<EmployeeViewModel>
    {
        public EmployeeValidator(ValidationEntitySettingServices validationEntitySettingService) : base(validationEntitySettingService)
        {
            // no longer needed
            //AddEmptyRuleFor(x => x.LastName, "Last Name is a required field!");
            //AddEmptyRuleFor(x => x.FirstName, "First Name is a required field!");
            //AddEmptyRuleFor(x => x.MiddleName, "Middle Name is a required field!");
        }
    }
    

    这是 ExpressionHelper 类,它提供了一种方法来创建一个通用成员表达式作为一个对象,当调用上面的 AddEmptyRuleFor 方法时可以传入该对象:

    public static class ExpressionHelper
    {
        public static Expression<Func<TModel, TProperty>> CreateMemberExpression<TModel, TProperty>(PropertyInfo propertyInfo)
        {
            if (propertyInfo == null)
                throw new ArgumentException("Argument cannot be null", "propertyInfo");
    
            ParameterExpression entityParam = Expression.Parameter(typeof(TModel), "x");
            Expression columnExpr = Expression.Property(entityParam, propertyInfo);
    
            if (propertyInfo.PropertyType != typeof(T))
                columnExpr = Expression.Convert(columnExpr, typeof(T));
    
            return Expression.Lambda<Func<TModel, TProperty>>(columnExpr, entityParam);
        }
    
        public static object CreateMemberExpressionForProperty<TModel>(PropertyInfo property)
        {
            MethodInfo methodInfo = typeof(ExpressionHelper).GetMethod("CreateMemberExpression", BindingFlags.Static | BindingFlags.Public);
            Type[] argumentTypes = new Type[] { typeof(TModel), property.PropertyType };
            MethodInfo genericMethod = methodInfo.MakeGenericMethod(argumentTypes);
            return genericMethod.Invoke(null, new object[] { property });
        }
    }
    

    【讨论】:

    • 谢谢史蒂夫。这看起来正是我需要的。但是如果我可能会问,AddEmptyRuleFor 方法是否可以自动运行类的所有属性,因为我可以从validationSetting.ValidationEntityProperties 集合中获取它们?现在,我需要通用验证器做的是仅通过检查数据库中的设置是否设置为 true 或 false 来验证必填字段?我希望你明白我在说什么。
    • 是的,它有点复杂,并且消息不会在您的属性名称中包含空格,但我已经用可能的解决方案更新了答案。我已经注释掉了不需要的原始代码。
    • 哇!谢谢史蒂夫。这可能需要我一段时间才能理解。但我很想尝试一下,看看它是如何工作的。非常感谢。
    • 您好,请您解释一下何时何地调用此方法AutoApplyEmptyRules()。我注意到它既没有被代码中的任何类或方法调用。我的猜测是,该方法将在实现BaseValidatorEntityValidator 的构造函数中调用。那正确吗?再次感谢您的帮助。
    • @er-sho 不知道。这是在 OPs 问题中
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-26
    • 1970-01-01
    • 2023-02-22
    • 1970-01-01
    • 2015-11-05
    • 2011-02-20
    • 1970-01-01
    相关资源
    最近更新 更多