Digging around: TextBoxFor in ASP.NET MVC 2

This is a companion post to Scott Guthrie’s ASP.NET MVC 2: Strongly Typed Html Helpers.

I was interested in what made this code:

<%= Html.TextBoxFor(model => model.ProductName) %>

in an aspx view template produce this html:

<input id="ProductName" name="ProductName" type="text" value="Aniseed Syrup" />

In order to do it ASP.NET MVC has performed two distinct operations:

  1. parsed the expression tree produced by the compiler for model => model.ProductName to extract the name of the property ("ProductName") to use in the id and name attributes of the input element
  2. compiled the expression and invoked the resulting delegate to produce the "Aniseed Syrup" value

TextBoxFor extension method

The call to Html.TextBoxFor ends up in the following extension method in the InputExtensions class:

public static MvcHtmlString TextBoxFor<TModel, TProperty>( 
    this HtmlHelper<TModel> htmlHelper, 
    Expression<Func<TModel, TProperty>> expression, 
    IDictionary<string, object> htmlAttributes) 
{ 
    return TextBoxHelper( 
        htmlHelper, 
        ModelMetadata.FromLambdaExpression(
            expression, 
            htmlHelper.ViewData).Model, 
        ExpressionHelper.GetExpressionText(expression), 
        htmlAttributes); 
}

I’ve highlighted the second and third parameters. The call to TextBoxHelper() does the final rendering by:

  • calling Convert.ToString() on the second parameter to populate the value attribute of the input element
  • using the third parameter to populate the name and id attributes of the input element

But let’s backtrack a bit first and look at the TextBoxFor signature itself.

Quick detour to Expression<Func<TModel, TProperty>>

Here’s the signature of the TextBoxFor method in the previous section:

public static MvcHtmlString TextBoxFor<TModel, TProperty>( 
    this HtmlHelper<TModel> htmlHelper, 
    Expression<Func<TModel, TProperty>> expression, 
    IDictionary<string, object> htmlAttributes) 
{ 
... 
... 
} 

By declaring a parameter of type Expression<Func<TModel, TProperty>> the TextBoxFor method has asked the compiler to generate an expression tree rather than a delegate.

Expression<TDelegate> is derived from LambdaExpression, which represents an expression tree which can be compiled into a delegate by calling its Compile() method.

Interestingly, the implementation of Compile() in Expression<TDelegate> calls Compile() on its base and casts the resulting delegate to TDelegate, so its a pretty thin wrapper for LambdaExpression.

All these uses of the term "lambda expression" can be mighty confusing. We call

model => model.ProductName

a lambda expression when we see it in code. The compiler however will treat it as an anonymous function or an expression tree, depending on the context

If it’s being assigned to Func<TModel, TProperty> it is treated as an anonymous function.

But if it’s being assigned to Expression<Func<TModel, TProperty>> then it is treated as an expression tree.

Anyway, back to the TextBoxFor method.

ExpressionHelper.GetExpressionText

public static MvcHtmlString TextBoxFor<TModel, TProperty>( 
    this HtmlHelper<TModel> htmlHelper, 
    Expression<Func<TModel, TProperty>> expression, 
    IDictionary<string, object> htmlAttributes) 
{ 
    return TextBoxHelper( 
        htmlHelper, 
        ModelMetadata.FromLambdaExpression(
            expression, 
            htmlHelper.ViewData).Model, 
        ExpressionHelper.GetExpressionText(expression), 
        htmlAttributes); 
} 

We want to get the text with which to populate the id and name attributes of our text box.

Because MVC has an expression tree rather than a delegate it can can pick it apart to find out, in this case, the name of the property being accessed.

Here’s my stripped down version of the implementation of GetExpressionText(), which covers the case of our simple model.ProductName property access:

public static string GetExpressionText(LambdaExpression expression) 
{ 
    MemberExpression memberExpression = (MemberExpression)expression.Body; 
    return memberExpression.Member.Name; 
}

The body of the expression is cast to a MemberExpression so that its Member.Name property can be accessed.

This returns "ProductName".

That was easy. Now on to the evaluation of the expression itself.

ModelMetadata.FromLambdaExpression

public static MvcHtmlString TextBoxFor<TModel, TProperty>( 
    this HtmlHelper<TModel> htmlHelper, 
    Expression<Func<TModel, TProperty>> expression, 
    IDictionary<string, object> htmlAttributes) 
{ 
    return TextBoxHelper( 
        htmlHelper, 
        ModelMetadata.FromLambdaExpression( 
            expression, 
            htmlHelper.ViewData).Model, 
        ExpressionHelper.GetExpressionText(expression), 
        htmlAttributes); 
} 

OK so we’ve got hold of of the string

Leave a Reply

Your email address will not be published. Required fields are marked *