FluentValidation - validating across multiple properties

seekay picture seekay · Oct 9, 2011 · Viewed 19k times · Source

Have a form where a user can enter start date/time and end date/time for an event. Here's the validator so far:

public class EventModelValidator : AbstractValidator<EventViewModel>
    {
        public EventModelValidator()
        {
            RuleFor(x => x.StartDate)
                .NotEmpty().WithMessage("Date is required!")
                .Must(BeAValidDate).WithMessage("Invalid date");
            RuleFor(x => x.StartTime)
                .NotEmpty().WithMessage("Start time is required!")
                .Must(BeAValidTime).WithMessage("Invalid Start time");
            RuleFor(x => x.EndTime)
                .NotEmpty().WithMessage("End time is required!")
                .Must(BeAValidTime).WithMessage("Invalid End time");
            RuleFor(x => x.Title).NotEmpty().WithMessage("A title is required!");
        }


        private bool BeAValidDate(string value)
        {
            DateTime date;
            return DateTime.TryParse(value, out date);
        }

        private bool BeAValidTime(string value)
        {
            DateTimeOffset offset;
            return DateTimeOffset.TryParse(value, out offset);
        }

    }

Now I'd also like to add validation that EndDateTime > StartDateTime (combined Date+Time properties), but not sure how to go about it.

Edit: To clarify, I need to somehow combine EndDate + EndTime/StartDate + StartTime i.e. DateTime.Parse(src.StartDate + " " + src.StartTime) and then validate EndDateTime vs. StartDateTime - how do I do that?

Answer

seekay picture seekay · Oct 10, 2011

Finally got it working after I re-read the documentation: "Note that there is an additional overload for Must that also accepts an instance of the parent object being validated."

public class EventModelValidator : AbstractValidator<EventViewModel>
    {
        public EventModelValidator()
        {
            RuleFor(x => x.StartDate)
                .NotEmpty().WithMessage("Date is required!")
                .Must(BeAValidDate).WithMessage("Invalid date");
            RuleFor(x => x.StartTime)
                .NotEmpty().WithMessage("Start time is required!")
                .Must(BeAValidTime).WithMessage("Invalid Start time");
            RuleFor(x => x.EndTime)
                .NotEmpty().WithMessage("End time is required!")
                .Must(BeAValidTime).WithMessage("Invalid End time")
                // new
                .Must(BeGreaterThan).WithMessage("End time needs to be greater than start time");
            RuleFor(x => x.Title).NotEmpty().WithMessage("A title is required!");
        }


        private bool BeAValidDate(string value)
        {
            DateTime date;
            return DateTime.TryParse(value, out date);
        }

        private bool BeAValidTime(string value)
        {
            DateTimeOffset offset;
            return DateTimeOffset.TryParse(value, out offset);
        }
        // new
        private bool BeGreaterThan(EventViewModel instance, string endTime)
        {
            DateTime start = DateTime.Parse(instance.StartDate + " " + instance.StartTime);
            DateTime end = DateTime.Parse(instance.EndDate + " " + instance.EndTime);
            return (DateTime.Compare(start, end) <= 0);
        }
    }

There might be a cleaner/more legant way to do this, but for now, it worksforme.