17

I am trying to format a date rendered by ASP.Net MVC's TextBoxFor using the value of a strongly typed view. The date is nullable so if it is null I want to see a blank value, otherwise I want to see it in the format MM/dd/yyyy.

<%= Html.TextBoxFor(model => model.BirthDate, new { style = "width: 75px;" })%>

Thanks,
Paul Speranza

tereško
  • 58,060
  • 25
  • 98
  • 150
Paul Speranza
  • 2,302
  • 8
  • 26
  • 43

11 Answers11

25

You can keep the strong typing by using a custom editor template and Html.EditorFor() instead of Html.TextBoxFor().

Create a new EditorTemplates folder in your /Views/Shared folder and add a new MVC 2 View User Control named DateTime.ascx. Add the following

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<System.DateTime?>" %>
<%= Html.TextBox("", (Model.HasValue ? Model.Value.ToString("MM/dd/yyyy") : string.Empty)) %>

Then in your view use

<%= Html.EditorFor(model => model.BirthDate)%></p>

Don't worry about the "", the naming will still work correctly.

If you are displaying the DateTime in a different culture format to the default application culture then you will need to change the culture information or alternatively create a custom model binder in order for the model binding to work when posting back the DateTime.

Community
  • 1
  • 1
David Glenn
  • 24,412
  • 19
  • 74
  • 94
  • It makes sense for the view to be strongly typed to the model, but not for the model to have types such as "DateTime?" – CRice Jan 20 '11 at 11:20
  • @CRice By passing a DateTime in the ViewModel it allows the View to take responsibility for formatting the date of birth. A standard View may display the full date whilst a mobile View may show a shortened version. – David Glenn Jan 20 '11 at 12:33
  • Fine if you absolutely can not determine something from a controller level, but that is an edge case and most often your solution will be overkill for just rendering a date as text. – CRice Jan 20 '11 at 14:01
  • @CRice The above provides a reusable method of displaying a DateTime in a custom format, whilst remaining strongly typed, in two lines of code. I would hardly call that overkill. I have updated my answer to remove the extra information on dealing with different cultures as this may cause confusion. – David Glenn Jan 20 '11 at 17:58
  • This is the smartest way to centralise the date formatting logic and keep the views dumb - as the majority has voted. – nick Jan 20 '11 at 22:56
  • Have you got a good way to pass attributes declared in the editorFor usage? http://stackoverflow.com/questions/1625327/editorfor-and-html-properties – CRice Apr 13 '11 at 07:12
10

MVC4 http://msdn.microsoft.com/en-us/library/hh833694.aspx

@Html.TextBoxFor(model => model.YOUR_DATE, "{0:MM/dd/yyyy}")
joym8
  • 4,014
  • 3
  • 50
  • 93
  • This also helped me resolve an issue with formatting a date when Data Annotations wouldn't work for TextBoxFor – Caffeinius Apr 28 '15 at 14:47
4

First, add this extension for getting property path:

public static string GetPropertyPath<TEntity, TProperty>(Expression<Func<TEntity, TProperty>> property)
{                       
     Match match = Regex.Match(property.ToString(), @"^[^\.]+\.([^\(\)]+)$");
     return match.Groups[1].Value;
}

Than add this extensions for HtmlHalper:

public static MvcHtmlString DateBoxFor<TEntity>(
            this HtmlHelper helper,
            TEntity model,
            Expression<Func<TEntity, DateTime?>> property,
            object htmlAttributes)
        {
            DateTime? date = property.Compile().Invoke(model);

            // Here you can format value as you wish
            var value = date.HasValue ? date.Value.ToShortDateString() : string.Empty;
            var name = ExpressionParseHelper.GetPropertyPath(property);

            return helper.TextBox(name, value, htmlAttributes);
        }

Also you should add this jQuery code:

$(function() {
    $("input.datebox").datepicker();
});

datepicker is a jQuery plugin.

And now you can use it:

 <%= Html.DateBoxFor(Model, (x => x.Entity.SomeDate), new { @class = "datebox" }) %>
