2

I have a view with textbox bound to a DateTime model field.

The view:

@Html.TextBoxFor(model => model.StartDate, new { @class = "datepicker", id = "startDate" })

The model:

[Required(ErrorMessageResourceType = typeof(Resources.ValidationMessages), ErrorMessageResourceName = "GeneralRequired")]
[DisplayFormat(DataFormatString = "{0:dd-MM-yyyy}", ApplyFormatInEditMode = true)]
[DataType(DataType.Date)]
[Display(Name = "StartDate", ResourceType = typeof(Resources.Something))]
public DateTime? StartDate { get; set; }

View uses jQuery datepicker to handle date input. Jquery generates only date, but I as far as I know there's no only date type in C#.

I use also custom DateTime model binder but it's rather to big to post its code (tl;dr ;)).

When user input for instance 15-01-2015 in the textbox and post it to the method the model binder converts it properly (to a 15-01-2015 12:00). The problem is when the validation is fired because there are two datetime fields on the form and if user enters only one of them then action method returns view with validation messages. Already entered field should be still filled obviously. The problem is that when controller's action method returns view it places a date time into textbox, not only a date.

Is there any method we can use to pass only date instead of date and time to a bound field in a view? Or maybe keep field bound but display only value.Date in textbox?

EDIT:

I've tried to apply Hugo Delsing's solution but faced next issue. Basically we're going to use EditorTemplate for DateTime field of the model. DateTime.cshtml looks as follows:

@model System.DateTime?
@if (Model == null)
{
    @Html.TextBox(
   string.Empty,
   "",
   new { @class = "datepicker", @type = "text" })
}
else
{
    @Html.TextBox(
   string.Empty,
   Model.ToString("dd-MM-yyyy"),
   new { @class = "datepicker", @type = "text" })
}

The problem appears as far as I understand in the line

Model.ToString("dd-MM-yyyy")

Because thrown error is

Shared\EditorTemplates\DateTime.cshtml(13): error CS1501: No overload for method 'ToString' takes 1 arguments

As far as I understand it's like that else is evaluated always, am I right? Is there any solution to achieve what I want without moving if statement to a main view and building something like this?

@if(model.StartDate.HasValue)
{
   @Html.EditorFor(model => model.StartDate, "TemplateFirst")
}
else
{
   @Html.EditorFor(model => model.StartDate, "TemplateSecond")
}
Arkadiusz Kałkus
  • 17,101
  • 19
  • 69
  • 108
  • Use http://xdsoft.net/jqplugins/datetimepicker/ instead of jQuery UI datepicker plugin – Aditya Singh Jan 20 '15 at 10:04
  • Have you specified the date format in the jquery datepicker script? - you need to show the code! Note setting `[DisplayFormat(DataFormatString = "{0:dd-MM-yyyy}", ApplyFormatInEditMode = true)]` has no impact if you are using `@Html.TextBoxFor()`. Its only relevant if you are using `@HtmlEditorFor()` to render the browsers datepicker. –  Jan 20 '15 at 12:25
  • @wizkid It's not an answer for my question. I'm not going to change it at this stage of project :). – Arkadiusz Kałkus Jan 20 '15 at 12:36
  • @StephenMuecke You're not right this attribute matters also in case of TextBoxFor helper. I'm using it and without this attribute model binder is unable to properly build DateTime property in the model object. – Arkadiusz Kałkus Jan 20 '15 at 12:38
  • It does not. `ApplyFormatInEditMode` is only applicable to `EditorFor()` The only way to set the format in `TextBoxFor()` is to use the overload which accepts a format string e.g. `@Html.TextBoxFor(m => m.StartDate, "{0:dd-MM-yyyy}", new { @class = ...})` And it has absolutely nothing to do with the `DefaultModelBinder` which uses the culture of the server. Perhaps you should study the MVC source code. –  Jan 20 '15 at 12:45
  • @StephenMuecke Maybe in case we use DefaultModelBinder it's true, but as I've written I use custom model binder. I can say that when I was using TextBoxFor missing DisplayFormat attribute caused lack of proper binding mentioned value. It know it's not a deep knowledge but changing adding and removing this attribute was changing how application has worked when it was using TextBoxFor. So at least in my case it mattered. – Arkadiusz Kałkus Jan 20 '15 at 14:22
  • @Landeeyo, I suspect you have fallen into the trap of making a mistake and in trying to fix it, have made more mistakes. I am not sure why you need a custom `ModelBinder` for the date property. If you set the culture correctly (e.g. ), then it would not be necessary (unless you have a requirement for multiple cultures for different elements of your app) –  Jan 21 '15 at 02:18

2 Answers2

1

The only way I got the working properly was to create a new EditorTemplate.

Create a view in ~/Views/Shared/EditorTemplates called DateTime.cshtml and put the following content in it.

@model System.DateTime?
@{
    var val = "";
    if (Model != null)
    {
        val = ((DateTime)Model).ToString("dd-MM-yyyy");
    }
}
@Html.TextBox(
   string.Empty, 
   val,
   new { @class="datepicker", @type = "text"})

Now if you call the default editor template it will work and always show the datepicker for dates.

<div class="editor-container">
    <div class="editor-label">
        @Html.LabelFor(model => model.StartDate)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.StartDate)
        @Html.ValidationMessageFor(model => model.StartDate)
    </div>
</div>

The @type = "text" is not realy needed as Stephen Muecke pointed out. It's the default. You could add/change it to date to have the HTML5 datepicker triggered. Keep in mind that it could show the HTML5 datepicker in 'MM-dd-yyyy' and then the value can't be validated.

