Skip to main content

Data Validation Nodes

Validation nodes check property values against rules and provide clear error messages when validation fails. Each validation rule can have a custom message for better error reporting.

String Validation

Validate text data against patterns and constraints:

builder.AddStringValidation<User>(x => x.Email)
.IsEmail()
.HasMaxLength(255);

Available Validators

ValidatorPurposeParameters
IsEmail()Valid email formatbool allowNull
IsUrl()Valid URL (HTTP/HTTPS)bool allowNull
IsGuid()Valid GUID formatbool allowNull
IsAlphanumeric()Letters and digits onlybool allowNull
IsAlphabetic()Letters onlybool allowNull
IsDigitsOnly()Digits onlybool allowNull
IsNumeric()Valid number formatbool allowNull
HasMinLength(min)Minimum lengthint min, bool allowNull
HasMaxLength(max)Maximum lengthint max, bool allowNull
HasLengthBetween(min, max)Length in rangeint min, int max, bool allowNull
Matches(pattern)Regex pattern matchstring pattern, RegexOptions, bool allowNull
Contains(substring)Contains substringstring substring, StringComparison, bool allowNull
StartsWith(prefix)Starts with prefixstring prefix, StringComparison, bool allowNull
EndsWith(suffix)Ends with suffixstring suffix, StringComparison, bool allowNull

Examples

// Email validation with custom messages
builder.AddStringValidation<User>(x => x.Email)
.IsEmail("Email address is not in valid format")
.HasMaxLength(255, "Email cannot exceed 255 characters");

// Password validation
builder.AddStringValidation<User>(x => x.Password)
.HasMinLength(8, "Password must be at least 8 characters")
.Matches("[A-Z]", RegexOptions.None, false, "Password must contain uppercase letter")
.Matches("[0-9]", RegexOptions.None, false, "Password must contain digit")
.Matches("[!@#$%^&*]", RegexOptions.None, false, "Password must contain special character");

// URL validation
builder.AddStringValidation<SocialProfile>(x => x.WebsiteUrl)
.IsUrl("Website URL must be valid HTTP/HTTPS URL")
.AllowNull(); // Optional field

// Phone number (numeric only)
builder.AddStringValidation<Contact>(x => x.Phone)
.IsDigitsOnly("Phone number must contain only digits")
.HasLengthBetween(10, 15, "Phone number must be 10-15 digits");

// Username validation
builder.AddStringValidation<Account>(x => x.Username)
.HasMinLength(3, "Username must be at least 3 characters")
.HasMaxLength(20, "Username must not exceed 20 characters")
.IsAlphanumeric("Username can only contain letters and digits");

Numeric Validation

Validate numeric data against ranges and constraints:

builder.AddNumericValidation<Order>(x => x.Quantity)
.IsGreaterThan(0)
.IsLessThan(1000);

Available Validators

ValidatorPurposeType
IsGreaterThan(value)Greater thanint, double, decimal
IsLessThan(value)Less thanint, double, decimal
IsBetween(min, max)In range (inclusive)int, double, decimal
IsPositive()> 0int, double, decimal, int?, double?, decimal?
IsNegative()< 0int, double, decimal
IsZeroOrPositive()>= 0int, double, decimal
IsNotNegative()>= 0 (alias)int, double, decimal
IsNonZero()!= 0int, double, decimal
IsEven()Even numberint
IsOdd()Odd numberint
IsFinite()Not NaN/Infinitydouble
IsIntegerValue()No fractional partdouble, decimal
IsNotNull()Not nullint?, double?, decimal?

Examples

// Price validation
builder.AddNumericValidation<Product>(x => x.Price)
.IsGreaterThan(0, "Price must be greater than zero")
.IsFinite("Price must be a valid number");

// Discount validation
builder.AddNumericValidation<Order>(x => x.Discount)
.IsBetween(0, 100, "Discount must be between 0 and 100 percent");

