0

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...
}
Community
  • 1
  • 1
ChrisOPeterson
  • 559
  • 1
  • 6
  • 24

1 Answers1

1

You could add a third parameter using the out modifier and pass the message through that:

public bool BeValidDatePair(DateTime? start, DateTime? end, out string message)
{
    ...

    if (start.Value < end.Value) message = "Start must come before end.";
}

Then you would call it like this:

string message = String.Empty;
BeValidDatePair(dateone, datetwo, message);

https://msdn.microsoft.com/en-us/library/t3c3bfhx.aspx


Another option may be to throw your own Exception types and catch them implicitly with a try/catch:

public class DateOutOfRangeException : Exception
{
    ...
}

And:

catch (DateOutOfRangeException ex)

Then:

if (start.Value < end.Value) throw new DateOutOfRangeException();

https://msdn.microsoft.com/en-us/library/87cdya3t(v=vs.110).aspx

Tim
  • 857
  • 6
  • 13