Composition and control flow
Write normal C# branches
Validator configuration supports native if/else, switch, loops, and foreach:
public void Configure(ValidationContext<CreateOrder> context)
{
if (context.Instance.RequiresApproval)
{
context.Property(order => order.ApproverId).Required();
}
switch (context.Instance.Channel)
{
case SalesChannel.Store:
context.Property(order => order.StoreId).Required();
break;
case SalesChannel.Web:
context.Property(order => order.SessionId).Required();
break;
}
}
In Debuggable mode this method executes at runtime, so ordinary breakpoints and step-through debugging work.
Nested objects
Use Children to validate a nested context and retain the complete property path:
context.Property(order => order.Customer)
.Required()
.Children(customer =>
{
customer.Property(value => value.Name).Required();
customer.Property(value => value.Email).MaxLength(200);
});
Collections
Use ForEach for element rules. context.Item is the current item inside the nested validation context:
context.Property(order => order.Lines)
.MinCount(1)
.ForEach(line =>
{
line.Property(item => item.Sku).Required();
line.Property(item => item.Quantity).GreaterThan(0);
});
Failures include indexed paths such as Lines[0].Sku.
Reuse a root validator
public sealed partial class CheckoutValidator(AddressValidator addressValidator)
: ValidatorDefinition<Checkout>
{
public override void Configure(ValidationContext<Checkout> context)
{
context.UseValidator(addressValidator);
context.Property(value => value.AcceptedTerms).Equal(true);
}
}
Reuse a property validator
Create a reusable PropertyValidatorDefinition<TProperty> and attach it to a property:
context.Property(order => order.ShippingAddress)
.Required()
.UseValidator(addressPropertyValidator);
Property validators skip null values. Add Required() or NotNull() when absence itself is invalid. Attribute-authored models can use [UseValidator<TValidator>].
Failure behavior
Configure collection globally, then override it at validator or property-chain scope:
services.AddValiantValidators(options =>
options.FailureBehavior = ValiantFailureBehavior.ValidateAll);
Use FailFast to stop within the configured scope after the first failure; use ValidateAll to collect all applicable failures. Narrower configuration overrides the global option.