Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The Archive Base

The Archive Base Logo The Archive Base Logo

The Archive Base Navigation

  • SEARCH
  • Home
  • About Us
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • Add group
  • Groups page
  • Feed
  • User Profile
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Buy Points
  • Users
  • Help
  • Buy Theme
  • SEARCH
Home/ Questions/Q 8229423
In Process

The Archive Base Latest Questions

Editorial Team
  • 0
Editorial Team
Asked: June 7, 20262026-06-07T16:47:27+00:00 2026-06-07T16:47:27+00:00

I have a model to validate and the problem is the date of birth

  • 0

I have a model to validate and the problem is the date of birth field.
It must be composed of 3 dropdowns (day, month, year).

<div id="dob-editor-field" class="model-field-editor">
      @Html.LabelFor(m => m.DateOfBirth, new { @class = "label-div" })
      @Html.Telerik().DropDownList().Name("DobDay").BindTo((SelectList)ViewData["Days"]).HtmlAttributes(new {id = "DobDaySel"})
      @Html.Telerik().DropDownList().Name("DobMonth").BindTo((SelectList)ViewData["Months"]).HtmlAttributes(new { id = "DobMonthSel"})
      @Html.Telerik().DropDownList().Name("DobYear").BindTo((SelectList)ViewData["Years"]).HtmlAttributes(new { id = "DobYearSel" })
      @Html.ValidationMessageFor(m => m.DateOfBirth)
</div>

On the server side i do this

        [HttpPost]
        public ActionResult Register(RegistrationModel regInfo, int DobDay, int DobMonth, int DobYear)
        {
            SetRegisterViewData(DobDay, DobMonth, DobYear);
            if (DobDay == 0 || DobMonth == 0 && DobYear == 0)
            {
                ModelState.AddModelError("DateOfBirth", "Date of birth is required");
            }
            else
            {
                DateTime dt = new DateTime(DobYear, DobMonth, DobDay);
                long ticks = DateTime.Now.Ticks - dt.Ticks;
                int years = new DateTime(ticks).Year;
                if (years < 18)
                {
                    ModelState.AddModelError("DateOfBirth", "You must be at least 18");
                }
            }            
            if (ModelState.IsValid)
            {
                //register user
                return RedirectToAction("Index", "Home");
            }
            return View(regInfo);
        }

Questions:

  1. Server side : how to make it better? (i am thinking of adding dob,
    month, and year properties RegistrationModel and add attribute on
    DateOfBirth to check those properties)
  2. Client side : i was looking at Perform client side validation for custom attribute but it got me confused. What is the way to make it?

LE:
I created a custom model binder for the date like this:

    public class DobModelBinder : DefaultModelBinder
    {
        protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
        {
            if (propertyDescriptor.Name == "DateOfBirth")
            {
                DateTime dob = DateTime.MinValue;
                var form = controllerContext.HttpContext.Request.Form;
                int day = Convert.ToInt32(form["DobDay"]);
                int month = Convert.ToInt32(form["DobMonth"]);
                int year = Convert.ToInt32(form["DobYear"]);
                if (day == 0 || month == 0 || year == 0)
                {
                    SetProperty(controllerContext, bindingContext, propertyDescriptor, DateTime.MinValue);
                }
                else
                {
                    SetProperty(controllerContext, bindingContext, propertyDescriptor, new DateTime(year, month, day));
                }
            }
            else
            {
                base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
            }
        }
    }

I registered it like this:

ModelBinders.Binders.Add(typeof(DateTime), new DobModelBinder());

I used it like this:

public ActionResult Register([ModelBinder(typeof(DobModelBinder))]RegistrationModel regInfo)

DateOfBirth binds well.

LE2:

I created validation attributes for the date of birth like this:

 public override bool IsValid(object value)
    {
        DateTime date = Convert.ToDateTime(value);
        return date != DateTime.MinValue;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        yield return new ModelClientValidationRule
        {
            ErrorMessage = this.ErrorMessage,
            ValidationType = "dateRequired"
        };
    }
}

