57

How can I force the format of datetime in asp.net mvc 4 ? In display mode it shows as I want but in edit model it doesn't. I am using displayfor and editorfor and applyformatineditmode=true with dataformatstring="{0:dd/MM/yyyy}" What I have tried:

  • globalization in web.config (both of them) with my culture and uiculture.
  • modifying the culture and uiculture in application_start()
  • custom modelbinder for datetime

I have no idea how to force it and I need to input the date as dd/MM/yyyy not the default.

MORE INFO: my viewmodel is like this

    [DisplayName("date of birth")]
    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
    public DateTime? Birth { get; set; }

in view I use @Html.DisplayFor(m=>m.Birth) but this works as expected (I see the formatting) and to input the date I use @Html.EditorFor(m=>m.Birth) but if I try and input something like 13/12/2000 is fails with the error that it is not a valid date (12/13/2000 and 2000/12/13 are working as expected but I need dd/MM/yyyy).

The custom modelbinder is called in application_start() b/c I don't know where else.

Using <globalization/> I have tried with culture="ro-RO", uiCulture="ro" and other cultures that would give me dd/MM/yyyy. I have also tried to set it on a per thread basis in application_start() (there are a lot of examples here, on how to do this)


For all that will read this question: It seems that Darin Dimitrov's answer will work as long as I don't have client validation. Another approach is to use custom validation including client side validation. I'm glad I found this out before recreating the entire application.

casperOne
  • 73,706
  • 19
  • 184
  • 253
amb
  • 1,599
  • 2
  • 11
  • 18
  • Could you please provide a little more info? Your model, controller and view? Also provide an example of the different output you get between the display and editor template. Also notice that the culture is set per thread. You mentioned something about `Application_Start` but this is executed only once, when your application starts. What about subsequent requests? How are you setting the culture for them? – Darin Dimitrov Jun 30 '12 at 09:01
  • application_start in executed only once! Use application_beginRequest instead! – Nas Jun 30 '12 at 09:23
  • Nas, where would application_beginRequest be ? I only see application_start in Global. In mvc 4 things started to be a little different then mvc 3 – amb Jun 30 '12 at 09:55

3 Answers3

103

Ahhhh, now it is clear. You seem to have problems binding back the value. Not with displaying it on the view. Indeed, that's the fault of the default model binder. You could write and use a custom one that will take into consideration the [DisplayFormat] attribute on your model. I have illustrated such a custom model binder here: https://stackoverflow.com/a/7836093/29407


Apparently some problems still persist. Here's my full setup working perfectly fine on both ASP.NET MVC 3 & 4 RC.

Model:

public class MyViewModel
{
    [DisplayName("date of birth")]
    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
    public DateTime? Birth { get; set; }
}

Controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new MyViewModel
        {
            Birth = DateTime.Now
        });
    }

    [HttpPost]
    public ActionResult Index(MyViewModel model)
    {
        return View(model);
    }
}

View:

@model MyViewModel

@using (Html.BeginForm())
{
    @Html.LabelFor(x => x.Birth)
    @Html.EditorFor(x => x.Birth)
    @Html.ValidationMessageFor(x => x.Birth)
    <button type="submit">OK</button>
}

Registration of the custom model binder in Application_Start:

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

And the custom model binder itself:

public class MyDateTimeModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var displayFormat = bindingContext.ModelMetadata.DisplayFormatString;
        var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

        if (!string.IsNullOrEmpty(displayFormat) && value != null)
        {
            DateTime date;
            displayFormat = displayFormat.Replace("{0:", string.Empty).Replace("}", string.Empty);
            // use the format specified in the DisplayFormat attribute to parse the date
            if (DateTime.TryParseExact(value.AttemptedValue, displayFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
            {
                return date;
            }
            else
            {
                bindingContext.ModelState.AddModelError(
                    bindingContext.ModelName,
                    string.Format("{0} is an invalid date format", value.AttemptedValue)
                );
            }
        }

        return base.BindModel(controllerContext, bindingContext);
    }
}

Now, no matter what culture you have setup in your web.config (<globalization> element) or the current thread culture, the custom model binder will use the DisplayFormat attribute's date format when parsing nullable dates.

Community
  • 1
  • 1
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 1
    This is the one I have used but with no luck. I have registered it with both datetime and datetime? It doesn't even show my error message so I don't think it's getting used. – amb Jun 30 '12 at 09:35
  • 1
    Then I will guess you will have to show your full code allowing us to reproduce the problem, because I am using this model binder without any problems. – Darin Dimitrov Jun 30 '12 at 09:37
  • Out of curiosity, what type of MVC project it is (4 or 3) ? – amb Jun 30 '12 at 10:02
  • Tested and working on both ASP.NET MVC 3 & 4 RC. I have updated my answer with my full working example. So now the question becomes: how is your setup different than mine and could you please provide a full example (as I did) allowing us to reproduce the problem you are encountering. Otherwise I don't see how I could help you any further. – Darin Dimitrov Jun 30 '12 at 16:47
  • I have tested your example and it's working. Funny thing is that on my full application it's still failing to bind. What I have extra from you is a @helper in the view so I can insert fields easily. I will have to recreate the entire application and see where it starts to fail. But other then this glitch (on my part) your custom model binder is perfect. – amb Jul 01 '12 at 06:59
  • Perfect, no more UK dates interpreted as US dates from jQuery UI's date picker in MVC 5.2.2. – Richard Oct 09 '14 at 17:00
  • I have one question. Does it work with both `DateTime` and `DatTime?` types? – Celdor Apr 16 '15 at 14:10
  • Does this solution works with jquery datepicker ? ... and where to add this custom model binder – Ibrahim Amer Oct 09 '15 at 01:03
  • Thank you sir very much for your help ... I wish if I could + this post infinite number of times :))) . Thanks again – Ibrahim Amer Oct 26 '15 at 14:24
1

Client validation issues can occur because of MVC bug (even in MVC 5) in jquery.validate.unobtrusive.min.js which does not accept date/datetime format in any way. Unfortunately you have to solve it manually.

My finally working solution:

$(function () {
    $.validator.methods.date = function (value, element) {
        return this.optional(element) || moment(value, "DD.MM.YYYY", true).isValid();
    }
});

You have to include before:

@Scripts.Render("~/Scripts/jquery-3.1.1.js")
@Scripts.Render("~/Scripts/jquery.validate.min.js")
@Scripts.Render("~/Scripts/jquery.validate.unobtrusive.min.js")
@Scripts.Render("~/Scripts/moment.js")

You can install moment.js using:

Install-Package Moment.js
lukyer
  • 7,595
  • 3
  • 37
  • 31
0

Thanks Darin, For me, to be able to post to the create method, It only worked after I modified the BindModel code to :

public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
    var displayFormat = bindingContext.ModelMetadata.DisplayFormatString;
    var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

    if (!string.IsNullOrEmpty(displayFormat) && value != null)
    {
        DateTime date;
        displayFormat = displayFormat.Replace("{0:", string.Empty).Replace("}", string.Empty);
        // use the format specified in the DisplayFormat attribute to parse the date
         if (DateTime.TryParse(value.AttemptedValue, CultureInfo.GetCultureInfo("en-GB"), DateTimeStyles.None, out date))
        {
            return date;
        }
        else
        {
            bindingContext.ModelState.AddModelError(
                bindingContext.ModelName,
                string.Format("{0} is an invalid date format", value.AttemptedValue)
            );
        }
    }

    return base.BindModel(controllerContext, bindingContext);
}

Hope this could help someone else...

Bashar Abu Shamaa
  • 1,998
  • 2
  • 21
  • 36