华赐软件 Bootstrap3

MVC3日期类型处理(待译)

Create user friendly date fields with ASP.NET MVC EditorTemplates & jQueryUI

Tags: ASP.NETASP.NET MVCjQueryjQueryUI

 

 

Today's web sites need to be user friendly, interactive, and responsive. The date field is of particular interest because at this point in time, users pretty much expect too see nicely formatted dates on the site in general, and datepicker/calendar widgets to interact with when editing date fields. The best way to do this, of course, is with client side script; and when working in ASP.NET MVC, those scripts can work seamlessly with templates.

What are MVC EditorTemplates & DisplayTemplates?

Editor & Display templates behave a lot like partial views, since they're just .cshtml/.ascx files (so they technically are partial views), and they produce output inside of regular views, just like partial views. However, there are some key differences that distinguish templates from views:

  • Templates render at an application wide scope, automatically. When you use partial views, you must call them from another view using helper syntax, e.g., @Html.Partial("_PartialViewName").
  • Templates render themselves only for the specific data types that they're setup to handle, e.g., DateTime, decimal or int,throughout the entire application. If you setup an EditorTemplate to render output for a particular data type, it will do so for every single field of that type in the application while partial views aren't tied to any particular data type.
  • A DisplayTemplate renders itself only when its specific data type needs to display in an Index, Details, Delete, or custom view. An EditorTemplate renders itself only when its specific data type is in edit mode and while in Edit mode.

A benefit of using Editor and DisplayTemplates is that you don't have to add repetitive code into views individually. More importantly you won't find yourself adding code to work with each field individually, as that creates code that's not DRY and harder to maintain. Just add your Html Helpers, widgets, & script to the templates directly and the Razor/ASPX view engine will take over to produce output based on those data types in templates.

Working with templates

Looking deeper at templates is a great way to see the conventions of ASP.NET MVC in action. MVC can do its magic because ofconvention over configuration, meaning that developers using ASP.NET MVC should setup, design, and code to a particular way of doing things, or adhering to a set of specific guidelines. In other words - using conventions. Some MVC conventions are fairly obvious, like the URL mappings to controllers or the folder structure of the project, and you'll notice others as you dig deeper into MVC.

Templates follow conventions too, starting with their location in the folder structure. In the \Views\Shared folder you'll find layout pages, partial views, and templates living together, although the templates split off into their own folders, \DisplayTemplates and \EditorTemplates. To create a template, add a file to one of the template folders and ensure the name matches the data type you want to create a template for, such as the DateTime data type, shown below:

image

So long as the name of the template file (DateTime.cshtml) matches the data type, you're in business, since the filename-datatype match is the convention you need for MVC templates to work. Otherwise, to configure rather than use convention would require a a lot of dealing with the web.config file, mapping out data types to templates.

Since templates contain no default code, you need to add your code to them manually, including the @model definition. Inside each template should be different code depending on its purpose; display or edit. To render a basic template of a <span> for displaying, and an <input> tag during editing, add the following code to each template file:

\DisplayTemplates\DateTime.cshtml

@model DateTime   
@String.Format("{0:d}", Model.Date)

\EditorTemplates\DateTime.cshtml

@model DateTime
@Html.TextBox("", String.Format("{0:d}", Model.Date.ToShortDateString()))

Inspecting the code shows that templates can use a data type as the model, as the @model statement in the above code denotes. This is quite different than a regular or partial view that uses a model built specifically for application consumption, such as a Product or Customer class. The DisplayTemplate need only show the date nicely formatted, however, the EditorTemplate needs to render a <input> tag in the browser, so a user can edit the field, and it should contain the formatted date from the model.

The above Display and EditorTemplates look something like this when rendered in the browser both in display and edit mode:

In display mode:

image

In edit mode:

image

Now every date in the application displays dates in one of two ways - display mode with a <span> or edit with an <input> [1]. This is a great way to keep code DRY, since there was no need to modify each date field yourself; something you see frequently in Web Forms applications.

Although the dates are now cleaned up and formatted for the user, the EditorTemplate still needs to be more interactive, which is accomplished by using jQuery client side script.

