Sunday, 18 October 2015

Custom Templates, Data Annotations and UI Hints in ASP.NET MVC


Let’s look at these concepts in a little more details using a simple demo app.

- Say we start off with the Internet Application ASP.NET MVC4 Project.

- Add an Entity in the Model folder with the following properties.
public class TimeCard
    {
        public int Id { get; set; }
        public string Subject { get; set; }
        public string Description { get; set; }
        public DateTime? StartDate { get; set; }
        public Decimal NumberOfHours { get; set; }
    }
- Use the Add Controller method to scaffold up the Controller and Views for it.

If we run the Application now and Navigate to the Create page for the DefaultController, it looks like this.
 Now we will go back to the TimeCard entity and decorate it with attributes as follows
public class TimeCard
    {
        public int Id { get; set; }
        public string Subject { get; set; }

        [DataType(DataType.MultilineText)]
        public string Description { get; set; }

        [DisplayName("Start Date")]
        public DateTime? StartDate { get; set; }

        [DataType(DataType.Duration)]
        [DisplayName("Number of Hours")]
        public Decimal NumberOfHours { get; set; }
    }
When we run the application again, the view changes to the following.
 As we can see, the Description box is now much bigger thanks to the DataType annotation, and the labels look better. However we still don’t have any DatePicker for our Dates, and what if we wanted the Number of hours to be a select going from 1 to 24?
We could always go ahead and edit the view and cram in the required jQuery to put in a Date Picker. Similarly we could remove the default text box that our MVC scaffolding is generating and replace it with a custom “Select”. Or, there is another way around it – Custom Templates.

Custom Templates in ASP.NET MVC
Universal Template
Let’s see how we can use Custom Templates to replace the Start Date with a Date Picker. By universal template, I mean the changes that we are going to do is going to apply to the entire project.
- In the Shared folder, we’ll add a folder called EditorTemplates.
- Next we add a CSHTML file called DateTime.cshtml and add the following content to it.
@model DateTime?

@Html.TextBox("", (Model.HasValue ? Model.Value.ToShortDateString() : string.Empty), new { @class = "datepicker" })
- As we can see, it simply drops in a TextBox with a CSS class called datepicker. This magically doesn’t convert it into a date picker. To tie it up with a date picker JavaScript control, you need to do a couple of additional steps. First thing, add a new JavaScript file in the Scripts folder and call it jquery.ext.datepicker.js

- In the JS file we drop the following JavaScript
$(function ()
{
    $(".datepicker").datepicker();
});
- Now we’ve to include it in one of our JavaScript bundles. Let’s update the BundleConfig.cs and add it to the jquery bundles as follows:
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.unobtrusive*",
"~/Scripts/jquery.validate*",
"~/Scripts/jquery.ext*"));
- We are all set. Note we have not touched any of the cshtml files. Let’s run the application. The create page will be as follows

- If you save the entry and navigate back to it for Editing, you’ll see that the Edit page also has a date picker. 

 - Fact is, here on when we use the Html.EditorFor(…) any DateTime type of field, it will get the date picker for free.

Reducing Scope of the Editor
Now if we wanted to reduce the scope of the Template, we could very well define it under Views\Default\EditorTemplates. This would have restricted it to the view in the Default controller.

Inverting the Template Association
Now that we’ve seen how to do a Universal Template and a Template per controller, let’s see what it takes to create a template that will be used only if we want to use it.
- Let’s add another cshtml file under the EditorTemplates folder and call it HoursOfTheDay.cshtml
- Add the following markup to it.
@model Decimal?
<select>
    <option value="0">Please Select</option>
    @for (int i = 1; i <= 24; i++)
    {
        if (Model.HasValue && ((int)Model.Value) == i)
        {
            <option value="@i" selected="selected">@i</option>
        }
        else
        {
            <option value="@i">@i</option>
        }
    }
</select>
- What this does is for Decimal values it provides a drop down with items 1 to 24. It also checks if the input value is between 1 – 24 and if it is, sets it as the selected option
- Now since it has a name HoursOfTheDay it will not get bound to all Decimal fields automatically. Instead there are two options to bind it.

Option 1: Using the UIHint attribute on our Model as follows:
[DataType(DataType.Duration)]
[DisplayName("Number of Hours")]
[UIHint("HoursOfTheDay")]
public Decimal NumberOfHours { get; set; }
As we can see, the UIHint provides the name of the Template to use. Again this makes it applicable to all pages to which this Entity Type is bound.


Option 2: If we want even finer grained control over when the template should be used, we can remove the UIHint from the attribute and use it in the cshtml markup as follows
<div class="editor-field">
    @Html.EditorFor(model => model.NumberOfHours, "HoursOfTheDay")
    @Html.ValidationMessageFor(model => model.NumberOfHours)
</div>



No comments:

Post a Comment