public class DateGraterThanEighteen : ValidationAttribute, IClientValidatable
{
    public override bool IsValid(object value)
    {
        DateTime date = Convert.ToDateTime(value);
        long ticks = DateTime.Now.Ticks - date.Ticks;
        int years = new DateTime(ticks).Year;
        return years >= 18;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        yield return new ModelClientValidationRule
        {
            ErrorMessage = this.ErrorMessage,
            ValidationType = "dateGraterThanEighteen"
        };
    }
}

I applied attributes like this

        [DateGraterThanEighteen(ErrorMessage="You must be at least 18")]
        [DateRequired(ErrorMessage = "Date of birth is required")]
        public DateTime DateOfBirth { get; set; }

LE3:

In the client side i do this:

      $(function () {
            jQuery.validator.addMethod('dobRequired', function (value, element, params) {
                if (!/Invalid|NaN/.test(new Date(value))) {
                    return true;
                }
                else {
                    return false;
                }
            }, '');
            jQuery.validator.unobtrusive.adapters.add('dateRequired', {}, function (options) {
                options.rules['dobRequired'] = true;
                options.messages['dobRequired'] = options.message;
            });
        });

Client validation doesn’t seems to work.
How can I fix it? I am kinda confused with the way these adapters work.

  • 1 1 Answer
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

