.net, Clean Code

MVC – Enhanced DropdownListFor – Part #1

This is the first in a couple of posts where I overload Html.DropDownListFor to a signature that makes more sense to me.

Part #1 is about explaining the problem and creating a more friendly extension method signature.

Part #2 is about structuring the project and creating the actual code.

The addition of the enhanced HTML helpers in MVC2 made a massive difference to code readability and robustness, going from (as an example)

@HTML.TextBox("UserName")

to

@HTML.TextBoxFor(m => m.UserName)

In both cases, the HTML rendered is:

<input id="UserName" name="UserName" type="text" value="the user name" />

With HTML.TextBoxFor, we get rid of the hard-coding and replace with a nice clean (and compiler friendly) lambda. I guess I have always wondered what the logic was for suffixing the original extension methods with “For”, but it’s become the accepted idiom to suggest that the extension contains a lambda.

However, from my own experience, and from speaking to others, there’s one enhanced extension method which still causes confusion among developers – DropDownListFor.

I think this is because the DropDownListFor extension doesn’t obviously follow the same method pattern abstraction as the others (nobody said it should – I’m not criticising).

So when you consider HTML.TextBoxFor(m => m.UserName), the lambda serves two purposes:

  1. The property name becomes the form field used for the HTML component’s Id/Name value.
  2. The contents of the property are rendered in the control.

HTML.DropDownListFor uses the first lambda to determine the form field for the HTML component’s Id and Name – which is consistent. But it does not store the data that is rendered in the browser. The actual contents of the dropdown list (and what’s selected) are determined by a special MVC object called SelectList.

So to display a dropdown list from a model which contains a list of usernames and user ids, and the user id to display is held in a property called UserId, you’d need to use the Razor snippet below.

@Html.DropDownListFor(m => m.UserId, new SelectList(Model.UserNames, "Id""Name", Model.UserId))

I think that looks a bit confusing. The hard coding feels like a backwards step from MVC2, and instantiating the SelectList after the lambda is definitely code starting to appear in the view, which we want to avoid.

You could of course move this instantiation across to your view model, but you’re just moving the problem – and you’ve just tightly coupled your ViewModel to the MVC implementation libraries.

I think a better method signature would be:

@Html.DropDownListFor(m => m.UserId, m => m.UserNames, m => m.Id, m => m.Name)

So now I have (what I think) is a nice overload specification for DropDownListFor. In the next post I’ll build up the project infrastructure in Visual Studio and start coding.