{Version 8.0.0}

Почему этот тест будет проходить?

Тест:

[Test]
public void Validation_NullTo_ShouldThrowModelValidationException()
{
    var config = new EmailMessage
    {
        Subject = "My Subject",
        Body = "My Body",
        To = null
    };

    EmailMessageValidator validator = new EmailMessageValidator();

    ValidationResult results = validator.Validate(config);
    if (!results.IsValid)
    {
        foreach (var failure in results.Errors)
        {
            Console.WriteLine("Property " + failure.PropertyName + " failed validation. Error was: " + failure.ErrorMessage);
        }
    }
    // results.Errors is Empty and resuts.IsValid is true here. Should be false with at least one Error message.
    Assert.True(results.IsValid);
}    

Если я изменю структуру сообщения таким образом, проверка завершится неудачно.

var config = new EmailMessage
{
    Subject = "My Subject",
    Body = "My Body"                
};
config.To = new EmailAddressList();

Валидаторы:

public class EmailAddressListAtLeastOneRequiredValidator : AbstractValidator<EmailAddressList>
{
    public EmailAddressListAtLeastOneRequiredValidator()
    {
        RuleFor(model => model)
            .NotNull()
            .WithMessage(AppMessages.Validation.AtLeastOneShouldBeDefined.ParseIn(nameof(EmailAddressList.Value)));

        RuleFor(model => model.Value)
            .NotNull()
            .WithMessage(AppMessages.Validation.AtLeastOneShouldBeDefined.ParseIn(nameof(EmailAddressList.Value)));

        RuleFor(model => model.Value.Count)
            .GreaterThan(0)
            .WithMessage(AppMessages.Validation.AtLeastOneShouldBeDefined.ParseIn(nameof(EmailAddressList.Value)));

        When(model => model.Value?.Count > 0, () =>
        {
            RuleFor(model => model.Value)
                .NotEmpty()
                .WithMessage(AppMessages.Validation.AtLeastOneShouldBeDefined.ParseIn(nameof(EmailAddressList.Value)));
        });
    }
}

public class EmailAddressListValidator : AbstractValidator<EmailAddressList>
{
    public EmailAddressListValidator()
    {
        RuleFor(model => model.Value).SetCollectionValidator(new EmailAddressValidator());
    }
}

public class EmailAddressValidator : AbstractValidator<EmailAddress>
{
    public EmailAddressValidator()
    {
        When(model => model.Value != null, () =>
        {
            RuleFor(model => model.Value)
                .EmailAddress()
                .WithMessage(AppMessages.Validation.ValueCannotBeNullOrEmpty.ParseIn(nameof(EmailAddress)));
        });
    }
}

public class EmailMessageValidator : AbstractValidator<EmailMessage>
{
    public EmailMessageValidator()
    {
        RuleFor(model => model.To).SetValidator(new EmailAddressListAtLeastOneRequiredValidator());

        When(model => model.Cc?.Value?.Count > 0, () =>
        {
            RuleFor(model => model.Cc).SetValidator(new EmailAddressListValidator());
        });

        When(model => model.Bcc?.Value?.Count > 0, () =>
        {
            RuleFor(model => model.Bcc).SetValidator(new EmailAddressListValidator());
        });

        RuleFor(model => model.Subject)
            .NotEmpty().WithMessage(AppMessages.Validation.ValueCannotBeNullOrEmpty.ParseIn(nameof(EmailMessage.Subject)))
            .MaximumLength(100).WithMessage(AppMessages.Validation.ValueLengthCannotBeGreaterThan.ParseIn(nameof(EmailMessage.Subject), 100));

        RuleFor(model => model.Body)
            .NotEmpty().WithMessage(AppMessages.Validation.ValueCannotBeNullOrEmpty.ParseIn(nameof(EmailMessage.Body)));
    }
}

Классы EmailMessage и EmailAddressList:

public class EmailMessage : IEmailMessage
{
    public EmailAddressList To { get; set; } = new EmailAddressList();
    public EmailAddressList Cc { get; set; } = new EmailAddressList();
    public EmailAddressList Bcc { get; set; } = new EmailAddressList();
    public string Subject { get; set; }
    public string Body { get; set; }
}


public class EmailAddressList : ModelValidation, IEnumerable<EmailAddress>
{
    public List<EmailAddress> Value { get; set; } = new List<EmailAddress>();

    public EmailAddressList()
        : base(new EmailAddressListValidator())
    {

    }

    public EmailAddressList(string emailAddressList)
    : base(new EmailAddressListValidator())
    {
        Value = Split(emailAddressList);
    }

    public EmailAddressList(IValidator validator)
        : base(validator ?? new EmailAddressListValidator())
    {

    }

    public List<EmailAddress> Split(string emailAddressList, char splitChar = ';')
    {
        return emailAddressList.Contains(splitChar)
            ? emailAddressList.Split(splitChar).Select(email => new EmailAddress(email)).ToList()
            : new List<EmailAddress> { new EmailAddress(emailAddressList) };
    }

    public string ToString(char splitChar = ';')
    {
        if (Value == null)
            return "";

        var value = new StringBuilder();
        foreach (var item in Value)
            value.Append($"{item.Value};");

        return value.ToString().TrimEnd(';');
    }

    public void Add(string emailAddress, string displayName = "")
    {
        Value.Add(new EmailAddress(emailAddress, displayName));
    }

    public void Add(EmailAddress emailAddress)
    {
        Value.Add(emailAddress);
    }

    public IEnumerator<EmailAddress> GetEnumerator()
    {
        return Value.GetEnumerator();
    }

    [ExcludeFromCodeCoverage]
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}
0
MB34 20 Дек 2018 в 20:53

1 ответ

Лучший ответ

Вот ответ Джереми Скиннера ...

Это нормальное и правильное поведение. Сложные дочерние валидаторы, установленные с помощью SetValidator, могут быть вызваны только в том случае, если целевое свойство не равно нулю. Поскольку свойство To имеет значение null, дочерний валидатор не вызывается. Что вам нужно сделать, так это объединить SetValidator с проверкой NotNull:

RuleFor (model => model.To) .NotNull (). WithMessage ("...");
.SetValidator (новый EmailAddressListAtLeastOneRequiredValidator ());

... а затем удалите RuleFor (model => model) .NotNull () из EmailAddressListAtLeastOneRequiredValidator, поскольку это никогда не будет выполнено. Переопределение PreValidate здесь не актуально. Подобное использование PreValidate актуально только при попытке передать значение null корневому валидатору. При работе с дочерними валидаторами они никогда не могут быть вызваны с нулевым экземпляром (что предусмотрено дизайном), а нулевое значение должно обрабатываться родительским валидатором.

0
MB34 27 Дек 2018 в 15:21