【问题标题】:All properties notempty except a few除了少数之外,所有属性都不是空的
【发布时间】:2021-04-26 14:05:06
【问题描述】:

我是使用FluentValidation 的新手,想知道是否可以为模型上的所有属性设置相同的规则,除了少数属性。

我有一系列具有 20 多个属性的模型,除了一对之外,这些属性都不应该为空。

我相信 RuleFor(x=>x).NotEmpty() 应该适用于所有属性,但我如何确保忽略 2 个属性?

提前致谢,

【问题讨论】:

  • 我不相信 FluentValidation 支持这一点。
  • 是的,我通过堆栈上的一些建议解决了问题,但这并不是最佳解决方案。

标签: c# .net fluentvalidation


【解决方案1】:

这是我想出来的,主要问题是表达式需要静态类型,所以你不能为所有类型隐式定义规则。

public interface IPropertiesValidator<T>
{
    void RuleForType<TProperty>(Action<IRuleBuilder<T, TProperty>> action, CascadeMode cascadeMode = CascadeMode.Continue);
}

internal class NotNullOrEmptyPropertiesValidator<T> : AbstractValidator<T>, IPropertiesValidator<T>
{
    private readonly List<PropertyInfo> properties;

    internal NotNullOrEmptyPropertiesValidator(Func<PropertyInfo, bool> filter, Action<IPropertiesValidator<T>> customize = null)
    {
        // get the properties the notnullorempty should apply to
        properties = typeof(T)
           .GetProperties()
           .Where(filter)
           .ToList();

        var message = "{PropertyName} of {PropertyType} type cannot be null or empty";

        // we need to explicity support a type because of the generic way the validators are set on FluentValidation
        // potentially there maybe another way but couldn't figure it out, the advantage is that we can handle both nullables or not
        RuleForType<string>(x => x.NotEmpty().WithMessage(message));

        RuleForType<sbyte>(x => x.NotEmpty().WithMessage(message));
        RuleForType<byte>(x => x.NotEmpty().WithMessage(message));
        RuleForType<short>(x => x.NotEmpty().WithMessage(message));
        RuleForType<ushort>(x => x.NotEmpty().WithMessage(message));
        RuleForType<int>(x => x.NotEmpty().WithMessage(message));
        RuleForType<uint>(x => x.NotEmpty().WithMessage(message));
        RuleForType<long>(x => x.NotEmpty().WithMessage(message));
        RuleForType<ulong>(x => x.NotEmpty().WithMessage(message));
        RuleForType<float>(x => x.NotEmpty().WithMessage(message));
        RuleForType<double>(x => x.NotEmpty().WithMessage(message));
        RuleForType<decimal>(x => x.NotEmpty().WithMessage(message));
        RuleForType<DateTime>(x => x.NotEmpty().WithMessage(message));

        RuleForType<sbyte?>(x => x.NotNull().NotEmpty().WithMessage(message), CascadeMode.Stop);
        RuleForType<byte?>(x => x.NotNull().NotEmpty().WithMessage(message), CascadeMode.Stop);
        RuleForType<short?>(x => x.NotNull().NotEmpty().WithMessage(message), CascadeMode.Stop);
        RuleForType<ushort?>(x => x.NotNull().NotEmpty().WithMessage(message), CascadeMode.Stop);
        RuleForType<int?>(x => x.NotNull().NotEmpty().WithMessage(message), CascadeMode.Stop);
        RuleForType<uint?>(x => x.NotNull().NotEmpty().WithMessage(message), CascadeMode.Stop);
        RuleForType<long?>(x => x.NotNull().NotEmpty().WithMessage(message), CascadeMode.Stop);
        RuleForType<ulong?>(x => x.NotNull().NotEmpty().WithMessage(message), CascadeMode.Stop);
        RuleForType<float?>(x => x.NotNull().NotEmpty().WithMessage(message), CascadeMode.Stop);
        RuleForType<double?>(x => x.NotNull().NotEmpty().WithMessage(message), CascadeMode.Stop);
        RuleForType<decimal?>(x => x.NotNull().NotEmpty().WithMessage(message), CascadeMode.Stop);
        RuleForType<DateTime?>(x => x.NotNull().NotEmpty().WithMessage(message), CascadeMode.Stop);

        // allows for custom types rules to be set when creating the validator
        customize?.Invoke(this);
    }

    public void RuleForType<TProperty>(Action<IRuleBuilder<T, TProperty>> action, CascadeMode cascadeMode = CascadeMode.Continue)
    {
        foreach (var prop in properties
            .Where(x => x.PropertyType == typeof(TProperty)))
        {
            var expression = CreateExpression<TProperty>(prop);
            var ruleBuilder = RuleFor(expression)
                .Cascade(cascadeMode)
                .Custom((_, context) =>
                {
                    context.MessageFormatter
                        .AppendArgument("PropertyType", typeof(TProperty).Name); // TODO: this is not working
                });
            action.Invoke(ruleBuilder);
        }
    }

    private Expression<Func<T, TProperty>> CreateExpression<TProperty>(PropertyInfo propertyInfo)
    {
        var param = Expression.Parameter(typeof(T));
        return Expression.Lambda<Func<T, TProperty>>(
            Expression.Property(param, propertyInfo), param);
    }
}

public static class NotNullOrEmptyPropertiesValidatorExtensions
{
    /// <summary>
    /// Sets a single <see cref="NotNullOrEmptyPropertiesValidator{T}"/> for all the <typeparamref name="TElement"/> in the collection.
    /// </summary>
    /// <typeparam name="T">The root type</typeparam>
    /// <typeparam name="TElement">The collection element type</typeparam>
    /// <param name="ruleBuilder">The rule builder</param>
    /// <param name="filter">The filter for the properties that this validator should apply</param>
    /// <param name="customize">Optional custom property return type rules to be set</param>
    /// <returns></returns>
    public static IRuleBuilderOptions<T, IEnumerable<TElement>> ForEachNotNullOrEmptyProperties<T, TElement>(this IRuleBuilder<T, IEnumerable<TElement>> ruleBuilder, 
        Func<PropertyInfo, bool> filter, Action<IPropertiesValidator<TElement>> customize = null)
    {
        var validator = new NotNullOrEmptyPropertiesValidator<TElement>(filter, customize);
        return ruleBuilder.ForEach(x => x.SetValidator(validator));
    }
}

【讨论】:

    猜你喜欢
    • 2010-11-28
    • 1970-01-01
    • 1970-01-01
    • 2013-10-24
    • 2017-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-31
    相关资源
    最近更新 更多