Working with jQueryUI widgets in MVC templates

The setup...

The jQueryUI library is a JavaScript framework that contains several widgets you can use in any web application, including Web Forms, MVC, ASP.NET Web Pages, or even pages built with non Microsoft web tools. First, you'll deed to download the jQueryUI and the widgets you'd like. The easiest way to do so is by visiting the jQueryUI site and choosing exactly what component(s) you want - in this case, the DatePicker widget. Once downloaded, you'll have a .zip file containing the files necessary for adding the DatePicker into the templates, along with some samples. You can download other widgets in the jQueryUI in the same fashion.

Inside the jquery-ui-1.8.11.custom.zip download, you'll find everything you need to add the jQuery DatePicker to the DateTime EditorTemplate, and some samples and styling. The important files:

  • All scripts from the \js folder
  • All content from the \css\<Theme Name> folder

Add the scripts to the \Scripts and the css to the \Content folder in your Visual Studio solution. You can then include the <script> tag in your layout view.

<script src="@Url.Content("~/Scripts/jquery-ui-1.8.11.custom.min.js")" type="text/javascript"></script>
The last step is to wire the EditorTemplate and jQuery together, with an attribute and script.

The EditorTemplate meets jQuery...

There's no need to change the DisplayTemplate, as that does its job just fine. However, we do want the jQuery calendar widget to display while in edit mode, which means it's necessary that the script can identify every date field, so the EditorTemplate must be modified like so:

@model DateTime
@Html.TextBox("", String.Format("{0:d}", Model.Date.ToShortDateString()), new { @class = "datefield" })

The modification adds the HTML class attribute to the <input> tag that the Html.TextBox helper renders. By adding the class element to the DateTime EditorTemplate, the DatePicker widget can look for elements with the class attribute equal to "datefield". Once the DatePicker has its wrapped set of elements to work with, it can then show the date picker for each element in the set.

jQuery script needs to call the datepicker function on the wrapped sets created found in the EditorTemplates. The final step doing so is to add the call to the jQueryUI DatePicker inside the jQuery ready function. This script can be added to its own .js file or a .js file already in the project.

$(function () {
$(".datefield").datepicker();
});

This code selects all elements with a class="datefield", which happen to be the <input> tags created in the EditorTemplate, then calls the datepicker() function on each element in the set. The jQueryUI's datepicker method will display a pop-up calendar for each element classed with "datefield" in edit mode, as shown here:

image

To see the rendered HTML, view the HTML source in the browser. The contents will look similar to the following HTML source, which is very unobtrusive and clean:

 

<input class="datefield" data-val="true" data-val-required="The Expiration Date field is required."  
id="ExpirationDate" name="ExpirationDate" type="text" value="3/27/2011" />

Summary

Display & EditorTemplates are a great way to apply display & formatting rules for any field, based on the data type rather than dealing with individual fields. Templates allow you to create clean code that's easily changed when requirements or schema changes.

The jQueryUI is has a great collection of client side goodies that you can use to spice up web user interfaces with little code, such as AutoComplete, Accordion elements, Dialogs, Sliders, etc... The components in the jQueryUI are most definitely worth exploring more, as they add a lot of richness to the UI without much ceremony.

Get the Code!

[1] You can separate out a single field to display a different template than the others for that data type. To do so, apply a UIHintattribute to a property in the model.

