FluentValidator, как валадировать св-во во втором валидаторе, исходя из первого валидатора?

Рейтинг: 4Ответов: 2Опубликовано: 16.05.2023

Допустим, что есть такие интерфейсы и классы их реализующие:

class FirstTest : IHasId, IHasGuid
{
    public int Id { get; set; }

    public Guid Guid { get; set; }
}

class SecondTest : IHasId, IHasString
{
    public int Id { get; set; }

    public string String { get; set; }
}

interface IHasId
{
    int Id { get; set; }
}

interface IHasGuid
{
    Guid Guid { get; set; }
}

interface IHasString
{
    string String { get; set; }
}

Вопрос, как сделать общий валидатор для IHasId, чтобы его можно было использовать так, что если валидатор для IHasId не смог найти значение, например, в БД, то валидация для IHasGuid или IHasString не использовалась?
Не хочется писать один и тот же валидатор для 2-х разных классов, где будет одинаковый метод валидации Id.
Как бы хотелось видеть валидаторы:

class SecondTestValidator : AbstractValidator<SecondTest>
{
    public SecondTestValidator()
    {
        Include(new IdValidator()).DependentRules(idValidator =>
        {
            if (idValidator.IdValid)
            {
                RuleFor(x => x.String).NotEmpty().MinimumLength(20).MaximumLength(50).Matches("[a-z]");
            }
        });
    }
}

class FirstTestValidator : AbstractValidator<FirstTest>
{
    public FirstTestValidator()
    {
        Include(new IdValidator()).DependentRules(idValidator =>
        {
            if (idValidator.IdValid)
            {
                RuleFor(x => x.Guid).NotEmpty();
            }
        });
    }
}

Но Include возвращает void.

Такой вариант тоже не сработал, проверка вызывается в любом случае:

class FirstTestValidator : AbstractValidator<FirstTest>
{
    public FirstTestValidator()
    {
        Include(new IdValidator(() => RuleFor(x => x.Guid).NotEmpty());
    }
}

class IdValidator : AbstractValidator<IHasId>
{
    public IdValidator(Action whenValid)
    {
        RuleFor(x => x.Id).Must(x => x > 10).DependentRules(whenValid);
    }
}

Ответы

▲ 2Принят

Примерное решение - использовать расширения для валидации полей.
Как это примерно выглядит:

  1. Создать расширения для нужных полей (можно передавать DbContext, UoW, etc...)
  2. В каждом валидаторе просто вешать правило валидации для нужного поля
internal static class RuleExtensions
{
    public static IRuleBuilderOptions<T, int> ValidateId<T>(this IRuleBuilder<T, int> builder)
        where T : IHasId
    {
        return builder.Must((t, x) => t.Id > 5);
    }
}

И создаем валидатор:

class FirstTestValidator : AbstractValidator<FirstTest>
{
    public FirstTestValidator()
    {
        RuleFor(x => x.Id).ValidateId().DependentRules(() =>
        {
            RuleFor(x => x.Guid).NotEmpty();
        });
    }
}

Таким образом валидация для поля Guid будет проводиться только если пройдет валидация для Id.

▲ 0
class SecondTestValidator : AbstractValidator<SecondTest>
{
    public SecondTestValidator()
    {
        Include(new IdValidator());

        RuleFor(x => x.String)
            .NotEmpty()
            .MinimumLength(20)
            .MaximumLength(50)
            .Matches("[a-z]")
            .When(x => ((IdValidator)x).IdValid);
    }
}

class FirstTestValidator : AbstractValidator<FirstTest>
{
    public FirstTestValidator()
    {
        Include(new IdValidator());

        RuleFor(x => x.Guid)
            .NotEmpty()
            .When(x => ((IdValidator)x).IdValid);
    }
}

class IdValidator : AbstractValidator<IHasId>
{
    public bool IdValid { get; private set; }

    public IdValidator()
    {
        RuleFor(x => x.Id)
            .Must(x =>
            {
                
                bool isValid = // проверка
                IdValid = isValid;
                return isValid;
            })
            .WithMessage("Некорректное значение Id");
    }
}