Hugo Delsing
  • 13,803
  • 5
  • 45
  • 72
  • Thank you very much for the answer. It's almost perfect. One thing missing is quite interesting issue. At the beginning I pass empty StartDate to the editor template and then we have problem. Moreover if I change DateTime to nullable in the DateTime.cshtml I still have a problem because Model.ToString is throwing null exception. That's not all - because if I try to make if which checks whether model is null or not it's impossible for Razor to resolve it and it still throws null reference exception. – Arkadiusz Kałkus Jan 20 '15 at 12:42
  • 2
    You don't need an editor template and you certainly don't need `@type="text"` since that is what `@Html.TextBoxFor()` does anyway. `@Html.TextBoxFor(m => m.StartDate, "{0: dd-MM-yyyy}", new { @class="datepicker" })` –  Jan 20 '15 at 12:53
  • 1
    Also I have updated my answer to allow nullables. And I think my solution is a lot more elegant if you ever need to change the datepicker plugin. You dont need to alter it in every view that uses a date. – Hugo Delsing Jan 20 '15 at 13:02
  • 1
    jQuery datapicker does NOT change the type attribute to date (`type="date"` renders the browsers implementation of its HTML5 datepicker). You set the format for the plugin using `$('.datepicker').datepicker({ dateFormat: 'dd-mm-yy' })`. And the default for `@Html.TextBox` is `type="text"`, not `type="date"` –  Jan 20 '15 at 13:08
  • @HugoDelsing Thank you so much for your great explanation and an enormous help! – Arkadiusz Kałkus Jan 20 '15 at 13:12
  • @StephenMuecke you are right. The first time I found this solution it was set to `date`. I later changed it to `text` to solve the problem I mentioned. Didn't realize `text` was the default and I could have left it out. Thinking back when I commented I thought I explicitly had to add it, instead of just changing it. – Hugo Delsing Jan 20 '15 at 13:17
  • @HugoDelsing, Also, with regard to your edit, HTML5 datepicker displays the date in the browsers culture (not necessarly 'MM-dd-YYYY` unless that's the culture), and the format needs to be specified using ISO format i.e. `[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]` and `[DataType(DataType.Date)]` on the property and then in the view `@Html.EditorFor(m => m.StartDate)` (no custom template) - which is probably what you did if you found that `type="date"` was being rendered –  Jan 20 '15 at 13:23
  • `type="date"` was in the editor template, that why it was rendered. But on my android device it always showed as 'MM-dd-yyyy' even though it's a Dutch phone, with Dutch browser and Dutch culture. – Hugo Delsing Jan 20 '15 at 13:33
  • @StephenMuecke If you'd post your solution with "@Html.TextBoxFor(m => m.StartDate, "{0: dd-MM-yyyy}", new { @class="datepicker" })" I'd like to +1 it because it's also very useful. Of course Hugo's solution is better when we have unified dates across the project but if we have different forms (or some classes of forms or inputs) with different formats your solution is great. – Arkadiusz Kałkus Jan 20 '15 at 14:50
  • @HugoDelsing, It wont work for a custom `EditorTemplate` unless you set the format to ISO - "yyyy-MM-dd" which is why you should not create a template for `DateTime` and instead add the `[DisplayFormat]` and `[DataType]` attributes I indicated above, and then just `@Html.EditorFor()` (all this is built in to MVC). Having said that, the HTML5 datepicker is only supported in some modern browsers (e.g. its not supported in FireFox at all) so, at least for a while, using a jquery plugin is better –  Jan 21 '15 at 02:34
1

If you are using jQuery datepicker, then you need to format the date in the plugin code and in the TextBoxFor() method

@Html.TextBoxFor(m => m.StartDate, "{0:dd-MM-yyyy}", new { @class = "datepicker" })

and the script

$('.datepicker').datepicker({ dateFormat: 'dd-mm-yy' })

Side note: you do not need to set the id attribute (the helper adds id="StartDate" for you)

Note also you do not need [DisplayFormat(DataFormatString = "{0:dd-MM-yyyy}", ApplyFormatInEditMode = true)] or [DataType(DataType.Date)] on the property when using TextBoxFor(). Those attributes are only respected when using @Html.EditorFor() and result in generating <input type="date" ...> which will render the browsers HTML5 datepicker. In any case, it needs to be DataFormatString = "{0:yyyy-MM-dd}" (ISO format) to bind correctly. Note that type="date" is only supported in some modern browsers, and not at all in FireFox yet)

A custom EditorTemplate for DateTime is not necessary, but if you were to do so it would be just

@model System.DateTime?
@Html.TextBoxFor(m => m.StartDate, "{0:dd-MM-yyyy}", new { @class = "datepicker" })

There is no need to test for null as per the accepted answer. This solution is inflexible because you cant pass html attributes (without some modifications to pass then as additionViewData as per this example)

You also mentioned using a custom ModelBinder which was too big to post. It's unclear why you would need this. If you set the server culture to accept that format then its not required, for example

<system.web>
  <globalization culture="en-AU" uiCulture="en-AU"/>

but in any case, it should not be more than a few lines of code. Refer this example

Finally if you include @Html.ValidationMessageFor(m => m.StartDate) then you can use the Globalisation Nuget Package (globalize.js) or you can override the $.validator.methods.date method, but since you can set options such as minimum and maximum dates in the jQuery datepicker, use of ValidationMessageFor may not be necessary.

Community
  • 1
  • 1