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. Reason: if the absence of a value is valid, then make the member nullable.
String cannot be String.Empty or only white-space. Reason: if the absence of text is valid, then make the member nullable. This helps since nullable is a first class strong typed 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. Reason: if the absence of any values is valid, then make the member nullable. This helps since nullable is a first class strong typed feature, where "list contains no values" is a runtime check.
There are two ways of applying the extended rules.
Using a base class ExtendedValidator:
classPersonValidatorFromBase:ExtendedValidator<Person>{publicPersonValidatorFromBase(){//TODO: add any extra rules}}Using an extension method AddExtendedRules:
classPersonValidatorNonBase:AbstractValidator<Person>{publicPersonValidatorNonBase()=>this.AddExtendedRules();//TODO: add any extra rules}The above are equivalent to:
publicclassPerson{publicGuidId{get;set;}publicstringFirstName{get;set;}publicstring?MiddleName{get;set;}publicstringFamilyName{get;set;}publicDateTimeOffsetDob{get;set;}}classPersonValidatorEquivalent:AbstractValidator<Person>{publicPersonValidatorEquivalent(){RuleFor(_ =>_.Id).NotEqual(Guid.Empty);RuleFor(_ =>_.FirstName).NotEmpty();RuleFor(_ =>_.MiddleName).SetValidator(newNotWhiteSpaceValidator<Person>());RuleFor(_ =>_.FamilyName).NotEmpty();RuleFor(_ =>_.Dob).NotEqual(DateTimeOffset.MinValue);}}Given the following models:
publicinterfaceIDbRecord{publicbyte[]RowVersion{get;}publicGuidId{get;}}publicclassPerson:IDbRecord{publicGuidId{get;set;}publicstringName{get;set;}publicbyte[]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]publicstaticvoidInit()=>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.
classPersonValidator:ExtendedValidator<Person>;The above is equivalent to:
classPersonValidatorEquivalent:AbstractValidator<Person>{publicPersonValidatorEquivalent(){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.