Using HtmlHelpers to generate Custom TextArea (Part 2)

This is Part 2 in a (very) mini series on HtmlHelper methods. Here is the link for Part 1, Using HtmlHelpers to generate HTML elements

This article is about creating a HtmlHelper for a TextArea although the same principles apply for other input controls as well. For this example I have created a simple Employee model class.

namespace HtmlHelpersDemo.Models
{
    public class Employee
    {
        public string Title { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}

Nothing special about the controller. An Employee object is instantiated and passed to the view. Following MVC conventions the Index view from the Home folder is rendered.

using HtmlHelpersDemo.Models;
using System.Web.Mvc;

namespace HtmlHelpersDemo.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View(new Employee());
        }
    }
}

Before looking at the new TextAreaFor HtmlHelper method we will look at how it is called. The method is written as an extension to the Mvc HtmlHelper class (System.Web.Mvc) which allows it to be called the same way the built in TextAreaFor methods are called using @Html..
The first example passes values in a htmlattributes parameter in the form of a class and a data_ref attribute which has an underscore rather than hyphen.

@model HtmlHelpersDemo.Models.Employee
@using HtmlHelpersDemo.Code;
@{
    ViewBag.Title = "Index";
}
<h2>Index</h2>

@Html.TextAreaFor(m => m.FirstName, new { @class = "input" , data_ref="123"}, false)

@Html.TextAreaFor(m => m.FirstName,  false)

Here is the code that does all the work. As mentioned this is written as a HtmlHelper extension method so the first parameter is not actually passed in from the calling code. Rather it defines the class the method is associated with and provides us with a local reference to manipulate, in this instance the local name of HtmlHelper is htmlHelper.

using System;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.Routing;

namespace HtmlHelpersDemo.Code
{
    public static class TextAreaExtensions
    {
        public static MvcHtmlString TextAreaFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression, object htmlAttributes, bool IsReadonly)
        {
            var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
			attributes["class"] = "form-control" + " " + attributes["class"];

            if (IsReadonly)
            {
                attributes.Add("readonly", IsReadonly);
            }

            MvcHtmlString html = default(MvcHtmlString);
            RouteValueDictionary routeValues = new RouteValueDictionary(attributes);
            html = System.Web.Mvc.Html.TextAreaExtensions.TextAreaFor(htmlHelper, expression, routeValues);
			TextAreaFor()
            return html;
        }

        public static MvcHtmlString TextAreaFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression, bool IsReadonly)
        {
            return htmlHelper.TextAreaFor(expression, null, IsReadonly);
        }
    }
}

Lets disect what the method is doing. The first line calls a built in method passing in the htmlAttributes parameter. This method replaces underscore characters with hyphens which is how the data_ref is translated to data-ref when rendered.

var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);

As all text area controls is this application requires the form-control css class, it is being added to the attributes in this method along with any other css class passed in throught the htmlattributes parameter.

attributes["class"] = "form-control" + 
    (!string.IsNullOrEmpty( attributes["class"] as string) ? " "+ attributes["class"] : "");

If the IsReadOnly parameter is set, the readonly state of the control is assigned to the attribues object.

if (IsReadonly)
{
	attributes.Add("readonly", IsReadonly);
}

The final four rows create a TextArea input control from the attributes and route values if there are any.

MvcHtmlString html = default(MvcHtmlString);
RouteValueDictionary routeValues = new RouteValueDictionary(attributes);
html = System.Web.Mvc.Html.TextAreaExtensions.TextAreaFor(htmlHelper, expression, routeValues);
return html;

There rendered html for the two examples above looks like this.

<textarea class="form-control input" cols="20" data-ref="123" id="FirstName" name="FirstName" rows="2">
</textarea>

<textarea class="form-control" cols="20" id="FirstName" name="FirstName" rows="2">
</textarea>

Enumeration Helper Extension Methods using Generics

Here is a helper class for enumerations. The purpose of the class is to use generics to wrap the current member methods, like Parse, but also to add additional methods, like returning the description of a Named Constant.

public static class EnumHelper
{
    public static Nullable<T> Parse<T>(String value, Boolean ignoreCase) where T : struct
    {
        return String.IsNullOrEmpty(value) ? null : (Nullable<T>)Enum.Parse(typeof(T), value, ignoreCase);
    }

