Свободная проверка с динамическим сообщением

Я пытаюсь создать пользовательскую проверку с динамическим сообщением в свободно используемой библиотеке проверки.

Например:

public class CreateProcessValidator : AbstractValidator<CreateProcessVM>
{
    public CreateProcessValidator()
    {
        RuleFor(x => x.ProcessFile).Must((x,e) => IsProcessFileValid(x.ProcessFile))).WithMessage("Parse failed with error : {0}");        
    }

    public bool IsProcessFileValid(HttpPostedFileBase file)
    {
        var errorMessage = "..."  // pass result to validaton message ?
        // logic
        return false;
    }
}

Есть ли обходной путь, как пройти валидацию результата?

Спасибо

2 ответа

Вы пробовали что-то подобное?

public class IsProcessFileValid : PropertyValidator
{
    public IsProcessFileValid(): base("{ValidationMessage}") {}

    protected override IsValid(PropertyValidatorContext context)
    {
        if (!IsProcessFileValid1(context))
            context.MessageFormatter.AppendArgument("ValidationMessage",
                "Custom validation message #1");

        if (!IsProcessFileValid2(context))
            context.MessageFormatter.AppendArgument("ValidationMessage",
                "Custom validation message #2");

        // ...etc

        return true;
    }

    private bool IsProcessFileValid1(PropertyValidatorContext context)
    {
        // logic
        return false;
    }

    private bool IsProcessFileValid2(PropertyValidatorContext context)
    {
        // logic
        return false;
    }

    // ...etc
}

С методом расширения:

public static class IsProcessFileValidExtensions
{
    public static IRuleBuilderOptions<T, object> MustBeValidProcessFile<T>
        (this IRuleBuilder<T, object> ruleBuilder)
    {
        return ruleBuilder.SetValidator(new IsProcessFileValid());
    }

}

... а затем использовать его без кастома WithMessage:

public CreateProcessValidator()
{
    RuleFor(x => x.ProcessFile).MustBeValidProcessFile();        
}

Создавая пользовательские PropertyValidatorВы можете инкапсулировать сообщение проверки по умолчанию в этом классе и сделать его динамическим. Однако вы не должны использовать .WithMessage расширение при объявлении RuleForпотому что это переопределит сообщение проверки по умолчанию, которое вы настроили непосредственно внутри PropertyValidator,

Столкнулся с той же проблемой при попытке вставить сообщение об исключении в WithMessage(). Он работал с перегрузкой методаFunc<T, string> messageProvider как параметр.

Вот решение, представленное на примере плакатов (рабочий код, FluentValidation v 9.1):

public class CreateProcessVM
{
    public object ProcessFile { get; set; }
}

public class CreateProcessValidator : AbstractValidator<CreateProcessVM>
{
    public CreateProcessValidator()
    {
        var message = "Something went wrong.";
        RuleFor(x => x.ProcessFile)
            .Must((x, e) => IsProcessFileValid(x.ProcessFile, out message))
            // .WithMessage(message); will NOT work
            .WithMessage(x => message); //Func<CreateProcessVM, string> as parameter
    }

    public bool IsProcessFileValid(object file, out string errorMessage)
    {
        errorMessage = string.Empty;
        try
        {
            Validate(file);
            return true;
        }
        catch (InvalidOperationException e)
        {
            errorMessage = e.Message;
            return false;
        }
    }

    private void Validate(object file)
    {
        throw new InvalidOperationException("File of type .custom is not allowed.");
    }
}

И тест, демонстрирующий, что мы действительно получаем сообщение об исключении в сообщении об ошибке:

[Fact]
public void Test()
{
    var validator = new CreateProcessValidator();
    var result = validator.Validate(new CreateProcessVM());
    Assert.False(result.IsValid);
    Assert.Equal("File of type .custom is not allowed.", result.Errors[0].ErrorMessage);
}

Там нет никакого способа сделать это. Я бы разделил сложный метод проверки, который у вас есть в настоящее время, на более мелкие методы (IsProcessFileValid1, IsProcessFileValid2, IsProcessFileValid3, ...), чтобы вы могли иметь более точный контроль над сообщением об ошибке. Кроме того, каждый метод будет отвечать только за проверку правильности повторного использования (единая ответственность):

RuleFor(x => x.ProcessFile)
    .Must(IsProcessFileValid1)
    .WithMessage("Message 1")
    .Must(IsProcessFileValid2)
    .WithMessage("Message 2")
    .Must(IsProcessFileValid3)
    .WithMessage("Message 3");

Также обратите внимание, как я упростил лямбду, так как метод можно было напрямую передать Must в качестве аргумента.

Вот как я это решил. Протестировано с помощью FluentValidation v8.5.0

class EmptyValidationMessage : IStringSource
{
    public string ResourceName => null;

    public Type ResourceType => null;

    public string GetString(IValidationContext context)
    {
        return string.Empty;
    }

    public static readonly EmptyValidationMessage Instance = new EmptyValidationMessage();
}

public class MyPropValidator : PropertyValidator
{
    public MyPropValidator() : base(EmptyValidationMessage.Instance)
    {
    }

    protected override bool IsValid(PropertyValidatorContext context)
    {
        // if not valid

        Options.ErrorMessageSource = new StaticStringSource("my message");

        // you can do LanguageStringSource, LazyStringSource, LocalizedStringSource, etc

        // example with localized string (https://github.com/clearwaterstream/LocalizedString.FluentValidation)

        Options.ErrorMessageSource = new LocalizedStringSource("my message").InFrench("moi message");

        return false;
    }
}
Другие вопросы по тегам