Serhiy
  • 4,357
  • 5
  • 37
  • 53
2

It's a dirty hack, but it seems to work.

<%= Html.TextBoxFor(model => model.SomeDate,
    new Dictionary<string, object> { { "Value", Model.SomeDate.ToShortDateString() } })%>

You get the model binding, and are able to override the HTML "value" property of the text field with a formatted string.

Cephas
  • 794
  • 8
  • 14
  • 2
    If this worked in MVC 1, it has stopped working in MVC 2. The default output seems to take priority for the value attribute. Not that I expected otherwise, but it doesn't work regardless of trying it as a `Dictionary` or an anonymous object (e.g., `new { value = Model.SomeDate.ToShortDateString() }`). – patridge Jun 15 '10 at 15:56
2

You can consider the following sample of TextBoxFor Extension method for datetime data:

    public static MvcHtmlString CalenderTextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
        {
            Func<TModel, TProperty> deleg = expression.Compile();
            var result = deleg(htmlHelper.ViewData.Model);

            string value = null;

            if (result.ToString() == DateTime.MinValue.ToString())
                value = string.Empty;
            else
                value = string.Format("{0:M-dd-yyyy}", result);

            return htmlHelper.TextBoxFor(expression, new { @class = "datepicker text", Value = value });
       }
Jilani pasha
  • 389
  • 3
  • 14
Ashraf Alam
  • 3,500
  • 32
  • 31
1

Just name it what it is looking for. Like:

Html.TextBox("Person.StartTime",Person.StartTime.ToShortDateString());

When it returns to the controller, your model will have the value bounded.

eeerahul
  • 1,629
  • 4
  • 27
  • 38
0

Have you tried to force the culture of your current thread application? You can override it in the web.config using this line (in the tag) :

<!-- Default resource files are set here. The culture will also change date format, decimal, etc... -->
<globalization enableClientBasedCulture="false" culture="en-US" uiCulture="en-US"/>
0

A simple solution is to not use the strongly typed helper.

<%= Html.TextBox("StartDate", string.Format("{0:d}", Model.StartDate)) %>
andersjanmyr
  • 11,302
  • 5
  • 29
  • 29
  • 2
    For complex models (e.g., Model.SubModel.SubSubModel), you will have to do the logic for generating your id/name with the appropriate prefix. Otherwise, you break binding and validation scope within the model state. – patridge Jun 15 '10 at 15:24
0

I use some custom helpers and have used them successfully in MVC 2 and 3 (code also on Snipplr). The helpers have some css logic thrown in as I use the jQuery-ui datepicker, but that can easily be removed.

public static MvcHtmlString DateTextBoxFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, string formatString, object htmlAttributes)
    {
        var metadata = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
        string format = String.IsNullOrEmpty(formatString) ? "M/d/yyyy" : formatString;
        DateTime date = metadata.Model == null ? new DateTime() : DateTime.Parse(metadata.Model.ToString());
        string value = date == new DateTime() ? String.Empty : date.ToString(format);
        RouteValueDictionary attributes = new RouteValueDictionary(htmlAttributes);
        string datePickerClass = "date-selector";
        if (attributes.ContainsKey("class"))
        {
            string cssClass = attributes["class"].ToString();
            attributes["class"] = cssClass.Insert(cssClass.Length, " " + datePickerClass);
        }
        else
        {
            attributes["class"] = datePickerClass;
        }
        return helper.TextBox(ExpressionHelper.GetExpressionText(expression), value, attributes);
    }

    public static MvcHtmlString DateTextBoxFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression)
    {
        return DateTextBoxFor<TModel, TValue>(helper, expression, String.Empty, null);
    }

    public static MvcHtmlString DateTextBoxFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, string formatString)
    {
        return DateTextBoxFor<TModel, TValue>(helper, expression, formatString, null);
    }
nkirkes
  • 2,635
  • 2
  • 21
  • 36
-1

Seriously, why should the view have to do this?

Map your core model which has the date time object to your mvc view model.

//core model
public class Person
{
    public DateTime? BirthDate { get; set;}
}