36 Comments

  • Rachel Appel said on Apr 18 2011 at 12:22 PM

    Edit to add:

    I'm assuming the standard Html.EditorFor found in the default MVC view templates (with a strongly typed view). You can find this code in the \Views\Products\Edit.cshtml. Also shown in brief below:

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

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

  • David Clarke said on Apr 18 2011 at 9:11 PM

    Hi Rachel - perfect timing on this post thank you but please tell me how to render the correct id attribute for the field. In the example you provide there doesn't appear to be any connection between the template which has DateTime as the Model, and the View Model that is supposed to bind the form?

    In my case I want to put an id attribute on the span tag used to render the display template so I can use jQuery to retrieve the value. Unfortunately a class attribute isn't specific enough. Show me the meta data?

    Thanks
    Dave

  • Leniel Macaferi said on Apr 19 2011 at 12:23 AM

    Rachel,

    I just want to say thank you for this awesome post.

    It'll help me a lot today.

    Keep these great posts coming.

    Regards,

    Leniel

  • ahmed said on Apr 19 2011 at 1:30 AM

    I like it, thks Rachel :)

  • TexasJetter said on Apr 19 2011 at 7:22 AM

    Great explanation of Display and Editor templates. My only comment would be to cover the case where the value may be null. I came across this variation of your date template which handles a nullable type:
    @model DateTime?
    @(Model.HasValue ? string.Format("{0:d}", Model) : string.Empty)

    Not a big change, but it will save a lot of headacks.

  • Leonardo said on Apr 19 2011 at 8:06 AM

    Very clear writing!
    Can you please show how to customize certain fields, among others of the same type?

    Say, one of my DateTime fields (only one of them) is editable only by those peoplo who have a specific permission (or if current month is march, or if it is a business day, or if I'm from Brazil, or...)

  • Rachel said on Apr 19 2011 at 10:24 AM

    @TexasJetter, 

    Yes, absolutely! That makes the code much more accurate. Thanks for adding that.

  • Rachel said on Apr 19 2011 at 10:28 AM

    @Leonardo, 

    You can apply the UIHint("<your-template-name-without-the-cshtml>") attribute to individual properties in the model and that will map just those properties to that particular .cshtml file. 

    See here http://msdn.microsoft.com/en-us/library/ee308450.aspx for all the details. 

  • Rachel said on Apr 19 2011 at 10:34 AM

    @DavidClark,

    I added a quick snippet as the first comment to this post since I realized I left out the view code.

    If you use the built-in Html Helpers (@Html.EditorFor, @Html.TextBox, etc.. they'll happily render those classes (data-val-*) that the jQuery libs and MVC framework pick up on. See here http://rachelappel.com/asp-net-mvc/how-data-annotations-for-asp-net-mvc-validation-work/

    You can additionally specify your own html attributes by using syntax similar to this:

    @Html.TextBox("", String.Format("{0:d}", Model.Date.ToShortDateString()), new { @class = "datefield" })

    The last argument allows you to add a new attribute to the output with this syntax,like in the snip above

    "new { @class = "datefield" } "

    Hopefully that helps!

  • Stuart Leeks said on Apr 19 2011 at 2:52 PM

    On a related note I've got a post showing how you could integrate date range validation and tie it into the datepicker :-). http://blogs.msdn.com/b/stuartleeks/archive/2011/01/25/asp-net-mvc-3-integrating-with-the-jquery-ui-date-picker-and-adding-a-jquery-validate-date-range-validator.aspx

  • David Clarke said on Apr 19 2011 at 4:15 PM

    Hi Rachel, thanks for your response but perhaps I didn't express my question clearly. I understand I can add attributes using the helper methods. But as an example, I have a numeric field called LastReading. To allow me to perform a calculation on that field using jQuery I need it to have an id attribute, e.g. id="LastReading". The current DisplayFor method just emits the value of the field and I want to create a template for the field that will write e.g. <span id="LastReading">1234</span>. So I need to be able to access the metadata for the field. Is this something that is available from a template?

  • David Clarke said on Apr 19 2011 at 4:54 PM

    Ok, got it:

    @model System.Int32
    <span id="@ViewData.ModelMetadata.PropertyName">@Model</span>

  • Rachel said on Apr 19 2011 at 5:59 PM

    Ha! David Clarke, you beat me to it. That was quick :)

  • David Clarke said on Apr 20 2011 at 4:46 PM

    I suspect I had a greater incentive to find a resolution ;-)

  • Derek Morrison said on Apr 21 2011 at 3:16 AM

    Helpful post - thanks!

    I did have a problem running the solution initially, though. I had never worked with SQL CE, so I got this error when I went to the index page: "Unable to find the requested .Net Framework Data Provider. It may not be installed."

    I fixed it by running the following commands in the NuGet console:
    install-package SqlServerCompact
    install-package EFCodeFirst.SqlServerCompact

    I guess it worked for you because you have SQL CE installed globally on your system?

  • Time said on Apr 22 2011 at 10:06 AM

    The system I am working on has to read dates with time, and the jquery ui datepicker falls short, so I found this great addon that works based on the datepicker but expands it to include time too:

    http://trentrichardson.com/examples/timepicker/

    Has worked great for me.

  • Rachel said on Apr 22 2011 at 6:18 PM

    @Derek,

    Yes, you do need SQL CE, however, you can change the connection string to point to a sqlexpress db.

    You can additionally find the EF code first package in the NuGet "Add library package reference" dialog, but the cmd line is just fine too.

  • Rachel said on Apr 22 2011 at 6:19 PM

    @Time, thanks for pointing out that nice extra jQuery sample.

  • Jan said on Apr 24 2011 at 9:10 AM

    Have you tested your code by trying to do a create?
    Gives me this message:
    The model item passed into the dictionary is null, but this dictionary requires a non-null model item of type 'System.DateTime'.

  • Rachel said on Apr 24 2011 at 6:58 PM

    @Jan,

    You can changed the DateTime editortemplate to check for nulls this way...

    @model DateTime?
    @(Model.HasValue ? string.Format("{0:d}", Model) : string.Empty)

  • Jan said on Apr 25 2011 at 3:37 AM

    This will work..

    @inherits System.Web.Mvc.WebViewPage<System.DateTime?> 
    @Html.TextBox("", (Model.HasValue ? Model.Value.ToString("MM/dd/yyyy") : DateTime.Today.ToShortDateString()), new { @class = "datefield" })

  • shawn z said on Aug 2 2011 at 1:50 PM

    When setting up an MVC 3 application, the foreign keys that should allow drop down lists to select an item do not get rendered as drop downs, but as static inputs. This can be resolved by creating a custom display and view for that field.
    We will need to start by creating a custom partial view that will live in “~/Views/Shared/DisplayTemplates/UserGuid.cshtml”, and “~/Views/Shared/EditTemplates/UserGuid.cshtml”. The code for one is located below:
    @model Guid

    @{ 
    incMvcSite.Models.MvcSiteDB db = new incMvcSite.Models.MvcSiteDB();
    incMvcSite.Models.SecUser usr = db.SecUsers.Single(u => u.Guid == Model);
    }
    @usr.Display

    This is a display for template that will look up the item in the referenced table and display it. We also need an edit for template as follows:
    @model Guid
    @{ 
    incMvcSite.Models.MvcSiteDB db = new incMvcSite.Models.MvcSiteDB();
    SelectList items = new SelectList(db.SecUsers.OrderBy(i => i.Display).ToList(), "Guid", "Display", Model);
    }
    @Html.DropDownList("", items)

    The edit for template is implemented as a drop down list. Originally, we has used static HTML code, but the problem will appear of implementing a “prefix”. Static HTML code does get handled by HTML helpers, so it’s recommended that you use the HTML.DropDownList().
    To force the MVC framework to use the new Display and Edit for templates, we need to annote our model item an add the following line:
    [UIHint("UserGuid")]

    This will cause MVC to use the Display and Edit templates named “UserGuid”, which are just partial views.

  • Rachel said on Aug 2 2011 at 2:46 PM

    Shawn Z,

    "When setting up an MVC 3 application, the foreign keys that should allow drop down lists to select an item do not get rendered as drop downs, but as static inputs. This can be resolved by creating a custom display and view for that field." 

    -that is the exact point of this post (but with dates, I'm not dealing with dropdowns here). I will have a post coming on an easier way to do dropdowns yet, with Helpers as well as partial views.

    -Also: I strongly recommend *against* putting this code in the view as you've done here. I *strongly* recommend adding it to the controller, and returning the actionresult to the view.

    This way you get a better separation of concerns.

  • Marc said on Aug 26 2011 at 4:21 PM

    I use this exact same template however, I have an issue with additional html properties passed in from the EditorFor getting dropped on the ground and replaced with just the {@class = "date"} property. How can I add the class="date" to the existing passed in properties?

  • Rachel said on Aug 29 2011 at 9:47 AM

    Marc,

    Try this syntax:

    @Html.EditorFor(model => model.WhateverField, new { class = "myclass", otherAttribute = "value" })

    Let me know if that works.

  • moncler said on Oct 29 2011 at 4:29 AM

    Editor & Display templates behave a lot like partial views, since they're just .cshtml/.ascx files (so they technically are partial views), and they produce output inside of regular views, just like partial views. However, there are some key differences that distinguish templates from views:

  • Bogdan said on Nov 7 2011 at 5:26 PM

    Hi,
    Thanks for the great and clean tutorial
    However I have a problem with localization.
    My locale format for date is dd.MM.yyyy. But when I pick from datepicker, it returns the date as MM/dd/yyyy which in turn is invalid for my locale so sate field ends with null value
    How should I customize the format of date returned?
    Thanks

  • Rachel said on Nov 10 2011 at 9:10 AM

    Bogdan,

    The post has more details & help than I could squeeze into a comment here.

    http://helios.ca/2010/02/17/asp-net-mvc-2-model-validation-with-localization/

    Hopefully that helps.
    Rach

  • andy said on Feb 10 2012 at 6:32 PM

    thanks for the code. very simple and clean

    wanted to throw in the nullable for editing as it took me a bit to work out.

    @model DateTime?
    @(Model.HasValue ? @Html.TextBox("", String.Format("{0:d}",Model.Value.ToShortDateString())) : @Html.TextBox("", string.Empty))

  • Rachel said on Feb 12 2012 at 10:14 PM

    Andy,

    Thanks for the pro tip! Very useful.

  • Naren Gokal said on Mar 16 2012 at 9:38 AM

    Hi Rachel, 

    Is there any way of ensuring that the datepicker is used instead of a user entering a date themselves. 

    I want to prevent user error.

    Ps. Thanks for your above post as to how to use the datepicker

  • Rachel said on Mar 19 2012 at 9:10 AM

    Naren,

    You could write some JS to do what you want but if you allow the client & server side validation happen, that's what will ensure they entered a correct date and prevent user error.

    See here http://rachelappel.com/asp-net-mvc/how-data-annotations-for-asp-net-mvc-validation-work/
    For more on validation

  • Misi Moisei said on Apr 17 2012 at 8:31 AM

    Hello Rachel,
    I have the same question like Bogdan. How to change the date format from MM/dd/yyyy to dd.MM.yyyy ?
    What is the connection between formating a date and the link you suggested (ASP.NET MVC 2 Model Validation With Localization) ? Formating your date by your culture ?

    BTW : Great example.

  • Rachel said on Apr 18 2012 at 4:51 PM

    Thanks, Misi,

    Yes, Bogdan asked about formatting for his locale, so that post (http://helios.ca/2010/02/17/asp-net-mvc-2-model-validation-with-localization/) does a good job with that. Regular formatting, without dealing with localization will look something like this:

    @Html.TextBox("", (Model.HasValue ? Model.Value.ToShortDateString() : string.Empty), new { @class = "datePicker" }) 

    This also does null checking.
    Rach

  • Misi said on Apr 21 2012 at 7:50 AM

    Compiler Error Message: CS1061: 'System.DateTime' does not contain a definition for 'HasValue' and no extension method 'HasValue' accepting a first argument of type 'System.DateTime' could be found.
    Where do you specify the date format(from MM/dd/yyyy to dd.MM.yyyy) ?

  • Rachel said on Apr 21 2012 at 10:24 AM

    The specific location is at the: 
    Model.Value.ToShortDateString()

    From the Value property you can call other methods such as ToString("yourformat"), where yourformat is the date pattern you want to use (dd.MM.yyyy).

 
 
posted @ 2012-08-09 22:10  OpenCsharp.Net  阅读(670)  评论(0编辑  收藏  举报
华赐软件 Bootstrap3
w.huacisoft.com">华赐软件 Bootstrap3