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?