I'm working on a project with lots of complicated Validation Rules. We are using Fluent Validation to help with this and its a wonderful tool. However, our models often have pairs of Nullable Datetimes indicating the start and end of something. The current model I am working on has three different pairs and I notice the validation is inconsistent because we are writing out the validation rules for this situation each time. Not DRY in the least. I am looking for a way to have a common pair of methods to validate a pair of datetimes with Fluent Validation.
So far I have found two approaches but neither really fits. The first is to use a custom PropertyValidator as described below, but using this you only have access to one property. I can't find a way to pass both datetimes in and can't pass in the parent model because this will be used on many models (Could do something with an interface but thats a lot of models to change...). Fluent validation with dynamic message
The Second approach is using Must with the predicate overload so i can pass in both my datetimes. This works but I can't figure out how to pass any messages back about which error is occurring, it is either true or false. This is my current testing code:
RuleFor(i => i.Start)
.Must((model,start) => BeValidDatePair(start, model.End));
public bool BeValidDatePair(DateTime? start, DateTime? end)
{
// Both values must be set or none
if (start.HasValue && !end.HasValue)
{
return false;
}
if (end.HasValue && !start.HasValue)
{
return false;
}
// End cannot exceed start by two years
if (end.Value > start.Value.AddYears(2))
{
return false;
}
return true;
}
Ideally, I would have two functions, one for the Start date, and one for the end. In each function I do the validations, and return which validation is failing. Any ideas?
// Ideal Case...
public class MyModel
{
string Title { get; set; }
DateTime? Start { get; set; }
DateTime? End { get; set; }
}
RuleFor(i => i.Start)
.Must((model,start) => BeValidStartDate(start, model.End));
RuleFor(i => i.End)
.Must((model,end) => BeValidEndDate(end, model.Start));
public bool BeValidStartDate(DateTime? start, DateTime? end)
{
// start must come before end
if (start.Value < end.Value)
{
// psuedocode...
return ValidationMessage("Start must come before end.");
}
// other validations...
}