    public static T[] GetValues<T>()
    {
        return (T[])Enum.GetValues(typeof(T));
    }

    public static string EnumToString<T>(object value)
    {
        return Enum.GetName(typeof(T), value);
    }

    public static T Parse<T>(String value, Boolean ignoreCase, T defaultEnum) where T : struct
    {
        if ((!string.IsNullOrEmpty(value)) && (Enum.IsDefined(typeof(T), value)))
            return (T)EnumHelper.Parse<T>(value, ignoreCase);
        else
            return defaultEnum;
    }

    public static string GetEnumDescription(Enum en)
    {
        Type type = en.GetType();
        MemberInfo[] memInfo = type.GetMember(en.ToString());
        if (memInfo != null && memInfo.Length > 0)
        {
            object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
            if (attrs != null && attrs.Length > 0)
                return ((DescriptionAttribute)attrs[0]).Description;
        }
        return en.ToString();
    }
}

To demonstrate how the helper methods are used I have written unit tests. The tests depend on the following enumeration to work.

public enum Days
{
    [Description("First day of week")]
    Monday,
    [Description("Second day of week")]
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday,
}

Here are the unit tests.

public class EnumHelperTests
{
    [SetUp]
    public void Setup()
    { }

    [TearDown]
    public void Teardown()
    { }

    [Test]
    public void EnumHelper_Parse_ReturnCorrectNamedConstant()
    {
        // Act
        var enumResult = EnumHelper.Parse<Days>("monday", true);

        // Assert
        Assert.AreEqual(enumResult, Days.Monday);
    }

    [Test]
    public void EnumHelper_EnumToString_ReturnsNamedConstantAsString()
    {
        // Act
        var day = EnumHelper.EnumToString<Days>(2);

        // Assert
        Assert.AreEqual(day, "Wednesday");
    }

    [Test]
    public void EnumHelper_GetEnumDescription_CorrectConstantDescription()
    {
        // Act
        var desc = EnumHelper.GetEnumDescription(Days.Monday);

        // Assert
        Assert.AreEqual("First day of week", desc);
    }

    [Test]
    public void EnumHelper_GetValues_ArrayOfNamedConstants()
    {
        //  Arrange
        var days = new Days[7] {
        Days.Monday,
        Days.Tuesday,
        Days.Wednesday,
        Days.Thursday,
        Days.Friday,
        Days.Saturday,
        Days.Sunday
    };

        // Act
        var enumDays = EnumHelper.GetValues<Days>();

        // Assert
        CollectionAssert.AreEqual(days, enumDays);
    }
}

A full demo app is available from Github here

String Object Extension Methods

The C# Programming Guide describes extension methods as,

Extension methods enable you to “add” methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. Extension methods are a special kind of static method, but they are called as if they were instance methods on the extended type. For client code written in C# and Visual Basic, there is no apparent difference between calling an extension method and the methods that are actually defined in a type.

Below are a couple of examples that extend the string object.  The first method reverses a string, functionality which appears to be missing from C#

public static String Reverse(this String oldString)
{
    StringBuilder newString = new StringBuilder();
    for (int i = oldString.Length - 1; i >= 0; i--)
    {
        newString.Append(oldString[i]);
    }
    return newString.ToString();
}

The next method uses regular expressions to replace characters in a string.

public static String Replace(this string originalString, string oldValue,
         string newValue, bool ignoreCase = false)
{
    Regex regEx = new Regex(oldValue,
        RegexOptions.IgnoreCase | RegexOptions.Multiline);
    if (ignoreCase)
    {
        regEx = new Regex(oldValue, RegexOptions.Multiline);
    }
    return regEx.Replace(originalString, newValue);
}

The type of the first parameter, which has the this keyword before it, defines what type of object is being extended. Normal this. notation can be used to reference properties of the extended object  from within the method.

To those of you familiar with SOLID principles the above description of extension methods will sound a lot like the Open-Closed principle (OCP).  This principle states that objects should be extendable without recompiling the application, modifying the original object or changing the existing behaviour of the object.  I agree to some degree that extensions methods do follow the OCP, though there is more to the principle than extending an object with static methods.  For now I will leave it to you to investigate  OCP further.