Child Model Validation using Parent Model Values. Fluent Validation. MVC4
Edit: SetCollectionValidator has been deprecated, however the same can be done now using RuleForEach:
public class ParentValidator : AbstractValidator<Parent>
{
public ParentValidator()
{
this.RuleFor(model => model.Name).NotEmpty();
this.RuleForEach(model => model.Children)
.SetValidator(model => new ChildValidator(model));
}
}
public class ChildValidator : AbstractValidator<Child>
{
public ChildValidator(Parent parent)
{
this.RuleFor(model => model.ChildProperty).NotEmpty();
this.RuleFor(model => model.Birthday).Must(birthday => parent.Birthday < birthday);
}
}
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.
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;
});
}
}
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);
}
}