1 Answer

  • Voted
  • Oldest
  • Recent
  • Random
  1. Editorial Team
    Editorial Team
    2026-06-07T16:47:29+00:00Added an answer on June 7, 2026 at 4:47 pm

    You could use a custom editor template.

    Let’s first look at how the final solution might look like first before getting into implementation details.

    So we could have a view model (as always) decorated with some data annotation attributes indicating the metadata we would like to attach to it:

    public class MyViewModel
    {
        [DisplayName("Date of birth:")]
        [TrippleDDLDateTime(ErrorMessage = "Please select a valid DOB")]
        [Required(ErrorMessage = "Please select your DOB")]
        [MinAge(18, ErrorMessage = "You must be at least 18 years old")]
        public DateTime? Dob { get; set; }
    }
    

    then we could have a controller:

    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            var model = new MyViewModel();
            return View(model);
        }
    
        [HttpPost]
        public ActionResult Index(MyViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }
    
            return Content(
                string.Format(
                    "Thank you for selecting your DOB: {0:yyyy-MM-dd}", 
                    model.Dob
                )
            );
        }
    }
    

    a view (~/Views/Home/Index.cshtml):

    @model MyViewModel
    @using (Html.BeginForm())
    {
        @Html.EditorFor(x => x.Dob)
        <button type="submit">OK</button>
    }
    

    and a corresponding editor template which will allow us to display 3 dropdown lists for editing the DateTime field instead of a simple textbox (~/Views/Shared/EditorTemplates/TrippleDDLDateTime.cshtml):

    @{
        var now = DateTime.Now;
        var years = Enumerable.Range(0, 150).Select(x => new SelectListItem { Value = (now.Year - x).ToString(), Text = (now.Year - x).ToString() });
        var months = Enumerable.Range(1, 12).Select(x => new SelectListItem { Value = x.ToString("00"), Text = x.ToString() });
        var days = Enumerable.Range(1, 31).Select(x => new SelectListItem { Value = x.ToString("00"), Text = x.ToString() });
    
        var result = ViewData.ModelState[ViewData.TemplateInfo.HtmlFieldPrefix];
        if (result != null)
        { 
            var values = result.Value.RawValue as string[];
            years = new SelectList(years, "Value", "Text", values[0]);
            months = new SelectList(months, "Value", "Text", values[1]);
            days = new SelectList(days, "Value", "Text", values[2]);
            result.Value = null;
        }
    }
    
    <div class="trippleddldatetime">
        @Html.Label("")
    
        @Html.DropDownList("", years, "-- year --")
        @Html.DropDownList("", months, "-- month --")
        @Html.DropDownList("", days, "-- day --")
    
        @Html.ValidationMessage("")
    </div>
    

    Now let’s see how the [TrippleDDLDateTime] attribute could be implemented:

    public class TrippleDDLDateTimeAttribute : ValidationAttribute, IMetadataAware
    {
        public void OnMetadataCreated(ModelMetadata metadata)
        {
            metadata.TemplateHint = "TrippleDDLDateTime";
        }
    
        public override bool IsValid(object value)
        {
            // It's the custom model binder that is responsible for validating 
            return true;
        }
    }
    

    Notice how the attribute implements the IMetadataAware interface which allows us to associate the view model property with the custom editor template we wrote (TrippleDDLDateTime.cshtml).

    And next comes the [MinAge] attribute:

    public class MinAgeAttribute : ValidationAttribute
    {
        private readonly int _minAge;
        public MinAgeAttribute(int minAge)
        {
            _minAge = minAge;
        }
    
        public override bool IsValid(object value)
        {
            if (value == null)
            {
                return true;
            }
    
            DateTime date = Convert.ToDateTime(value);
            long ticks = DateTime.Now.Ticks - date.Ticks;
            int years = new DateTime(ticks).Year;
            return years >= _minAge;
        }
    }
    

    The last piece of the puzzle is to write a custom model binder that will be associated to properties decorated with the [TrippleDDLDateTime] attribute in order to perform the parsing:

    public class TrippleDDLDateTimeModelBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var metadata = bindingContext.ModelMetadata;
            var trippleDdl = metadata.ContainerType.GetProperty(metadata.PropertyName).GetCustomAttributes(typeof(TrippleDDLDateTimeAttribute), true).FirstOrDefault() as TrippleDDLDateTimeAttribute;
            if (trippleDdl == null)
            {
                return base.BindModel(controllerContext, bindingContext);
            }
    
            var prefix = bindingContext.ModelName;
            var value = bindingContext.ValueProvider.GetValue(prefix);
            var parts = value.RawValue as string[];
            if (parts.All(string.IsNullOrEmpty))
            {
                return null;
            }
    
            bindingContext.ModelState.SetModelValue(prefix, value);
    
            var dateStr = string.Format("{0}-{1}-{2}", parts[0], parts[1], parts[2]);
            DateTime date;
            if (DateTime.TryParseExact(dateStr, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
            {
                return date;
            }
    
            bindingContext.ModelState.AddModelError(prefix, trippleDdl.ErrorMessage);
    
            return null;
        }
    }
    

    Notice how the binder simply uses the default binder if the field is not decorated with the custom attribute. This way it doesn’t interfere with other DateTime fields for which we don’t want the tripple ddl behavior. The model binder will simply be associated with the DateTime? type in Application_Start:

    ModelBinders.Binders.Add(typeof(DateTime?), new TrippleDDLDateTimeModelBinder());
    

    OK, so far we have a solution that performs server side validation. That’s always what you should start with. Because that’s where you can also stop and still have a safe and working site.

    Of course if you have time you could now improve the user experience by implementing client side validation. Client side validation is not compulsory, but it saves bandwidth and avoids server round-trips.

    So we start by making our 2 custom attributes implement the IClientValidatable interface which is the first step in enabling unobtrusive client side validation.

    [TrippleDDLDateTime]:

    public class TrippleDDLDateTimeAttribute : ValidationAttribute, IMetadataAware, IClientValidatable
    {
        public void OnMetadataCreated(ModelMetadata metadata)
        {
            metadata.TemplateHint = "TrippleDDLDateTime";
        }
    
        public override bool IsValid(object value)
        {
            // It's the custom model binder that is responsible for validating 
            return true;
        }
    
        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            var rule = new ModelClientValidationRule();
            rule.ErrorMessage = ErrorMessage;
            rule.ValidationType = "trippleddldate";
            yield return rule;
        }
    }
    

    [MinAge]:

    public class MinAgeAttribute : ValidationAttribute, IClientValidatable
    {
        private readonly int _minAge;
        public MinAgeAttribute(int minAge)
        {
            _minAge = minAge;
        }
    
        public override bool IsValid(object value)
        {
            if (value == null)
            {
                return true;
            }
    
            DateTime date = Convert.ToDateTime(value);
            long ticks = DateTime.Now.Ticks - date.Ticks;
            int years = new DateTime(ticks).Year;
            return years >= _minAge;
        }
    
        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            var rule = new ModelClientValidationRule();
            rule.ErrorMessage = ErrorMessage;
            rule.ValidationType = "minage";
            rule.ValidationParameters["min"] = _minAge;
            yield return rule;
        }
    }
    

    OK, so we have implemented the GetClientValidationRules on both attributes. All that’s left is to write the corresponding unobtrusive adapters.

    This should be done in a separate javascript file of course. For example it could be trippleddlAdapters.js:

    (function ($) {
        $.fn.getDateFromTrippleDdls = function () {
            var year = this.find('select:nth(0)').val();
            var month = this.find('select:nth(1)').val();
            var day = this.find('select:nth(2)').val();
            if (year == '' || month == '' || day == '') {
                return NaN;
            }
    
            var y = parseInt(year, 10);
            var m = parseInt(month, 10);
            var d = parseInt(day, 10);
    
            var date = new Date(y, m - 1, d);
            var isValidDate = date.getFullYear() == y && date.getMonth() + 1 == m && date.getDate() == d;
            if (isValidDate) {
                return date;
            }
    
            return NaN;
        };
    
        $.validator.unobtrusive.adapters.add('trippleddldate', [], function (options) {
            options.rules['trippleddldate'] = options.params;
            if (options.message) {
                options.messages['trippleddldate'] = options.message;
            }
        });
    
        $.validator.addMethod('trippleddldate', function (value, element, params) {
            var parent = $(element).closest('.trippleddldatetime');
            var date = parent.getDateFromTrippleDdls();
            console.log(date);
            return !isNaN(date);
        }, '');
    
        $.validator.unobtrusive.adapters.add('minage', ['min'], function (options) {
            options.rules['minage'] = options.params;
            if (options.message) {
                options.messages['minage'] = options.message;
            }
        });
    
        $.validator.addMethod('minage', function (value, element, params) {
            var parent = $(element).closest('.trippleddldatetime');
            var birthDate = parent.getDateFromTrippleDdls();
            if (isNaN(birthDate)) {
                return false;
            }
    
            var today = new Date();
            var age = today.getFullYear() - birthDate.getFullYear();
            var m = today.getMonth() - birthDate.getMonth();
            if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
                age--;
            }
            return age >= parseInt(params.min, 10);
        }, '');
    })(jQuery);
    

    Finally we include the 3 necessary scripts to the page to enable the unobtrusive client side validation:

    <script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/trippleddlAdapters.js")" type="text/javascript"></script>
    
    • 0
    • Reply
    • Share
      Share
      • Share on Facebook
      • Share on Twitter
      • Share on LinkedIn
      • Share on WhatsApp
      • Report

Sidebar

Related Questions

I have the simplest model with a single Date field that I want to
I have a fairly complex model needing to be validated, the problem is that
I have a date field (i'm using the jquery ui datepicker) in a form
I have a model with a StartTime date and an EndTime date : [Required]
I have a very odd problem with unobtrusive validation. I have a model with
Good morning fellow Overflowers, Small problem with model associations. I have these model associations:
Good afternoon. I have a Model that has a field called percentage. All similar
My problem is the next: I have a method which validate the image dimension
I have the following problem: I need to validate data in a controller, which
I have two tables: Client(id,name,...) Purchase(id,item,date,client_id,...) They have their respective Model, with their validations.

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help
  • SEARCH

Footer

© 2021 The Archive Base. All Rights Reserved
With Love by The Archive Base

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.