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. 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
}
}
snippet source | anchor
Using an extension method AddExtendedRules
:
class PersonValidatorNonBase :
AbstractValidator<Person>
{
public PersonValidatorNonBase() =>
this.AddExtendedRules();
//TODO: add any extra rules
}
snippet source | anchor
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; }
}
snippet source | anchor
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);
}
}
snippet source | anchor
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; }
}
snippet source | anchor
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");
snippet source | anchor
The PersonValidator
used only the standard rules, so needs no constructor.
class PersonValidator :
ExtendedValidator<Person>;
snippet source | anchor
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");
}
}
snippet source | anchor
Pointed Star designed by Eliricon from The Noun Project.