Skip to content
Sep 3 / davidalpert

ASP.NET MVC Preview 5: strongly typed HtmlHelpers

ScottGu’s recent post on Form Posting Scenarios in ASP.NET MVC Preview 5 turned on a lot of lights for me regarding recent changes in How a Method Becomes an Action and the new ActionNameAttribute.

Magic data binding …

I was particularly impressed with the round-trip scenario that Scott describes and the clean code in his Edix.aspx view:

            <tr>
                <td>Product Name:</td>
                <td>
                    <%=Html.TextBox("ProductName") %>
                    <%=Html.ValidationMessage("ProductName", "*")%>
                </td>
            </tr>

That seems like a very simple way to add/enable server-side validation and I look forward to using it.

… with strongly typed helpers

In my ongoing quest to eliminate “magic strings” from my code I would much rather write this

            <tr>
                <td>Product Name:</td>
                <td>
                    <%=Html.TextBox<Product>(p => p.ProductName) %>
                    <%=Html.ValidationMessage<Product>(p => p.ProductName, "*")%>
                </td>
            </tr>

and lean on the speed and compile-time checks of intellisense and strongly typed functions.

Red – write a failing test

To achieve this syntax, I wrote the following test

        [Fact]
        public void TextBoxOfT_should_take_a_property_access_and_name_the_textbox_with_the_property_name()
        {
            // Arrange
            HtmlHelper Html =
                MoqHtmlHelper.CreateMoqHtmlHelper<ProductsController>("~/Products/Edit/10", (NorthwindDataContext)null);

            // act
            string result = Html.TextBox<Product>(p => p.ProductName);

            // assert
            Assert.False(String.IsNullOrEmpty(result));
            Assert.True(Regex.IsMatch(result, "id=\"ProductName\"", RegexOptions.IgnoreCase), result);
        }

Green – make it work

And made it pass with the following code

        public static string TextBox<T>(this HtmlHelper helper,
                                        Expression<Func<T, object>> memberAccessExpression)
        {
            MemberExpression memberExpression =
                memberAccessExpression.Body as MemberExpression;

            // Choice: instead of throwing an exception,
            //         you could return an empty string
            if (memberExpression == null)
            {
                throw new InvalidExpressionException(
                    memberAccessExpression
                    + " is not a valid propery access");
            }

            string name memberExpression.Member.Name;
            return helper.TextBox(name);
        }

Thanks here go to Nigel Sampson for some help with the nuances of using LINQ MemberExpressions to grab property names.

Refactor, refactor, refactor

Finally, I refactored the property name resolution so that I could share it amongst both TextBox<T> and ValidationMessage<T>

        public static string GetNameOfMember<T>(Expression<Func<T, object>> memberAccessExpression)
        {
            MemberExpression memberExpression =
                memberAccessExpression.Body as MemberExpression;

            // Choice: instead of throwing an exception,
            //         you could return an empty string
            if (memberExpression == null)
            {
                throw new InvalidExpressionException(
                    memberAccessExpression
                    + " is not a valid propery access");
            }

            return memberExpression.Member.Name;
        }

        public static string TextBox<T>(this HtmlHelper helper,
                                        Expression<Func<T, object>> memberAccessExpression)
        {
            string name = GetNameOfMember<T>(memberAccessExpression);
            return helper.TextBox(name);
        }

        public static string ValidationMessage<T>(this HtmlHelper helper, Expression<Func<T, object>> memberAccessExpression)
        {
            string name = GetNameOfMember<T>(memberAccessExpression);
            return helper.ValidationMessage(name);
        }

        public static string ValidationMessage<T>(this HtmlHelper helper, Expression<Func<T, object>> memberAccessExpression, string validationMessage)
        {
            string name = GetNameOfMember<T>(memberAccessExpression);
            return helper.ValidationMessage(name, validationMessage);
        }

For completeness, here is the code

HtmlHelperExtensions.cs
a complete set of typed extensions on HtmlHelper’s various helper functions
HtmlHelperExtensionsFixture.cs
tests for the important bits using xUnit.NET and Moq
MoqHtmlHelper.cs
a MoqHtmlHelper class that I wrote to mock up HtmlHelper for testing
MvcMockHelpers.cs
The MvcMoqHelper class that Kzu contributed to Scott Hanselman’s blog
mvcApplication49_stronglyTypedHtmlHelpers.zip
A modified version of the sample application ScottGu included in his post that demonstrates usage of this code.

Leave a comment