2

So I have a form that includes a list of items, and each item has a select list. My code is like so (with extraneous items removed):

@for( var i = 0; i < Model.InputSlots.Count; i++)
{
 @Html.DropDownListFor(model => model.InputSlots[i].VariableId,
      new SelectList(Model.Variables, "VariableId", "Name"),
      "--Select Variable--", 
      new { @class = "span8" })
}

The drop down list displays correctly, and if I select one of the items and post the form, the selection posts correctly, but when I load the page with the VariableId set to a valid option, it does not show the selected option, but shows the default "--Select Variable--" instead.

I use the DropDownListFor in many other places in my code, and it works as expected, just not in this case. The only difference that I can tell is that it is referencing a property of an array item.

I have set a break-point in the view to verify that Model.InputSlots[i].VariableId is set to a valid option (2), and I also viewed the resulting HTML to make sure that there was an option with a value of 2 and both seemed correct, but there was no "selected" on the option element with the value of 2.

Is this a known behavior, or am I just doing something wrong somewhere that I haven't noticed?

Update:

Setting the selected item in the SelectList constructor does work, i.e.:

 @Html.DropDownListFor(model => model.InputSlots[i].VariableId,
      new SelectList(Model.Variables, "VariableId", "Name", Model.InputSlots[i].VariableId),
      "--Select Variable--", 
      new { @class = "span8" })

But it does not seem to want to get it from the expression as it does in other cases.

Gerald
  • 23,011
  • 10
  • 73
  • 102
  • See e.g. http://stackoverflow.com/questions/17691742 and either of the five questions linked from it (they're all variations of the same thing). – JimmiTh Jul 26 '13 at 00:02
  • Those links did remind me that there was an argument in the constructor of SelectList to set the selected item, which does work, but they don't really address the issue of why it's not getting it from my lambda as it's supposed to. My understanding is that DropDownListFor is really supposed to ignore that argument unless it can't figure it out from the expression. – Gerald Jul 26 '13 at 00:24
  • Right, misread the question... The problem here is that all the helper uses the lambda for is translating it into a string representation, which it uses to look up the values in `ModelState` or `ViewData`. In other words, by that point it actually has no idea what value `i` has, so it can't find the value of `Model.InputSlots[i]`. – JimmiTh Jul 26 '13 at 09:19
  • The helper must be able to figure out the value of i, because it uses it for the name of the input field. i.e. the name of the select element generated looks like this: `name="InputSlots[0].VariableId"` – Gerald Jul 26 '13 at 14:29
  • Yeah, that was a bit of nonsense from me. I'll add an answer with an explanation that's closer to the actual one. – JimmiTh Jul 27 '13 at 03:31

1 Answers1

2

The issue here is the way all the DropDown helpers look up the defaultValue (which is always a pain point).

When they find ModelState (i.e., when returning the view from a POST), they'll use that, and your case will work fine, because the ModelState does have a dictionary entry named InputSlots[0].VariableId.

When there's no ModelState, however, they'll fall back to ViewData and its Eval() method.

That method will look through the view data for an InputSlots[0].VariableId dictionary key - or a property on the Model with that name (through TypeDescriptor.GetProperties()).

It will also try InputSlots[0] - i.e. find a property with that name or a dictionary entry with that key - and then look for a VariableId sub-property.

Of course, neither InputSlots[0].VariableId nor InputSlots[0] will be found, because there isn't a property or key that includes [0] in its name - and Eval() doesn't do any work to parse the indexers - it works by simply splitting the string at dots (.).

In short, the helpers - as they work in MVC 4 - won't be able to automatically find a default value if there's an indexer in the lambda expression (or string literal) passed to the helper. I can't think of a much better way than specifying it yourself, like you do in the update to the question.

JimmiTh
  • 7,389
  • 3
  • 34
  • 50
  • You know, I completely forgot that MVC was open-source. After looking over the code, it seems that you are correct. I would have thought they would just execute the lambda expression to get the default value, but apparently not. – Gerald Jul 27 '13 at 21:51