Extends FluentValidation with some more opinionated rules and extensions.
See Milestones for release notes.
https://nuget.org/packages/ExtendedFluentValidation/
It leverages nullability information to make all non-nullable reference properties to be required.
DateTime
, DateTimeOffset
, and DateOnly
cannot be MinValue
.
String cannot be String.Empty
or only white-space. The logic being: if the absence of text is valid, then make the member nullable. This helps since nullable is a first class strong type feature, where "string is empty or only white-space" is a runtime check.
Guids cannot be Guid.Empty
.
Lists and Collection cannot be empty if ValidatorConventions.ValidateEmptyLists()
is called in a module initializer. The logic being: if the absence of any values is valid, then make the member nullable. This helps since nullable is a first class strong type feature, where "list contains no values" is a runtime check.
There are two ways of applying the extended rules.
Using a base class ExtendedValidator
:
class PersonValidatorFromBase :
ExtendedValidator<Person>
{
public PersonValidatorFromBase()
{
//TODO: add any extra rules
}
}
Using an extension method AddExtendedRules
:
class PersonValidatorNonBase :
AbstractValidator<Person>
{
public PersonValidatorNonBase() =>
this.AddExtendedRules();
//TODO: add any extra rules
}
The above are equivalent to:
public class Person
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string? MiddleName { get; set; }
public string FamilyName { get; set; }
public DateTimeOffset Dob { get; set; }
}
class PersonValidatorEquivalent :
AbstractValidator<Person>
{
public PersonValidatorEquivalent()
{
RuleFor(_ => _.Id)
.NotEqual(Guid.Empty);
RuleFor(_ => _.FirstName)
.NotEmpty();
RuleFor(_ => _.MiddleName)
.SetValidator(new NotWhiteSpaceValidator<Person>());
RuleFor(_ => _.FamilyName)
.NotEmpty();
RuleFor(_ => _.Dob)
.NotEqual(DateTimeOffset.MinValue);
}
}
Given the following models:
public interface IDbRecord
{
public byte[] RowVersion { get; }
public Guid Id { get; }
}
public class Person :
IDbRecord
{
public Guid Id { get; set; }
public string Name { get; set; }
public byte[] RowVersion { get; set; }
}
It is desirable to have the rules for IDbRecord
defined separately, and not need to duplicate them for every implementing class. This can be done using shares rules.
Configure any shared rules at startup:
[ModuleInitializer]
public static void Init() =>
ValidatorConventions.ValidatorFor<IDbRecord>()
.RuleFor(record => record.RowVersion)
.Must(rowVersion => rowVersion?.Length == 8)
.WithMessage("RowVersion must be 8 bytes");
The PersonValidator
used only the standard rules, so needs no constructor.
class PersonValidator :
ExtendedValidator<Person>;
The above is equivalent to:
class PersonValidatorEquivalent :
AbstractValidator<Person>
{
public PersonValidatorEquivalent()
{
RuleFor(_ => _.Id)
.NotEqual(Guid.Empty);
RuleFor(_ => _.Name)
.NotEmpty();
RuleFor(_ => _.RowVersion)
.NotNull()
.Must(rowVersion => rowVersion?.Length == 8)
.WithMessage("RowVersion must be 8 bytes");
}
}
Pointed Star designed by Eliricon from The Noun Project.