Search code examples
asp.netasp.net-mvc-5data-annotationsdatetime-formatdate-formatting

MVC 5 does not validate properly a Model's field with custom DateTime annotation


In my ASP.NET MVC 5 project, I added a DataAnnotation to format the model's DateTime field like "dd/mm/yyyy", however when the field is rendered in the Edit View by @Html.EditorFor this is still validated as a date like "mm/dd/yyyy" (for example, if I insert a date like "13/12/2019" I got an error because day 13 is validated as a month).

This is the Entity Model generated from the database:

namespace MyProject
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;

    public partial class Supplier
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public Supplier()
        {
            this.Brands = new HashSet<Brand>();
        }

        public long Id { get; set; }
        public string Name { get; set; }
        public string Agent { get; set; }
        public string Address { get; set; }
        public string Phone { get; set; }
        public string Email { get; set; }

        public Nullable<System.DateTime> NextOrderDate { get; set; }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual ICollection<Brand> Brands { get; set; }
    }
}

I use the work-around explained here to add DataAnnotations so they are not wiped-out when regenerating entities from database, so I added also this metadata class:

namespace MyProject
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;

    [MetadataType(typeof(SupplierMetadata))]
    public partial class Supplier
    {
        // Note this class has nothing in it.  It's just here to add the class-level attribute.
    }

    public class SupplierMetadata
    {
        [Required]
        public string Name { get; set; }

        [Required]
        public string Agent { get; set; }

        [Required]
        public string Address { get; set; }

        [Required]
        public string Phone { get; set; }

        [Required]
        public string Email { get; set; }

        [Display(Name = "Next Order")]
        [DisplayFormat(DataFormatString = "{0:dd/mm/yyyy}")]
        public Nullable<System.DateTime> NextOrderDate { get; set; }

    }

And this is how my field is rendered:

<div class="form-group">
    @Html.LabelFor(model => model.NextOrderDate, htmlAttributes: new { @class = "col-form-label col-lg-2" })
    <div class="col-lg-10">
        @Html.EditorFor(model => model.NextOrderDate, new { htmlAttributes = new { @class = "form-control" } })
        @Html.ValidationMessageFor(model => model.NextOrderDate, "", new { @class = "text-danger" })
    </div>
</div>

I also added this code to Global.asax.cs but nothing is changed:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Web;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace MyProject
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {

            CultureInfo newCulture = (CultureInfo)Thread.CurrentThread.CurrentCulture.Clone();
            newCulture.DateTimeFormat.ShortDatePattern = "dd/mm/yyyy";
            newCulture.DateTimeFormat.DateSeparator = "/";
            Thread.CurrentThread.CurrentCulture = newCulture;

            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
    }
}

Solution

  • I resolved this issue due to jQuery validation not accounting for the culture when parsing a date. If you turn off the client side validation the date is parsed just fine on the server that is aware of the culture. The fix is to override the jQuery validation for date and include an additional jQuery globalization plugin.

    My finally working solution is

    • Install moment.js using:

      Install-Package Moment.js

    And then add fix for date format parser in the View:

    @section Scripts {
        @Scripts.Render("~/bundles/jqueryval")
        @Scripts.Render("~/Scripts/moment.js")
    
        <script>
    
    
            $(function () {
                $.validator.methods.date = function (value, element) {
                    return this.optional(element) || moment(value, "DD/MM/YYYY", true).isValid();
                }
            });
        </script>
    }