ASP.NET MVC - saving an invalid DateTime selection with three drop-down lists

I'm still pretty new to ASP.NET and MVC, and despite days of searching and experimenting, I am drawing a space in the best way to solve this problem.

I wrote the BirthdayAttribute attribute, which should work similarly to EmailAddressAttribute. The birthday attribute sets the UI hint so that the DateTime's birthday is displayed using an editor template that has three drop-down lists. The attribute can also be used to set some additional metadata that reports the drop-down year, how many years it should be displayed.

I know that I can use jQuery date picker, but in case of a birthday I find that 3 dropdown menus are much more useful.

@model DateTime
@using System;
@using System.Web.Mvc;
@{
    UInt16 numberOfVisibleYears = 100;
    if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("NumberOfVisibleYears"))
    {
        numberOfVisibleYears = Convert.ToUInt16(ViewData.ModelMetadata.AdditionalValues["NumberOfVisibleYears"]);
    }
    var now = DateTime.Now;
    var years = Enumerable.Range(0, numberOfVisibleYears).Select(x => new SelectListItem { Value = (now.Year - x).ToString(), Text = (now.Year - x).ToString() });
    var months = Enumerable.Range(1, 12).Select(x => new SelectListItem{ Text = new DateTime( now.Year, x, 1).ToString("MMMM"), Value = x.ToString() });
    var days = Enumerable.Range(1, 31).Select(x => new SelectListItem { Value = x.ToString("00"), Text = x.ToString() });
}

@Html.DropDownList("Year", years, "<Year>") /
@Html.DropDownList("Month", months, "<Month>") /
@Html.DropDownList("Day", days, "<Day>")

ModelBinder . , . , , .

public class DateSelector_DropdownListBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        if (controllerContext == null)
            throw new ArgumentNullException("controllerContext");
        if (bindingContext == null)
            throw new ArgumentNullException("bindingContext");

        if (IsDropdownListBound(bindingContext))
        {
            int year    = GetData(bindingContext, "Year");
            int month   = GetData(bindingContext, "Month");
            int day     = GetData(bindingContext, "Day");

            DateTime result;
            if (!DateTime.TryParse(string.Format("{0}/{1}/{2}", year, month, day), out result))
            {
                //TODO: SOMETHING MORE USEFUL???
                bindingContext.ModelState.AddModelError("", string.Format("Not a valid date."));
            }

            return result;
        }
        else
        {
            return base.BindModel(controllerContext, bindingContext);
        }

    }

    private int GetData(ModelBindingContext bindingContext, string propertyName)
    {
        // parse the int using the correct value provider
    }

    private bool IsDropdownListBound(ModelBindingContext bindingContext)
    {
        //check model meta data UI hint for above editor template
    }
}

, , , , DateTime , , .

, , - , 30 31 . , .

30 , . , ( EmailAddressAttribute), .

. , .

, Javascript AJAX, , - , .

+5
1

- , .

: .NET 2.0 , #, ASP.NET, MVC Entity Framework. -, , .

TODO:

  • , 30 . [] .

  • ,

, , , , , DateTime , 30 . . , , ViewModel.

, DateTime Date. , Javascript . , .

date-ish DateTime DateTime .

Date.cs

public class Date
{
    public Date() : this( System.DateTime.MinValue ) {}
    public Date(DateTime date)
    {
        Year = date.Year;
        Month = date.Month;
        Day = date.Day;
    }

    [Required]
    public int Year  { get; set; }

    [Required, Range(1, 12)]
    public int Month { get; set; }

    [Required, Range(1, 31)]
    public int Day   { get; set; }

    public DateTime? DateTime
    {
        get
        {
            DateTime date;
            if (!System.DateTime.TryParseExact(string.Format("{0}/{1}/{2}", Year, Month, Day), "yyyy/M/d", CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
                return null;
            else
                return date;
        }
    }
}

, DateTime. Year, Month Day, GetTime getter, DateTime, . null.

DefaultModelBinder Date, . ValidationAtribute, , , 30 , .

DateValidationAttribute.cs

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public sealed class DateValidationAttribute : ValidationAttribute
{
    public DateValidationAttribute(string classKey, string resourceKey) :
        base(HttpContext.GetGlobalResourceObject(classKey, resourceKey).ToString()) { }

    public override bool IsValid(object value)
    {
        bool result = false;
        if (value == null)
            throw new ArgumentNullException("value");

        Date toValidate = value as Date;

        if (toValidate == null)
            throw new ArgumentException("value is an invalid or is an unexpected type");

        //DateTime returns null when date cannot be constructed
        if (toValidate.DateTime != null)
        {
            result = (toValidate.DateTime != DateTime.MinValue) && (toValidate.DateTime != DateTime.MaxValue);
        }

        return result;
    }
}

ValidationAttribute, . , "App_GlobalResources" .

IsValid, , , DateTime, , , . DateTime.MinValue MaxValue .

. Date ModelBinder. DefaultModelBinder, , . -, DateValidationAttribute, . , , , . .

, .

DateSelector_DropdownList.cshtml

@model Date
@{
    UInt16 numberOfVisibleYears = 100;
    if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("NumberOfVisibleYears"))
    {
        numberOfVisibleYears = Convert.ToUInt16(ViewData.ModelMetadata.AdditionalValues["NumberOfVisibleYears"]);
    }
    var now = DateTime.Now;
    var years = Enumerable.Range(0, numberOfVisibleYears).Select(x => new SelectListItem { Value = (now.Year - x).ToString(), Text = (now.Year - x).ToString() });
    var months = Enumerable.Range(1, 12).Select(x => new SelectListItem { Text = new DateTime(now.Year, x, 1).ToString("MMMM"), Value = x.ToString() });
    var days = Enumerable.Range(1, 31).Select(x => new SelectListItem { Value = x.ToString(), Text = x.ToString() });
}

@Html.DropDownList("Year", years, "<Year>") /
@Html.DropDownList("Month", months, "<Month>") /
@Html.DropDownList("Day", days, "<Day>")

, , .

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class DateSelector_DropdownListAttribute : DataTypeAttribute, IMetadataAware
{
    public DateSelector_DropdownListAttribute() : base(DataType.Date) { }

    public void OnMetadataCreated(ModelMetadata metadata)
    {
        metadata.AdditionalValues.Add("NumberOfVisibleYears", NumberOfVisibleYears);
        metadata.TemplateHint = TemplateHint;
    }

    public string TemplateHint { get; set; }
    public int NumberOfVisibleYears { get; set; }
}

, , . , . , - DateTime, , , .

, ?

+2

All Articles