January 2018 : Instructor-led Online Course in ASP.NET Core 2.0. Conducted by Bipin Joshi. Read more...
Registration for January 2018 batch of ASP.NET Core 2.0 instructor-led online course has already started. Conducted by Bipin Joshi. Register today ! Click here for more details.

Create Strongly Typed Custom HTML Helper for Picking Dates

Recently a beginner asked me as to how a strongly typed HTML helper can be created. Although the process is relatively simple you need to keep in mind a certain steps. In this short article I will explain how a custom HTML helper can be created to render an HTML5 date picker input field. I will explain unbound as well as model bound versions for better clarity.

ASP.NET MVC offers TextBox() and TextBoxFor() helper to render a textbox that is unbound and model bound respectively. You can use the same helpers to render an HTML5 date picker by supplying an anonymous object as shown below:

@Html.TextBox("birthdate", null, new { type = "date" })
@Html.TextBoxFor(m => m.BirthDate, new { type="date" })

As you can see from the above code, you can override the default input type from text to date by supplying the anonymous object. This works well in many cases but at times you may prefer creating a custom HTML helper that saves that extra step of overriding input type. In nutshell you may want to write something like:

@Html.DatePicker("birthdate")
@Html.DatePickerFor(m=>m.BirthDate)

You can achieve this by creating a custom HTML helper. To illustrate how this can be accomplished, let's develop unbound as well as model bound helper that is intended to display date fields.

Model

Begin by creating a new ASP.NET MVC application in Visual Studio. Add an Entity Framework data model for the Employees table of Northiwnd database. The Employee entity class will look like this:

We will use the BirthDate property of the Employee class and display it in the date picker custom helper.

Now, add a folder named CustomHelpers and add a class to it - DatePickerHelper.cs

Unbound DatePicker helper

First we will write unbound version of our DatePicker helper.

public static class DatePickerHelper
{
    public static MvcHtmlString DatePicker(this HtmlHelper helper, 
                                           string name)
    {
        TagBuilder tag = new TagBuilder("input");
        tag.Attributes.Add("name", name);
        tag.Attributes.Add("id", name);
        tag.Attributes.Add("value", "");
        tag.Attributes.Add("type", "date");

        MvcHtmlString html = new MvcHtmlString(
                        tag.ToString(TagRenderMode.SelfClosing));
        return html;
    }

    public static MvcHtmlString DatePicker(this HtmlHelper helper, 
                                    string name, DateTime value)
    {
        TagBuilder tag = new TagBuilder("input");
        tag.Attributes.Add("name", name);
        tag.Attributes.Add("id", name);
        tag.Attributes.Add("value", value.ToString("yyyy-MM-dd"));
        tag.Attributes.Add("type", "date");

        MvcHtmlString html = new MvcHtmlString(
                         tag.ToString(TagRenderMode.SelfClosing));
        return html;
    }
}

The DatePicker class is marked to be static and all its methods are static. You might have already guessed they are extension methods of HtmlHelper class. In order to create a custom HTML helper you need to write it as an extension method of inbuilt HtmlHelper class. The HtmlHelper class resides in System.Web.Mvc namespace. All the extension methods of DatePicker class will return MvcHtmlString - a class that wraps all the HTML markup to be rendered in the browser as a part of the extension method.

The above code shows two overloads of DatePicker() method. The first overload accepts helper and name parameters whereas the second overload accepts helper, name and value parameters. The helper parameter indicates that this method is extending HtmlHelper class. The name parameter indicates the name of the date picker control in the final HTML markup. The value parameter indicates the default value of the date picker.

Inside, the methods use TagBuilder to construct the <input> element needed to display the date picker. Notice that the name and as well as id attributes are set to the name you specify. The type attribute is set to date. In the second overload even the value attribute is set to the value parameter of the extension method. HTML5 date picker requires date value in ISO date format (yyyy-MM-dddd) and that's why the supplied date is formatted as shown.

An MvcHtmlString is constructed using the ToString() method of the TagBuilder class. Since the input tag is self closing (<input ... />) TagRenderMode is specified as SelfClosing. The MvcHtmlString instance is then returned from the methods.

Strongly typed or model bound DatePicker helper

So far so good. Now let's develop the model bound version. This will require some more steps and the code is slightly complex than before.

Add another method to the DatePicker class with the following signature:

public static MvcHtmlString DatePickerFor<TModel, TProperty>(
              this HtmlHelper<TModel> helper, 
              Expression<Func<TModel, TProperty>> expression)
{
...
}

The DatePickerFor() method is a generic method that allows you to specify type of model (TModel) and its property to be bound (TProperty). It takes two parameters helper and expression. The helper parameter is similar to the earlier versions but also specifies TModel. The Expression parameter indicates the property of model to be bound. I won't go into the details of LINQ and Lambda expressions here. It is suffice to say that the expression parameter represents the model property to be bound with the date picker field.

Inside the DatePickerFor() method write the following code:

string datePickerName = ExpressionHelper.
                        GetExpressionText(expression);
string datePickerFullName = helper.ViewContext.ViewData.
                    TemplateInfo.GetFullHtmlFieldName
                    (datePickerName);
string datePickerID = TagBuilder.CreateSanitizedId
                      (datePickerFullName);

The first line determines the name of the date picker. This is done using GetExpressionText() method of ExpressionHelper class. You pass the lambda expression to this method as a parameter and it will return its string representation. For example, if you pass m=>m.BirthDate then datePickerName would be BirthDate.

The next line determines the fully qualified name of the date picker. This is done to cover scenarios where the date picker is nested or is placed in partial pages. Finally, client side ID of the date picker is determined using CreateSanitizedId() method of TagBuilder class.

Now, continue the DatePickerFor() by adding the following lines:

ModelMetadata metadata = ModelMetadata.FromLambdaExpression
                         (expression, helper.ViewData);
DateTime datePickerValue = (metadata.Model == null ? 
                         DateTime.Now : DateTime.Parse
                         (metadata.Model.ToString()));

The above code grabs the value of the date picker. This value comes from the specified model property. ModelMetadata object basically represents metadata about the model properties and in this case you are retrieving one based on the expression. The second line examines the metadata.Model property to decide whether property value is null or not. If it is null, current date-time is assigned to datePickerValue variable, otherwise value of Model property is assigned to datePickerValue.

Now, further add the following code that forms the <input> tag for the date picker field:

TagBuilder tag = new TagBuilder("input");
tag.Attributes.Add("name", datePickerFullName);
tag.Attributes.Add("id", datePickerID);
tag.Attributes.Add("type", "date");
tag.Attributes.Add("value", datePickerValue.ToString("yyyy-MM-dd"));

This code should be familiar to you by now. It uses a TagBuilder object and adds name, id, type and value attributes. As before value attribute is formatted as yyyy-MM-dd.

The code you wrote so far takes care of rendering a date field that is data bound. However, in real-world cases the field under consideration might be validated using client side script and data annotations. To take care of this possibility you need to add the following code:

IDictionary<string, object> validationAttributes = helper.
                            GetUnobtrusiveValidationAttributes
                            (datePickerFullName, metadata);

foreach (string key in validationAttributes.Keys)
{
    tag.Attributes.Add(key, validationAttributes[key].ToString());
}

The GetUnobtrusiveValidationAttributes() method returns a Dictionary that contains all the client side custom data attributes (data-*) required for the proper functioning of unobtrusive JavaScript based validation. A foreach loop iterates through this Dictionary and adds those attributes to the TagBuilder object under consideration.

Finally, MvcHtmlString is obtained from the TagBuilder as shown below:

MvcHtmlString html=new MvcHtmlString(tag.ToString(
                       TagRenderMode.SelfClosing));
return html;

Completed DatePickerFor() method

This completes the DatePickerFor() method. The complete code of DatePickerFor() is given below for your quick reference:

public static MvcHtmlString DatePickerFor<TModel, TProperty>
(this HtmlHelper<TModel> helper, 
Expression<Func<TModel, TProperty>> expression)
{
    string datePickerName = 
          ExpressionHelper.GetExpressionText(expression);
    string datePickerFullName = helper.ViewContext.ViewData.
                   TemplateInfo.GetFullHtmlFieldName
                   (datePickerName);
    string datePickerID = TagBuilder.CreateSanitizedId
                          (datePickerFullName);

    ModelMetadata metadata = ModelMetadata.FromLambdaExpression
                             (expression, helper.ViewData);
    DateTime datePickerValue = (metadata.Model == null ? 
                             DateTime.Now : DateTime.Parse(
                             metadata.Model.ToString()));

    TagBuilder tag = new TagBuilder("input");
    tag.Attributes.Add("name", datePickerFullName);
    tag.Attributes.Add("id", datePickerID);
    tag.Attributes.Add("type", "date");
    tag.Attributes.Add("value", datePickerValue.
                                ToString("yyyy-MM-dd"));

    IDictionary<string, object> validationAttributes = helper.
    GetUnobtrusiveValidationAttributes
    (datePickerFullName, metadata);

    foreach (string key in validationAttributes.Keys)
    {
        tag.Attributes.Add(key, validationAttributes[key].ToString());
    }

    MvcHtmlString html=new MvcHtmlString(
               tag.ToString(TagRenderMode.SelfClosing));
    return html;
}

Enabling Unobtrusive JavaScript Validations

In the previous sections you took precaution to emit attributes necessary for unobtrusive validations. So, let's enable them so that we can test our helper accordingly. Add a class - EmployeeMetadata and write the following code into it:

public class EmployeeMetadata
{
    [Required]
    public DateTime BirthDate { get; set; }
}

[MetadataType(typeof(EmployeeMetadata))]
public partial class Employee
{
}

The EmployeeMetadata class defines BirthDate property. It also decorates the property with [Required] attribute. Then EmployeeMetadata is attached with the Employee model class as its metadata class. This is done using the partial Employee class.

Now, add HomeController and create Index() action in it.

public ActionResult Index()
{
    NorthwindEntities db = new NorthwindEntities();
    return View(db.Employees.First());
}

Then add Index view. In the <head> section of Index view add <script> references for the following files:

<script src="~/Scripts/jquery-1.10.2.js"></script>
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>

These JavaScript files are required to support unobtrusive validations.

Then you can use our DatePickerFor() helper as shown below:

@using(Html.BeginForm("ProcessForm","Home",FormMethod.Post))
{        
    @Html.DatePicker("birthdateUnbound")
    @Html.DatePickerFor(m=>m.BirthDate)
    @Html.ValidationMessageFor(m=>m.BirthDate)
    <input type="submit" value="Submit" />
}

The <form> submits to ProcessForm action. The ProcessForm() action simply receives the data and returns Index view.

public ActionResult ProcessForm(Employee obj)
{
    return View("Index",obj);
}

The following figure shows how the model bound date picker is displayed in the browser and how the unobtrusive validations work in case BirthDate is kept empty.

You will find that if you comment out the foreach loop from the DatePickerFor() method then client side unobtrusive validations stop working.

That's it! Keep coding !!


Bipin Joshi is a software consultant, an author and a yoga mentor having 22+ years of experience in software development. He also conducts online courses in ASP.NET MVC / Core and Design Patterns. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Meditation and Mindfulness to interested individuals. To know more about him click here.

Get connected : Twitter  Facebook  Google+  LinkedIn

Posted On : 01 June 2015


Tags : MVC C# jQuery JavaScript