c# - Validación del modelo hijo utilizando valores de modelo padre. Validación fluida. MVC4
asp.net-mvc validation (3)
Crear un validador de propiedades personalizado como este
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;
}
}
Agrega reglas como esta
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());
}
}
Una alternativa al validador de propiedades personalizadas es utilizar el método personalizado:
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;
});
}
Forma cruda de mostrar indicios que fallaron: (probablemente debería ser el nombre de algún otro identificador)
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;
});
}
}
A continuación se muestra una versión simplificada de mi problema.
No puedo aplanar el modelo. Hay una lista de "niños" que necesito para validar un cumpleaños.
Parece que no puedo hacer referencia a la fecha en la clase de padres y me preguntaba cómo se hace esto en la validación fluida.
Modelo
[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; }
}
Validador
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
}
}
Hay una manera más fácil de hacerlo usando el validador de hijos:
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();
}
}
Hoy en día, la answer de @ johnny-5 se puede simplificar aún más utilizando el método de extensión SetCollectionValidator
y pasando el objeto principal al validador secundario:
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);
}
}