• All Posts
  • Code
  • Design
  • Process
  • Speaking
  • Poetry
  • About
D.

September 03, 2008 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:

1
2
3
4
5
6
7
<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

1
2
3
4
5
6
7
<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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 and ValidationMessage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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.

back to top

© David Alpert 2025