views:

422

answers:

4

What approach do you recommend for validating a DateTime on the client side in MVC?

Let's say I have a model with a property named DateOfBirth that is a DateTime, like so.

public class UserModel
{
    [DataType(DataType.Date)]
    public DateTime DateOfBirth {get;set;}
}

On the View, I have a simple

<%: Html.LabelFor(model=>model.DateOfBirth) %>
<%: Html.EditorFor(model=>model.DateOfBirth) %>
<%: Html.ValidationMessageFor(model=>model.DateOfBirth) %>
<input type="submit" value="Submit" />

I can use either the Microsoft MVC validations or the jQuery validations. How do I get the DateTime to validate client-side?

I realize all the DataTypeAttribute does is provide formatting hints and doesn't really do any validation (it leaves that part to the ModelBinder).

Basically I want to duplicate what the ModelBinder does when it tries to put the posted value into the model's DateOfBirth property.

What are your recommendations?

A: 

I am up against the same issue and can not find a solution. I can't believe everyone has not run into this issue. I was using the jquery.maskedinput.js module and it worked great, but when I started adding the "[DataType(DataType.Date)]" decoration with "EditorFor" if assigns the datetime input a class of class="text-box single-line". Adding this class breaks the maskedinput js. It also formats my lower dates as "2/3/1010" which then blows my jquery mask of "99/99/9999".

+4  A: 

As suggested above, follow Phil Haack's post on custom validations: http://haacked.com/archive/2009/11/19/aspnetmvc2-custom-validation.aspx

Here's how I would do it:


  1. Add a "DateFormatAttribute" class, like so:

    public class DateFormatAttribute : ValidationAttribute {
      public override bool IsValid(object value) {    
        if (value == null) {
          return true;
        }

        // Note: the actual server side validation still has to be implemented :-)
        // Just returning true now...

        return true;
      }
    }

  1. Add a "DateFormatValidator" class, like so:

    public class DateFormatValidator : DataAnnotationsModelValidator 
    {
      string message;

      public PriceValidator(ModelMetadata metadata, ControllerContext context
        , DateFormatAttribute attribute)
        : base(metadata, context, attribute) 
      {
        message = attribute.ErrorMessage;
      }

      public override IEnumerable GetClientValidationRules() 
      {
        var rule = new ModelClientValidationRule {
          ErrorMessage = message,
          ValidationType = "date" // note that this string will link to the JavaScript function we'll write later on
        };

        return new[] { rule };
      }
    }

  1. Register the above classes somewhere in Global.asax.cs:

    DataAnnotationsModelValidatorProvider
        .RegisterAdapter(typeof(DateFormatAttribute), typeof(DateFormatValidator));

  1. Add a validation function on the client. Note that this will have to be implemented attributng the locale of the user. The following is a Dutch (nl-NL, nl-BE) client-side validation function:

    /*
     * Localized default methods for the jQuery validation plugin.
     * Locale: NL
     */
    jQuery.extend(jQuery.validator.methods, {
        date: function(value, element) {
            return this.optional(element) || /^\d\d?[\.\/-]\d\d?[\.\/-]\d\d\d?\d?$/.test(value);
        }
    });

That should cover things...

maartenba
+1 pretty good and complete explanation
moi_meme
I haven't tried this yet, but it looks good. Question: Since the new attribute doesn't add anything that the DataType(DataType.Date) attribute doesn't already do on the server side, is it possible to just extend that attribute?
Josh
Haven't tried that, but may work...
maartenba
Finally had time to try this. I skipped the ValidationAttribute and just made a DataTypeValidator : DataAnnotationsModelValidator<DataTypeAttribute> class. I used a switch in the GetClientValidationRules to change the rule based on the DataType. Worked like a charm! Thanks for your help!
Josh
+1  A: 

Josh,

your problem is quite a common problem in MVC, which is that the modelbinder is trying to BIND the inputted values from the form into the model. obviously, if it doesnt 'fit' you'll get an error straight away.

so how do I make the modelbinder use my custom validation? and return my error message?

well, first read and do the things written by phil haack. then you have your custom validation.

the next thing is. dont use Integers and datetimes in your model! If the user can input whatever he wants in a textbox, this always will give problems.

what you should do is, make a flatObject of your object.

a flatObject is pretty simple. It's an object, an exact copy of the variables inside, only, the inst and datetimes are strings (cos those always bind in the modelbinder)

example:

namespace MVC2_NASTEST.Models {

    public partial class FlatNieuw {
        public int Niw_ID { get; set; }
        public string Niw_Datum { get; set; }
        public string Niw_Titel { get; set; }
        public string Niw_Bericht { get; set; }
        public int Niw_Schooljaar { get; set; }
        public bool Niw_Publiceren { get; set; }
    }
}

the only ints i have are from the dropdowns, cos those dont fail, if the value in the dropdowns are ints. the date (datum) is a string. i do the custom validation on this string. the modelbinder binds to this FlatNieuw object.

my Nieuw class has exactly the same names of fields as this class. so when you are using UpdateModel() this still works. if you are making a new entry, you can use automapper to map this flatObject to your normal Object.

i think this, together with phil haack's blog should give you a hand on how to do this. if you have questions dont hesitate to ask.

Stefanvds
A: 

according to my experience some times either Microsoft MVC validations or the jQuery validations is a over kill for some projects that we are developing. that is why some times i code/grab small ones by my self.

Solution One: Custom plugin(you can change it into the way suits you)

(function($) {
    $.fn.extend({
            ValidateInput: function() {
                var bValid = true;
                this.removeClass('ui-state-error');
                this.each(function() {
                    if ($(this).hasClass('date')) {

                            var valdate = checkRegexp($(this), /^(([0-2]\d|[3][0-1])\/([0]\d|[1][0-2])\/[1-2]\d{3})$/, "date format is wrong, please input as dd/MM/yyyy, e.g. 02/28/2010");
                            if (!valdate) {
                                $(this).val("input in 'dd/mm/yyyy' format");
                            }
                            bValid = bValid && valdate;
                return bValid;

                        }
                }); 

    }});

    function checkRegexp(o, regexp, n) {
        if (!(regexp.test(o.val()))) {
            o.addClass('ui-state-error');
            //updateTips(n);
            return false;
        } else {
            return true;
        }
        }

 })(jQuery);

on your view:

  1. add class='date' to your input box
  2. call the plugin $("#yourInput").alidateInput();

Solution 2: Use Jquery UI Date Pick (the solution i am using now)

     <script language="javascript" type="text/javascript">
            $(function() {
// use date picker to your textbox input 
                $(".yourInput").datepicker();
                $(".yourInput").datepicker('option', { dateFormat: "dd/mm/yy" });
// disable any human input to the textbox input
                $(".yourInput").keyup(function() {
                    $(this).val("");
                });
            });
        </script>

More Details on Solution 2: http://www.gregshackles.com/2010/03/templated-helpers-and-custom-model-binders-in-asp-net-mvc-2/

D.J