Skip to content

Expo Nested Models

SteffenBlake edited this page Sep 28, 2026 · 1 revision

Nested models

Expo validates a non-null child whose declared type implements IExpoValidatable. Mark both the parent and child with [Expo] and make them partial to generate their validation methods.

Validate a child object

// RegistrationStudent.cs
[Expo]
public sealed partial class RegistrationStudent
{
    [IsRequired]
    [StringMatchesEmail]
    public string? Email { get; init; }
}
// GroupRegistration.cs
[Expo]
public sealed partial class GroupRegistration
{
    [IsRequired("A lead student is required.")]
    public RegistrationStudent? Lead { get; init; }

    [ItemsHasMaximumLength(4)]
    public List<RegistrationStudent?>? Guests { get; init; }
}

Calling GroupRegistration.TryValidate checks the parent's rules, then the child rules. A bad email on Lead produces /Lead/Email. A bad email on the first guest produces /Guests/0/Email.

No separate cascade attribute is needed. Keep the property's declared type specific enough for Expo to recognize the child contract; declaring it as object does not describe a validatable child.

Missing children

A null child is skipped by nested validation. [IsRequired] on Lead reports the missing object at /Lead. Once the child exists, its email rules report failures at /Lead/Email.

A null guest list also skips nested validation. Add [IsRequired] if that list must be supplied. Null elements inside a child collection are skipped; requiring the collection does not require every child item. Use a custom rule to reject null entries when needed.

Validate a complete object graph

var group = new GroupRegistration
{
    Lead = new RegistrationStudent { Email = "lead@example.edu" },
    Guests = [new RegistrationStudent { Email = "invalid" }]
};

bool valid = group.TryValidate(out var errors);

This returns false with an email failure at /Guests/0/Email. The parent keeps the child's failure message and prefixes its pointer with the parent path. Deeper nesting repeats that behavior.

Use an acyclic input graph. Generated validation follows child references and does not track visited objects to break reference cycles.

See Collections for outer size and item rules, and Errors and metadata for consuming nested paths.

Clone this wiki locally