FluentValidation Устанавливает допустимый результат в Пользовательское свойство

#.net-5 #fluentvalidation

Вопрос:

Я проверяю содержимое для импорта файла, и у меня есть IsValid свойство для каждой строки.

 public class Header 
{
    public int LineNumber { get; set; }
    public string Property1 { get; set; }
    public string Property2 { get; set; }
    public bool IsValid { get; set; }
}

public class Detail
{
    public int LineNumber { get; set; }
    public string Property1 { get; set; }
    public string Property2 { get; set; }
    public string Property3 { get; set; }
    public bool IsValid { get; set; }
}

public class Trailer
{
    public int LineNumber { get; set; }
    public string Property1 { get; set; }
    public bool IsValid { get; set; }
}

public class ImportFile
{
    public Header Header { get; set; }
    public List<Detail> Details { get; set; }
    public Trailer Trailer { get; set; }
}
 

и мои валидаторы выглядят примерно так:

 public class DetailValidator : AbstractValidator<Detail>
{
    public DetailValidator()
    {
        RuleFor(d => d.Property1)
            .Cascade(CascadeMode.Stop)
            .NotEmpty()
            .WithState(d => d.LineNumber)
            .Length(3)
            .WithState(d => d.LineNumber);

        RuleFor(d => d.Property2)
            .Cascade(CascadeMode.Stop)
            .NotEmpty()
            .WithState(d => d.LineNumber)
            .MaximumLength(50)
            .WithState(d => d.LineNumber);

        ...
    }
}

public class ImportFileValidator : AbstractValidator<ImportFile>
{
    public ImportFileValidator()
    {
        RuleFor(f => f.Header)
            .SetValidator(new HeaderValidator());

        RuleForEach(f => f.Details)
            .SetValidator(new DetailsValidator());

        ...
    }   
}
 

После того, как я вызову проверку, я хотел установить IsValid свойство каждой строки файла (будь то заголовок, детали или трейлер) на основе результата проверки.

Что возможно на данный момент, так это то , что, поскольку я использую WithState для хранения LineNumber , я могу сопоставить ValidationResult экземпляр с ImportFile экземпляром, чтобы установить действительность каждой строки, как показано ниже:

 ImportFile file = // parsed file content
var result = new ImportFileValidator().Validate(file);

foreach (var detail in file.Details)
{
    var error = result.Errors.FirstOrDefault(e => 
        Convert.ToInt32(e.CustomState) == detail.LineNumber);

    detail.IsValid = error == null;
}
 

И я также должен проверить заголовок и трейлер.

Есть ли способ сделать это внутри валидаторов? Я пытаюсь изучить документацию FluentValidation, но, похоже, не могу найти там то, что мне было нужно.

Ответ №1:

Когда я изучал доступные методы в FluentValidation, я увидел OnFailure и OnAnyFailure методы. Эти методы могут быть хорошим подспорьем в том, что мне нужно было сделать, но проблема в том, что они устарели 10.3.0 и будут удалены в версии 11 . Вместо этого они предлагают использовать пользовательский валидатор.

  1. Валидаторы заголовка, Деталей и аннотации трейлера остаются такими, как есть.
  2. Я создал пользовательские расширения валидатора для этих 3.

Каждый метод расширения создает экземпляр соответствующего валидатора и выполняет его. Я могу сделать их универсальными для заголовка, деталей и трейлера, так как они будут делать одно и то же, установите свойство isValid для результата проверки.

 public static IRuleBuilderOptionsConditions<ImportFile, T> IsHeaderValid<T>(this IRuleBuilder<ImportFile, T> ruleBuilder)
    where T : Header
{
    return builder.Custom((header, context) =>
    {
        // Create the Header Abstract Validator Instance
        var validator = new HeaderValidator();
        var result = validator.Validate(Header);

        header.IsValid = result.IsValid;

        // Pass the errors to the context
        result.Errors.ForEach(context.AddFailure);
    }
}
 
  1. Мне пришлось изменить ImportFileValidator, чтобы вызывать пользовательские валидаторы, вместо использования setvalidator.

ImportFileValidator выглядит следующим образом:

 public class ImportFileValidator : AbstractValidator<ImportFile>
{
    public ImportFileValidator()
    {
        RuleFor(f => f.Header)
            .IsHeaderValid();

        RuleForEach(f => f.Details)
            .IsDetailValid();

        ...
    }   
}
 

Это в значительной степени то, как я смог установить IsValid свойство без необходимости выполнять сопоставление, которое я изначально сделал в вопросе.