//view model
public class PersonForm
{
    public string BirthDate { get; set; }
}

So mapping might look like:

public interface IDomainToViewMapper<TModel, TViewModel>
{
    /// <summary>
    /// Use an automapper or custom implementation to map domain model to presentation model.
    /// </summary>
    /// <param name="source">domain model</param>
    /// <returns>presentation model</returns>
    TViewModel MapDomainToView(TModel source);        
}

public interface IPersonMapper : IDomainToViewMapper<Person, PersonForm>
{
}

public class PersonMapper : IPersonMapper
{
    #region IDomainToViewMapper<Person,PersonForm> Members

    public PersonForm MapDomainToView(Person source)
    {
        PersonForm p = new PersonForm();

        if (source.BirthDate.HasValue)
        {
            p.BirthDate = source.BirthDate.Value.ToShortDateString();
        }

        return p;
    }

    #endregion
}

And your controller action might look like:

    public ActionResult Index()
    {
        Person person = //get person;
        var personForm = _personMapper.MapDomainToView(person);

        return View(personForm)
    }

You won't have to change your view example at all then.


From Chapter 2, MVC 2 in Action (Manning)

public class CustomerSummary
{
public string Name { get; set; }
public bool Active { get; set; }
public string ServiceLevel { get; set; }
public string OrderCount { get; set;}
public string MostRecentOrderDate { get; set; }
}

This model is intentionally simple; it consists mostly of strings. That’s what we’re representing, after all: text on a page. The logic that displays the data in this object will be straightforward; the view will only output it. The presentation model is designed to minimize decision making in the view.

CRice
  • 12,279
  • 7
  • 57
  • 84
  • By allowing the View to take responsibility for formatting a DateTime you can have normal View display 'January 20, 2010' and a different mobile View to display '1/20/10' without having to maintain duplicate controller code. The view may also want to display the age with the date of birth. – David Glenn Jan 20 '11 at 12:25
  • The question mentions nothing of needing different views for the same model. It is also the controllers responsibility to remove decision making from the view, and the view just to render the model. – CRice Jan 20 '11 at 13:47
  • The controller is a lightwieight coordinator. I really don't think it is the responsibility of a controller to handle formatting. Definitely helper logic. – nick Jan 20 '11 at 22:53
  • I agree the controller is lightweight. The controller would call mapping in my case (see my answer) which turns the object into how it is to be presented. – CRice Jan 20 '11 at 22:59
  • IMHO, the view should be responsible for the formatting. I agree with Nick about having a helper, or several of them, that the views can pick and choose from. I have always been of the thought that the controller does only 2 things - get data from a data source and pass that along to the appropriate view. – Paul Speranza Jan 21 '11 at 18:28
  • How would you explain my quote from MVC 2 in action then? They are using strings in the view – CRice Jan 24 '11 at 02:01
  • I would look at it as I do everything else. Everyone has somewhat different ways of doing things that make sense to, or work better for, them. MVC 2 In Action is just a book with the authors ideas. That is one way of doing things. I like to take the best ideas and the ones that I see getting the most traction and apply them to my situation. In this case I prefer the view having the responsibility for formatting - that is what makes the most sense to me. I do see your point, and I appreciate your input. Food for thought. – Paul Speranza Jan 24 '11 at 16:15
-3

Have you tried just passing in the date format that you'd like?

Html.TextBoxFor(model => model.BirthDate.ToString("MM/dd/yyyy"), 
                new { style = "width: 75px;" })
womp
  • 115,835
  • 26
  • 236
  • 269
  • 6
    If I use that <%= Html.TextBoxFor(model => model.BirthDate.Value.ToString("MM/dd/yyyy")) %> I get the exception "Templates can be used only with field and property accessor expressions." – Paul Speranza Jan 18 '10 at 20:29
  • 1
    The point of the lambda is to use expressions to generate an id for the textbox from the model. This is a bad idea. – CRice Jan 20 '11 at 09:34
  • And he does this for every text box that displays a date? not very DRY. – nick Jan 20 '11 at 22:55