// Age validation
builder.AddNumericValidation<Person>(x => x.Age)
.IsPositive("Age cannot be negative")
.IsLessThan(150, "Age must be less than 150");

// Quantity validation
builder.AddNumericValidation<OrderItem>(x => x.Quantity)
.IsGreaterThan(0, "Quantity must be at least 1")
.IsLessThan(10001, "Quantity cannot exceed 10000");

// Rating validation
builder.AddNumericValidation<Review>(x => x.Rating)
.IsBetween(1, 5, "Rating must be between 1 and 5 stars");

// Measurement validation
builder.AddNumericValidation<Sensor>(x => x.Reading)
.IsFinite("Reading must be a valid number, not NaN or Infinity");

Nullable Numeric Validation

Validate nullable numeric types with built-in null handling:

// Optional age that, if provided, must be positive
builder.AddNumericValidation<Person>(x => x.OptionalAge)
.IsPositive("Age must be positive (null is allowed)")
.IsNotNull("Age is required"); // If you need to enforce non-null

// Optional quantity that, if provided, must be greater than zero
builder.AddNumericValidation<OrderItem>(x => x.OptionalQuantity)
.IsPositive("Quantity must be greater than zero if provided");

// Using IsNotNegative (more intuitive alias for IsZeroOrPositive)
builder.AddNumericValidation<Stock>(x => x.Quantity)
.IsNotNegative("Stock quantity cannot be negative");

The nullable overloads for IsPositive() automatically handle null values - they pass validation if the value is null, but fail if it's provided and doesn't meet the criteria. Use IsNotNull() to explicitly require non-null values.

DateTime Validation

Validate date and time values:

builder.AddDateTimeValidation<Event>(x => x.StartDate)
.IsInFuture()
.IsWeekday();

Available Validators

ValidatorPurposeSupports Nullable
IsInFuture()After current date/timeDateTime, DateTime?
IsInPast()Before current date/timeDateTime, DateTime?
IsToday()Today's dateDateTime only
IsWeekday()Monday-FridayDateTime only
IsWeekend()Saturday-SundayDateTime only
IsDayOfWeek(day)Specific day of weekDateTime only
IsUtc()UTC timezoneDateTime only
IsLocal()Local timezoneDateTime only
IsNotMinValue()Not DateTime.MinValueDateTime only
IsNotMaxValue()Not DateTime.MaxValueDateTime only
IsBefore(date)Before specific dateDateTime, DateTime?
IsAfter(date)After specific dateDateTime, DateTime?
IsBetween(from, to)Within date rangeDateTime, DateTime?
IsInYear(year)Within specific yearDateTime only
IsInMonth(month)Within specific monthDateTime only
IsNotNull()Not nullDateTime? only

Examples

// Event scheduling validation
builder.AddDateTimeValidation<Event>(x => x.StartTime)
.IsInFuture("Event must start in the future")
.IsWeekday("Events can only be scheduled on weekdays");

// Birth date validation
builder.AddDateTimeValidation<Person>(x => x.BirthDate)
.IsInPast("Birth date must be in the past")
.IsBetween(
DateTime.Now.AddYears(-150),
DateTime.Now,
"Birth date must be between 150 years ago and today");

// Appointment scheduling
builder.AddDateTimeValidation<Appointment>(x => x.ScheduledTime)
.IsInFuture("Appointment must be in the future")
.IsWeekday("Appointments can only be scheduled on weekdays");

// Transaction timestamp
builder.AddDateTimeValidation<Transaction>(x => x.Timestamp)
.IsUtc("Transaction timestamp must be in UTC");

// Specific day requirement
builder.AddDateTimeValidation<WeeklyReport>(x => x.GeneratedDate)
.IsDayOfWeek(DayOfWeek.Friday, "Reports must be generated on Fridays");

Nullable DateTime Validation

Validate optional DateTime fields:

