The validation rule interfaces
A validation rule class must implement at least one of the following two interfaces.
Rules can implement these multiple times
If it makes sense to do so, it is perfectly reasonable for a rule class to implement both interfaces. Likewise, a rule class may implement either or both interfaces more than once for different generic types if it's appropriate. Developers might need to use explicit interface implementation to avoid ambiguity of overloads.
Example: The IntegerInRange
rule
The rule IntegerInRange
in the standard rules package implements IRule<TValidated>
for all of the following types:
Byte
Int16
Int32
Int64
Nullable<Byte>
Nullable<Int16>
Nullable<Int32>
Nullable<Int64>
This way the same conceptual rule may be used regardless of the data-type it validates. This rule may be reused regardless of the type of integer value, or whether that value is nullable or not
Which interface to choose?
The IRule<in TValidated>
interface is suitable for the majority of validation rules.
The value to be validated is provided to the GetResultAsync
method and the rule logic returns a result based upon that value.
Use the IRule<in TValue, in TParent>
interface when the validation rule also requires access to the object from which the value was derived.
This is most common when validating properties of a model object.
The GetResultAsync
method in IRule<in TValue, in TParent>
receives a 'parent' object as an additional parameter.
Example: Validating a library book loan
Imagine we are validating the following model.
public class LibraryBookLoan
{
public long BookId { get; set; }
public DateTime LoanDate { get; set; }
public DateTime ReturnDate { get; set; }
}
Now, we wish to write a validation rule that the ReturnDate
(the end of the loan) must not be earlier than the LoanDate
(the beginning of the loan).
Whilst we could write a class which implements IRule<LibraryBookLoan>
, such a rule would not be associated with the ReturnDate
property, as it would need to be applied at the level of the loan object itself.
Instead, here we would implement IRule<DateTime,LibraryBookLoan>
in our rule class, like so.
using static CSF.Validation.Rules.CommonResults;
public class MustNotBeBeforeLoanDate : IRule<DateTime, LibraryBookLoan>
{
public ValueTask<RuleResult> GetResultAsync(DateTime validated,
LibraryBookLoan parent,
RuleContext context,
CancellationToken token = default)
{
if(parent is null) return PassAsync();
return validated >= parent.LoanDate ? PassAsync() : FailAsync();
}
}