Dec 272013
 

I learn something every day whether I like it or not. Today’s lesson:

SelectList thinks she’s smarter than you.

Observations

I was working in an MVC4 app, making some forms light up with some custom HtmlHelpers. Everything is dandy until a drop-down doesn’t re-populate with the previously selected value after a POST or a fresh GET. That’s funky. The right value is in the database. So I looked at the .cshtml. I had two drop-downs next to each other. I changed the custom HtmlHelpers to PODDLFs (plain old DropDownListFor) and it does the same thing. The one for Suffix “binds” the previously selected value as I’d expect, but the one for Title appears to do nothing.

@Html.DropDownListFor(model => model.Title, Model.SelectLists.PreferredTitles)
@Html.DropDownListFor(model => model.Suffix, Model.SelectLists.Suffixes)

So to be safe, let’s print out the value of Title as a string literal.

Testing: @Model.Title

Yep, works fine. I see “Mr.” just as I’d expect. So I searched for every instance of “.Title” to see if this is happening somewhere else in the app, but there are no other uses in a .cshtml file. What I did find was many instances of @ViewBag.Title being used to set the window and page titles throughout the app. I renamed “Title” to “Prefix” on the model and the fog clears a little. There’s something going on with ViewBag’s Title taking precedence over my model’s Title. To be sure, I undid the renaming operation and changed the impacted view’s ViewBag.Title to be “Mr.”, and then “Dr.”. Regardless of the current value of Model.Title, the value of ViewBag.Title is always used to set the selected value.

Analysis

You can build your SelectList and set whatever “selectedValue” you want. DropDownListFor calls SelectInternal (excerpt below) to build the MvcHtmlString. SelectInternal is responsible for binding the appropriate value for the model/property used in the expression of DropDownListFor.  When the value is not found with GetModelStateValue, ViewData.Eval is used to get the “selected value”. Deep in the internals of ViewData.Eval, ViewBag takes precedence over your model.

object defaultValue = allowMultiple ? htmlHelper.GetModelStateValue(fullHtmlFieldName, typeof(string[])) : htmlHelper.GetModelStateValue(fullHtmlFieldName, typeof(string));
if ((!flag && (defaultValue == null)) && !string.IsNullOrEmpty(name))
{
    defaultValue = htmlHelper.ViewData.Eval(name);
}
if (defaultValue != null)
{
    selectList = GetSelectListWithDefaultValue(selectList, defaultValue, allowMultiple);
}

So what actually happened was SelectInternal took my page title and tried to make it the selected value in the drop-down list. Knowing why it does this doesn’t make me any happier. I’d really prefer that DropDownListFor use my model’s value like I told it to. Alas, I didn’t write this code and it’s pretty dumb of me to not recognize the clear naming conflict. So I’ll accept this and move on.

Corrective Action

Clearly the best solution is to use much more descriptive names that don’t clobber each other. Changing ViewBag.Title to be ViewBag.PageTitle is the path of least resistance. Simply using “Title” on the model wasn’t very good either. It would be better as “Salutation”, “NamePrefix” or “PreferredTitle” anyway. These types of hidden naming conflicts are sure to stump some people. Remembering this little nugget of the SelectList internals will keep naming/conflicts on my mind for some time.

 Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)