-->

10/10/2011

MVC Razor - Custom Html Helper Calsses.


We have seen several examples on MVC Razor in earlier posts. In every code module we demonstrated until now, @Html.SomeFunction() made our lifes easy with MVC. These are nothing but Html Helper classes.

HTML Helper classes:
An HTML Helper is just a method that returns a string. The string can represent any type of content that you want. For example, you can use HTML Helpers to render standard HTML tags like HTML <input> and <img> tags. You also can use HTML Helpers to render more complex content such as a tab strip or an HTML table of database data.
The ASP.NET MVC framework includes the following set of standard HTML Helpers (this is not a complete list):
  • Html.ActionLink()
  • Html.BeginForm()
  • Html.CheckBox()
  • Html.DropDownList()
  • Html.EndForm()
  • Html.Hidden()
  • Html.ListBox()
  • Html.Password()
  • Html.RadioButton()
  • Html.TextArea()
  • Html.TextBox()
We can even write our own helper classes to perform common & complex tasks with ease.
Immediately a question pops up into your mind.
Why Helper classes, why not a DLL where i can use through out the project?
Yes, you can do that, but with helper class you can do better. Let me explain you. If you use the function as DLL, you might use it in model or Controller. Thus you are making that functionality specific to that controller or Model or may be view. But if you use it as a helper class, it can be used just like any html helper class through out the project. In future if you changed the helper method name, it requires no code change in Controller or Model. Search the old method name and replace it with new method name just like any html tag.
Enough with theory, lets jump in to practice.


You might have seen EBay site, where the time remaining to close the bid will be displayed beside each product.
Now my objective is to create a simple html helper class to display date difference in one of my applications. Lets see how we have done it step by step.

Step 1: Create a class library with required functionality to perform the required task. But there a few things that we need to take care.
                  i. All Html helper classes are static.
                 ii. All helper methods are static.
                iii. One of the parameters of helper method should be HTMLHelper object.
                iv. The return type of every Html helper method will be "MVCHtmlString"
namespace MvcHtmlHelpers
{
    public static class MvcHtmlHelpers
    {
        public static MvcHtmlString CalculateDateDiff(this HtmlHelper helper, string strdate1, string strdate2)
        {
            try
            {
                int Years, Weeks, Months, Days;
                DateTime date1, date2;
                if ((strdate1 != "" || strdate1 != null) && (strdate2 != "" || strdate2 != null))
                {
                    date1 = Convert.ToDateTime(strdate1);
                    date2 = Convert.ToDateTime(strdate2);

                    TimeSpan diff = date2 - date1;
                    Years = diff.Days / 366;
                    DateTime workingDate = date1.AddYears(Years);

                    while (workingDate.AddYears(1) <= date2)
                    {
                        workingDate = workingDate.AddYears(1);
                        Years++;
                    }

                    //months
                    diff = date2 - workingDate;
                    Months = diff.Days / 31;
                    workingDate = workingDate.AddMonths(Months);

                    while (workingDate.AddMonths(1) <= date2)
                    {
                        workingDate = workingDate.AddMonths(1);
                        Months++;
                    }

                    //weeks and days
                    diff = date2 - workingDate;
                    Weeks = diff.Days / 7; //weeks always have 7 days
                    Days = diff.Days % 7;

                    string strReturn = Years.ToString() + " Years " + Months.ToString() + " Months " + Weeks.ToString() + " Weeks " + Days.ToString() + " Days ";
                    return new MvcHtmlString(strReturn);

                }
                else
                {
                    return new MvcHtmlString("Please enter Proper Dates");
                }
            }
            catch (FormatException ex)
            {
                return new MvcHtmlString("Wrong date Format");
            }
        }
      
    }

}
The above method will return the date diff in format "1 years, 2 months, 3 weeks, 3 days".