// Optional deadline that, if provided, must be in the future
builder.AddDateTimeValidation<Task>(x => x.DeadlineDate)
.IsInFuture("Deadline must be in the future (null is allowed)")
.IsNotNull("Deadline is required"); // If you need to enforce non-null

// Optional end date that must be after start date if provided
builder.AddDateTimeValidation<Event>(x => x.EndDateTime)
.IsInFuture("End time must be in the future"); // null passes validation

// Optional notification time
builder.AddDateTimeValidation<Reminder>(x => x.ScheduledTime)
.IsNotNull("Scheduled time is required");

Collection Validation

Validate collections and their elements:

builder.AddCollectionValidation<Document>(x => x.Tags)
.HasMinLength(1)
.HasMaxLength(10);

Available Validators

ValidatorPurpose
HasMinLength(min)Minimum items
HasMaxLength(max)Maximum items
HasLengthBetween(min, max)Item count in range
NotEmpty()Contains at least one item
IsEmpty()Contains no items
Contains(item)Contains specific item
NotContains(item)Does not contain item
AllItemsMatch(predicate)All items satisfy condition
AnyItemMatches(predicate)At least one item satisfies condition

Examples

// Tag validation
builder.AddCollectionValidation<Article>(x => x.Tags)
.HasMinLength(1, "Article must have at least one tag")
.HasMaxLength(10, "Article cannot have more than 10 tags");

// Category selection
builder.AddCollectionValidation<Product>(x => x.Categories)
.NotEmpty("Product must have at least one category");

// Email recipients
builder.AddCollectionValidation<EmailMessage>(x => x.Recipients)
.HasMinLength(1, "Message must have at least one recipient")
.AllItemsMatch(email => email.Contains("@"), "All recipients must have valid email format");

Custom Messages

All validators support custom error messages:

builder.AddStringValidation<User>(x => x.Email)
.IsEmail("Please provide a valid email address")
.HasMaxLength(255, "Email address is too long");

builder.AddNumericValidation<Product>(x => x.Price)
.IsGreaterThan(0, "Products must have a price greater than zero")
.HasDecimals(2, "Prices can only have up to 2 decimal places");

Multiple Rules

Chain multiple validation rules on a single property:

builder.AddStringValidation<User>(x => x.Username)
.HasMinLength(3, "Username must be at least 3 characters")
.HasMaxLength(20, "Username must not exceed 20 characters")
.IsAlphanumeric("Username can only contain letters and numbers")
.Matches("^[a-z]", RegexOptions.IgnoreCase, false, "Username must start with a letter");

// Multiple properties
builder.AddStringValidation<User>(x => x.Email)
.IsEmail()
.HasMaxLength(255);

builder.AddStringValidation<User>(x => x.Password)
.HasMinLength(8)
.Matches("[A-Z]");

Validation with Filtering

Combine validation with filtering:

// Only validate active users
builder.AddFiltering<User>(x => x.IsActive);
builder.AddStringValidation<User>(x => x.Email).IsEmail();

Error Handling

Validation errors are captured and can be handled:

try
{
await pipeline.ExecuteAsync();
}
catch (ValidationException ex)
{
Console.WriteLine($"Property: {ex.PropertyPath}");
Console.WriteLine($"Rule: {ex.RuleName}");
Console.WriteLine($"Value: {ex.PropertyValue}");
Console.WriteLine($"Message: {ex.Message}");
}

// Custom error handler
builder.WithErrorHandler(validationHandle, new CustomValidationHandler());

// Skip invalid items instead of throwing
builder.WithErrorDecision(validationHandle, NodeErrorDecision.Skip);

Thread Safety

All validation nodes are stateless and thread-safe. Compiled validators can be safely shared across parallel pipelines.

Performance

  • Validators use compiled expressions for property access
  • String validators use pre-compiled regex patterns
  • Range validators use direct numeric comparisons (no boxing)
  • First failure short-circuits remaining validators (configurable)