Skip to content

Rules API ​

This page lists the members a rules class uses. For how they fit together, see Rules classes.

IValidationRulesFor<T> ​

csharp
public interface IValidationRulesFor<T>
{
    static abstract void Describe(ValidationRules<T> rules, T x);
}

A rules class implements it with public static void Describe(ValidationRules<T> rules, T x). The generator reads the body and never calls it. A class can implement the interface for several types.

ValidationRules<T> ​

ValidationRules<T> is the type of the rules parameter. Every method except Ensure, As and Apply returns a PropertyRules<T, TValue>, which later rules in the same chain apply to. Every method that takes a value, except As, also takes an optional field:, which replaces the field name derived from the value.

Presence ​

MethodPasses whenCode
Require(string? value)The string is not null, empty or whitespace.required
RequireAllowingEmpty(string? value)The string is not null.required
Require<TValue>(TValue? value)The reference or nullable value is not null.required
Require<TElement>(ImmutableArray<TElement> value)The array is not default. An empty array passes.required

A default ImmutableArray<T> has no array behind it, so Require reads it as missing. On an ImmutableArray<T>?, Require fails on null and on a default array. Require on an ImmutableArray<TElement> starts a chain on IReadOnlyList<TElement>, so Count and Each can follow it. Count, Unique and Each read an ImmutableArray<T>? through the array it holds. C# cannot infer the element type through the nullable, so Count, Unique and an Each over objects need it written, as in rules.Count<string>(x.Tags, 1, 5).

Require on a property of any other non-nullable value type can never fail, and the generator reports VM3101.

Strings ​

MethodPasses whenCode
Length(string? value, int min = 0, int max = int.MaxValue)The length is within the bounds.string_length
Pattern(string? value, Func<Regex> pattern)The regular expression matches. Pass a method group, such as a [GeneratedRegex] method.pattern

Length with constant bounds in the wrong order is reported as VM1101.

Numbers, dates and times ​

MethodPasses whenCode
Range<TValue>(TValue value, TValue min, TValue max)The value is within the bounds, inclusive.range
RangeAtLeast<TValue>(TValue value, TValue min)The value is at least min.range
RangeAtMost<TValue>(TValue value, TValue max)The value is at most max.range
MultipleOf(long? value, long divisor)The value divides by the divisor.multiple_of
MultipleOf(decimal? value, decimal divisor)The value divides by the divisor.multiple_of
MultipleOf(double? value, double divisor)The value divides by the divisor.multiple_of

The range methods take any struct that implements IComparable<TValue> and IFormattable, and each has an overload for the nullable form. Range with constant bounds in the wrong order is reported as VM1101.

The double overload converts the value to decimal before it divides, as [MultipleOf] does on a double property, so 0.3 is a multiple of 0.1. A constant divisor that is zero or negative is reported as VM1104.

Values ​

MethodPasses whenCode
AllowedValues<TValue>(TValue value, TValue[] allowed)The value is one of allowed.enum

Write allowed as an array or a collection expression of constants, such as ["active", "pending"]. The chained form takes the values as separate arguments, .AllowedValues("active", "pending"). A value that is not a compile-time constant is reported as VM3108, and an empty set as VM3109.

Collections ​

MethodEffectCode
Count<TElement>(IReadOnlyList<TElement>? value, int min = 0, int max = int.MaxValue)Passes when the number of items is within the bounds.array_bounds
Unique<TElement>(IEnumerable<TElement>? value)Passes when no item appears twice.unique_items
Each(IReadOnlyList<string>? value)Applies the rules chained after it to every element.from those rules
Each<TElement>(IReadOnlyList<TElement>? value)Runs the validators for TElement on every element.from those validators
Each<TElement>(IReadOnlyList<TElement>? value, Polymorphism polymorphism)Runs the validators polymorphism chooses for each element's actual type.from those validators
Nested<TValue>(TValue? value)Runs the validators for TValue on the value.from those validators
Nested<TValue>(TValue? value, Polymorphism polymorphism)Runs the validators polymorphism chooses for the value's actual type.from those validators

Count with constant bounds in the wrong order is reported as VM1101.

Nested is for a single object. Use Each for a collection of objects. Each accepts a list of strings or of a reference type. For a list of numbers or other value types, check the elements in a loop and report through Context. Nested on a collection, and a second Nested or Each in the chain after a descent, are reported as VM3001. A descent into a type with no rules is dropped with VM1501, and one into a property that already has [ValidateNested] is dropped with VM3106.