Step2: Lets create a simple model to support this from date and to date structure.
namespace CustomHtmlExtensionTest.Models
{
    public class DateModels
    {
        private string _toDate;
        private string _frmDate;
        [Required(ErrorMessage = "From Date is required")]
        [DataType(DataType.DateTime)]
        public string FrmDate { get { return _frmDate == null ? DateTime.Now.AddYears(-1).ToString("MM-dd-yyyy") : _frmDate; } set { _frmDate = value; } }
        [Required(ErrorMessage = "To Date is required")]
        [DataType(DataType.DateTime)]
        public string ToDate { get { return _toDate == null ? DateTime.Now.ToString("MM-dd-yyyy") : _toDate; } set { _toDate = value; } }
    }
}
Step 3: Now create model and view to support testing the helper class. i created a view which accepts from date and to date and a simple action link which will trigger the calculation part.
View UI:
Controller code:
namespace CustomHtmlExtensionTest.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Message = "MVC Custom Helper class Sample";
            DateModels model = new DateModels();
            return View(model);
        }


        //public ActionResult Index(string Dates)
        //{

        //    DateModels model = new DateModels();
        //    if (Dates != null)
        //    {
        //        ModelState.Clear();
        //        string[] strDates = Dates.Split('$');
        //        model.FrmDate = strDates[0];
        //        model.ToDate = strDates[1];
        //    }
        //    ViewBag.Message = "MVC Custom Helper class Sample";
        //    return View(model);
        //}

        [HttpPost]
        public ActionResult Index(DateModels model)
        {
            ViewBag.Message = "MVC Custom Helper class Sample";            
            return View(model);
        }

        public ActionResult About()
        {
            return View();
        }
    }
}
Now how to use our newly created html helper class in our view.

Step 4: Add the reference of Helper class project to current MVC project. Include the namespace of html helper class in web.config of MVC app.
 Step 5: Include the namespace in View using "Using".
Step 6: Just call our helper class where ever required like any other helper class and provide required parameter values.
Finally Our View code will be :
@model CustomHtmlExtensionTest.Models.DateModels
           
@using MvcHtmlHelpers;

<script type="text/javascript"> 
   function Calculate() {      
    var url = '@Url.Action("Index", "Home")';
    $.post(url, $("#Index").serialize(), function (data) { });
}
</script>


@{
    ViewBag.Title = "Home Page";
}

<h2>@ViewBag.Message</h2>

@using (Html.BeginForm("Index", "Home", FormMethod.Post, new { id="Index"}))
{
<p>
    <table>
    <tr>
        <td style="width:40%">
         From Date :
        </td>
        <td>
            @Html.EditorFor(m => m.FrmDate) (MM/DD/YYYY)
            @Html.ValidationMessageFor(m => m.FrmDate)
        </td>
    </tr>
    <tr>
        <td>
            To Date :
        </td>
        <td>
            @Html.EditorFor(m => m.ToDate) (MM/DD/YYYY)    
            @Html.ValidationMessageFor(m => m.ToDate)
        </td>
    </tr>
    <tr>
       <td colspan="2" align="center">
        <label id="lblMessage">
            @Html.CalculateDateDiff(Model.FrmDate, Model.ToDate)          
         </label>
       </td>
    </tr>
    <tr>
        <td colspan="2" align="center">
           @*@Html.ActionLink("Calculate", "Index", "Home", new { Dates = Model.FrmDate.ToString() + "$" + Model.ToDate.ToString() }, null)*@
           <input type="submit" value="Calculate" />
          @*<a onclick="Calculate()">Calculate</a>*@
        </td>
    </tr>
    </table>
    
    <br />    
</p>
}
Please note that we are using Ajax post back for updating the model and view.

The final out put will be a helper call where you can just import the assembly to a  view and use the helper class in your view with out a single line of code in controller or model.
Out Put:
Observe the default validation done just by defining Data annotations in model.
Look out the precise date diff which is generated just by calling the @Html.CalculateDateDiff(string Frmdate, string Todate)

The whole functionality of helper class is completely disconnected from implementation of Controller and Model, thus providing Loosely coupled implementation. So lesser the level of dependency, better the design will be.

Until now we have seen how html helper class behavior in case of post back. Now in the View definition, i disabled the Input button and enabled the third link ie.,
  <a onclick="Calculate()">Calculate</a>
Now if you run the view and change the date value of from and try hitting the Calculate link.
The out put of Html helper class is not going to update as required. Reason being the view will not be constructed until next postback. But we always need not to do a postback. In that scenario how to update the helper class output.
To do so, we need to update the $.post() function. We need to add the callback functionality in order to do the task.
function Calculate() {        
       var url = '@Url.Action("Index", "Home")';
       $.post(url, $("#Index").serialize(), function (data) {
           var $response = $(data);
           $('#lblMessage').html($response.find("#lblMessage").text());
       });

Let me explain this. when you say function(data){}, the data will be the response sent by controller which will be having the markup of updated view. But keep in mind that its our job to update the required content to view using JQuery.
So, all i did is convert that data object to JQuery object and then query it for my required data and then update the required element with it.
Code:
Click Here
Is it helpful for you? Kindly let me know your comments / Questions.

7 comments:

  1. MvcHtmlHelpers may need reference to System.Web.Mvc

    ReplyDelete
  2. You rock! This was very helpful in getting my last changes made on my site.

    ReplyDelete
  3. You rock! This was very helpful in getting my last changes made on my site.
    thank you very much

    ReplyDelete