views:

49

answers:

0

In ASP.NET MVC 2, how would you go about binding a view model property that is a DateTime where the application must have 3 drop down lists for choosing month, day, year?I've read Scott H.'s blog post about binding dates some time ago, and that seems entirely too convoluted for such a simple case. Surely there's a cleaner / better way to do it?

Whatever solution I use, I would like to retain built-in validation using the DataAnnotations stuff, and I'd also like to be able to specify a min / max date range using a validation attribute.

My first thought was a simple custom model binder like so:

protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
    var model = bindingContext.Model as RsvpViewModel;
    var form = controllerContext.HttpContext.Request.Form;

    if (model == null) 
        throw new ArgumentException("bindingContext.Model");

    if (propertyDescriptor.Name.Equals("BirthDate"))
    {
        if (!string.IsNullOrEmpty(form["BirthYear"]) &&
            !string.IsNullOrEmpty(form["BirthMonth"]) &&
            !string.IsNullOrEmpty(form["BirthDay"]))
        {
            try
            {
                var yy = int.Parse(form["BirthYear"]);
                var mm = int.Parse(form["BirthMonth"]);
                var dd = int.Parse(form["BirthDay"]);
                model.BirthDate = new DateTime(yy, mm, dd);
                return;
            }
            catch (Exception)
            {
                model.BirthDate = DateTime.MinValue;
                return;
            }
        }
    }

    base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}

Then I tried creating a DateTimeAttribute to do the validation, but ran into some difficulty specifying a date range in the attribute declaration because attribute parameter types are limited, and DateTime is not one of the allowable types.

I ended up creating an IDateRangeProvider interface and an implementation specific to birth dates like so:

public interface IDateRangeProvider
{
    DateTime GetMin();
    DateTime GetMax();
}

public class BirthDateRangeProvider : IDateRangeProvider
{
    public DateTime GetMin()
    {
        return DateTime.Now.Date.AddYears(-100);
    }

    public DateTime GetMax()
    {
        return DateTime.Now.Date;
    }
}

This allowed me to use a DateTime property on my view model and retain all of the build in goodness...

[DisplayName("Date of Birth:")]
[Required(ErrorMessage = "Date of birth is required")]
[DateTime(ErrorMessage = "Date of birth is invalid", RangeProvider=typeof(BirthDateRangeProvider))]
public DateTime? BirthDate { get; set; }

But really, the whole solution smells of overengineering and overthinking it. Isn't there a better way?