polymorphism is a Polymorphism from ValidationModules.Constraints, and chooses the validators as it does on [ValidateNested]. It has to be a constant, and any other expression is reported as VM3001. Without it, Nested and Each run only the validators for the declared type, and a descent into a type that is not sealed is reported as VM3111. Polymorphism.Runtime on a sealed type is reported as VM1504.

Conditions ​

csharp
public ValidationRules<T> Ensure(
    bool condition,
    string? field = null,
    string? code = null,
    string? message = null,
    ValidationSeverity severity = ValidationSeverity.Error
);

Reports an error when condition is false. The expression is copied into the validator as written and is not guarded against null. Without field:, the field is the first member of x that the condition reads. Without code:, the code is derived from the condition, as described in Validation codes. Without message:, the message is the condition's text, with each member of x written as its field name.

Other members ​

MemberEffect
For<TValue>(TValue value, string? field = null)Starts a chain for a value without a rule of its own.
As<TFacet>(TFacet value)Runs the rules declared for an interface or base type of x, at the current level, including the constraint attributes on its properties. The type's own checks leave those attributes out. The argument must be x. The type of x itself is reported as VM3110. For a type from another assembly, it runs every IValidatorFor<TFacet> registered in the container, in registration order.
Apply(RuleAction<T> rule)Runs a hand-written rule after every other rule on the type. Top level of Describe only.
ContextAn IValidationContextReporter for reporting errors from code. See below.

RuleAction<T> is a delegate: ValidationFlow RuleAction<in T>(ref ValidationContext context, T value). Pass a static method group, internal or public. A lambda whose whole body calls one such method with its own parameters is read as that method, and any other lambda is VM3008.

PropertyRules<T, TValue> ​

PropertyRules<T, TValue> is the type a rule method returns. These extension methods continue a chain:

MethodApplies to a chain on
Require()a string, a reference type, or a nullable value type
RequireAllowingEmpty()a string
Length(min, max)a string
Pattern(regex)a string
Range(min, max), RangeAtLeast(min), RangeAtMost(max)a value type, nullable or not
MultipleOf(divisor)an integral, decimal or floating-point number, nullable or not
AllowedValues(params allowed)any value
Count(min, max)an IReadOnlyList<T>
Unique()an IEnumerable<T>
Each()an IReadOnlyList<T>
Each(polymorphism)an IReadOnlyList<T> of a reference type
Nested(), Nested(polymorphism)a reference type

A chain is typed by the method that starts it. On an int property, rules.Range(x.Quantity, 1, 100) starts a chain on int?, and rules.For(x.Quantity) starts one on int. The range methods and MultipleOf accept both forms:

csharp
rules.For(x.Weight).Range(0.5, 30).MultipleOf(0.5);

The chain's type also types the arguments. On a float chain, write 0.5f. On a byte, sbyte, short or ushort chain, cast an integer literal, as in .MultipleOf((short)5).

When Require or RequireAllowingEmpty fails, the rest of its chain is skipped.

After Each on a list of strings, chain Length or Pattern to check each element. Require after Each is reported as VM3001. Use Length(1, ...) to reject empty elements.

Context ​

rules.Context is an IValidationContextReporter:

MethodEffect
Report(field, code, message, severity)Reports an error against field, below the current path.
Report(field, code, value, messageInfo, severity)Reports an error with a structured message.
ReportHere(code, message, severity)Reports an error against the current path itself.

severity defaults to ValidationSeverity.Error. A field built with nameof(x.Member) becomes the member's field name at build time.

Report helpers ​

These extension methods report a built-in code with its default message. They work on rules.Context in a rules class and on ValidationContext in a hand-written validator. Each takes the field first, then its own arguments, then optional severity, code and value arguments. In ReportRange, ReportRangeAtLeast and ReportRangeAtMost, the exclusivity flags come last, after value, so pass them by name. code replaces the code and keeps the message. value records the failed value in ValidationError.Value.

HelperArgumentsCode
ReportRequiredrequired
ReportStringLengthmin, maxstring_length
ReportItemCountmin, maxarray_bounds
ReportRangemin, max, exclusiveMin, exclusiveMaxrange
ReportRangeAtLeastmin, exclusiverange
ReportRangeAtMostmax, exclusiverange
ReportMultipleOfdivisormultiple_of
ReportPatternpattern
ReportAllowedValuesallowedValues, the list as textenum
ReportDeniedValuesdeniedValues, the list as textenum
ReportEmailemail
ReportPhonephone
ReportUrlurl
ReportCreditCardcredit_card
ReportBase64base64
ReportFileExtensionextensions, the list as textfile_extension
ReportUniqueItemsunique_items
ReportCustomcustom
csharp
rules.Context.ReportStringLength(nameof(x.Guest), 0, 10, code: "guest_too_long");

Released under the MIT License.