Child Model Validation using Parent Model Values. Fluent Validation. MVC4

萝らか妹 提交于 2019-11-28 22:28:20

问题


Below is a simplified version of my problem.

I can not flatten the model. There is a List of "children" that I need to validate a birthday.

I can not seem to reference the date in the Parent class and was wondering how this is done in Fluent Validation?

Model

[Validator(typeof(ParentValidator))]
public class Parent
{
    public string Name { get; set; }
    public DateTime Birthdate { get; set; }

    public List<Child> Children { get; set; }
}

public class Child
{
    public string ChildProperty{ get; set; }
    public DateTime Birthdate { get; set; }
}

Validator

public class ParentValidator : AbstractValidator<Parent>
{
    public ParentValidator()
    {
         RuleFor(model => model.Name).NotEmpty();
         RuleForEach(model => model.Children).SetValidator(new ChildValidator());
    }
}

public class ChildValidator : AbstractValidator<Child>
{
    public ChildValidator()
    {
        RuleFor(model => model.ChildProperty).NotEmpty();
        //Compare birthday to make sure date is < Parents birthday
    }
}

回答1:


Create a custom property validator like this

public class AllChildBirtdaysMustBeLaterThanParent : PropertyValidator
{
    public AllChildBirtdaysMustBeLaterThanParent()
        : base("Property {PropertyName} contains children born before their parent!")
    {
    }

    protected override bool IsValid(PropertyValidatorContext context)
    {
        var parent = context.ParentContext.InstanceToValidate as Parent;
        var list = context.PropertyValue as IList<Child>;

        if (list != null)
        {
            return ! (list.Any(c => parent.BirthDay > c.BirthDay));
        }

        return true;
    }
}

Add rules like this

public class ParentValidator : AbstractValidator<Parent>
{
    public ParentValidator()
    {
        RuleFor(model => model.Name).NotEmpty();
        RuleFor(model => model.Children)
               .SetValidator(new AllChildBirtdaysMustBeLaterThanParent());

        // Collection validator
        RuleFor(model => model.Children).SetCollectionValidator(new ChildValidator());
    }
}

Alternative to the Custom Property validator is to use the Custom method:

    public ParentValidator()
    {
        RuleFor(model => model.Name).NotEmpty();
        RuleFor(model => model.Children).SetCollectionValidator(new ChildValidator());

        Custom(parent =>
        {
            if (parent.Children == null)
                return null;

            return parent.Children.Any(c => parent.BirthDay > c.BirthDay)
               ? new ValidationFailure("Children", "Child cannot be older than parent.")
               : null;
        });
    }

Crude way of showing indicies that failed: (should probably be name of some other identifier)

public class ParentValidator : AbstractValidator<Parent>
{
    public ParentValidator()
    {
        RuleFor(m => m.Children).SetCollectionValidator(new ChildValidator());

        Custom(parent =>
        {
            if (parent.Children == null)
                return null;

            var failIdx = parent.Children.Where(c => parent.BirthDay > c.BirthDay).Select(c => parent.Children.IndexOf(c));
            var failList = string.Join(",", failIdx);

            return failIdx.Count() > 0
               ? new ValidationFailure("Children", "Child cannot be older than parent. Fail on indicies " + failList)
               : null;
        });
    }

}



回答2:


There is a way easier way to do this using set child validator:

public class ChildValidator : AbstractValidator<Child>
{
    public ChildValidator(Parent parent)
    {
        RuleFor(model => model.ChildProperty).NotEmpty();
        RuleFor(model => model.Birthday).Must(birthday => parent.Birthday > birthday);

    }
}

public class ParentValidator : AbstractValidator<Parent>
{
    public ParentValidator()
    {
         RuleFor(model => model.Name).NotEmpty();
    }

    public override ValidationResult Validate(Parent parent)
    {
        RuleFor(model => model.Children).SetCollectionValidator(new ChildValidator(this));

        return base.Validate();
    }

}



回答3:


Nowadays the answer by @johnny-5 can be simplified even further by using the SetCollectionValidator extension method and passing the parent object to the child validator:

public class ParentValidator : AbstractValidator<Parent>
{
    public ParentValidator()
    {
         RuleFor(model => model.Name).NotEmpty();
         RuleFor(model => model.Children)
             .SetCollectionValidator(model => new ChildValidator(model))
    }
}

public class ChildValidator : AbstractValidator<Child>
{
    public ChildValidator(Parent parent)
    {
        RuleFor(model => model.ChildProperty).NotEmpty();
        RuleFor(model => model.Birthday).Must(birthday => parent.Birthday < birthday);
    }
}



回答4:


Building on the answer of @kristoffer-jalen it is now:

public class ParentValidator : AbstractValidator<Parent>
{
    public ParentValidator()
    {
         RuleFor(model => model.Name).NotEmpty();
         //RuleFor(model => model.Children)
         //    .SetCollectionValidator(model => new ChildValidator(model))
         RuleForEach(model => model.Children)
                .SetValidator(model => new ChildValidator(model));
    }
}

public class ChildValidator : AbstractValidator<Child>
{
    public ChildValidator(Parent parent)
    {
        RuleFor(model => model.ChildProperty).NotEmpty();
        RuleFor(model => model.Birthday).Must(birthday => parent.Birthday < birthday);
    }
}

as SetCollectionValidator is deprecated.



来源:https://stackoverflow.com/questions/18720105/child-model-validation-using-parent-model-values-fluent-validation-mvc4

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!