Getting some very annoying behaviour from my MVC3 app. So I've got a model with 3 values:
[DisplayName("Airlines ")]
[StringLength(2, ErrorMessage = "Airline codes can only be 2 characters in length")]
public string AirlineCode1 { get; set; }
[DisplayName("Airlines")]
[StringLength(2, ErrorMessage = "Airline codes can only be 2 characters in length")]
public string AirlineCode2 { get; set; }
[DisplayName("Airlines")]
[StringLength(2, ErrorMessage = "Airline codes can only be 2 characters in length")]
public string AirlineCode3 { get; set; }
Now these are populated from a DropDowList
so I'm popping the DropDownListItem
s in the ViewBag
and rendering them in the view such:
@Html.LabelFor(l => l.AirlineCode1) <span>(select/enter code in box on right)</span>
<div id="airportCode1">
@Html.DropDownListFor(d => d.AirlineCode, ViewBag.AirLines as List<SelectListItem>) <input type="text" maxlength="2" value="@Model.AirlineCode1" />
</div>
<div id="airportCode2" style="@Model.AirlineCode2Style">
@Html.DropDownListFor(d => d.AirlineCode, ViewBag.AirLines2 as List<SelectListItem>) <input type="text" maxlength="2" value="@Model.AirlineCode2" />
</div>
<div id="airportCode3">
@Html.DropDownListFor(d => d.AirlineCode3, ViewBag.AirLines3 as List<SelectListItem>) <input type="text" maxlength="2" value="@Model.AirlineCode3" />
</div>
So my controller looks like:
IEnumerable<SelectListItem> airLines = PopulateAirlines(user);
ViewBag.AirLines = airLines;
ViewBag.AirLines2 = airLines;
ViewBag.AirLines3 = airLines;
Now in some circumstances I want to prepoulate AirLineCode
in the model. so I set the model value in the controller. This resulted in some odd behaviour. Suddenly all my DropDownList
s contained the prepopulated value!
Checked the model, value only set in AirLineCode1
. Checked the ViewBag
, no selected SelectListItems
. So I figured the ViewBag
must be maintaining a reference. So I changed my code to:
ViewBag.AirLines = PopulateAirlines(user);
ViewBag.AirLines2 = PopulateAirlines(user);
ViewBag.AirLines3 = PopulateAirlines(user);
Boom, fixed. Problem is PopulateAirlines
is an expensive process!
Problem is the ViewBag appears to be maintaing a reference between the 3 SelectListItem
List
s. How do I stop it doing this and still only make one call to PopulateAirlines(user);
?
I tried the below code and this completely blew up:
IEnumerable<SelectListItem> airLines = PopulateAirlines(user);
ViewBag.AirLines = airLines;
ViewBag.AirLines2 = airLines.Select(s => new SelectListItem() { Text = s.Text, Value = s.Value, Selected = s.Selected });
ViewBag.AirLines3 = airLines.Select(s => new SelectListItem() { Text = s.Text, Value = s.Value, Selected = s.Selected });
with the error:
There is no ViewData item of type 'IEnumerable' that has the key 'AirlineCode2'.
What??!
It may be because of in this code:
IEnumerable<SelectListItem> airLines = PopulateAirlines(user);
ViewBag.AirLines = airLines;
ViewBag.AirLines2 = airLines;
ViewBag.AirLines3 = airLines;
all the ViewBag
properties are referring to the same list. A change to the list will therefore reflect when referencing any of the properties. When you call the PopulateAirlines
() method you're creating 3 different lists.
You'll need make three separate lists but only create the first using the PopulateAirlines
method and then clone it twice. That will not be as expensive.