这是我想出来的,主要问题是表达式需要静态类型,所以你不能为所有类型隐式定义规则。
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));
}
}