ListBox for ArgumentNullException Parameter Name: Source

Setup:

I installed the controller using MvcScaffolding.

For the Model.IdCurrencyFrom property, the scaffolding created the Html.DropDownListFor:

@Html.DropDownListFor(model => model.IdCurrencyFrom, ((IEnumerable<FlatAdmin.Domain.Entities.Currency>)ViewBag.AllCurrencies).Select(option => new SelectListItem { Text = (option == null ? "None" : option.CurrencyName), Value = option.CurrencyId.ToString(), Selected = (Model != null) && (option.CurrencyId == Model.IdCurrencyFrom) }), "Choose...") 

This works great with both new recordings and editing existing ones.

Problem:

There are only 3 currencies: AR $, US $ and GB Β£. So, instead of a drop down list, I need a ListBox.

So, I changed the above:

 @Html.ListBoxFor(model => model.IdCurrencyFrom, ((IEnumerable<FlatAdmin.Domain.Entities.Currency>)ViewBag.AllCurrencies).Select(option => new SelectListItem { Text = (option == null ? "None" : option.CurrencyName), Value = option.CurrencyId.ToString(), Selected = (Model != null) && (option.CurrencyId == Model.IdCurrencyFrom) })) 

Now I get an ArgumentNullException, Parameter name: source, but only when editing an existing record. Creating new records, this works great.

Questions:

What's happening?!

Nothing changed. Return to DropDownListFor and everything works fine. Switching to ListBox (unlike ListBoxFor), and I get an error.

The model is not null (as I said, it works great with DropDownListFor) ... and I ran out of ideas.

+7
source share
1 answer

I checked the source of the HTML helpers, it was a fun exercise.

TL; DR; The problem is that ListBoxFor is for multiple selection and expects an enumerated Model property. The Model property ( model.IdCurrencyFrom ) is not enumerable, so you get an exception.

Here are my findings:

  • The ListBoxFor method will always display a select element with the attribute multiple="multiple" . It is hardcoded in System.Web.Mvc.Html.SelectExtensions

     private static MvcHtmlString ListBoxHelper(HtmlHelper htmlHelper, string name, IEnumerable<SelectListItem> selectList, IDictionary<string, object> htmlAttributes) { return SelectInternal(htmlHelper, null /* optionLabel */, name, selectList, true /* allowMultiple */, htmlAttributes); } 

    So maybe you still don’t want to allow the user multiple currencies ...

  • Your problem starts when this ListBoxHelper tries to get the default value from your model property:

     object defaultValue = (allowMultiple) ? htmlHelper.GetModelStateValue(fullName, typeof(string[])) : htmlHelper.GetModelStateValue(fullName, typeof(string)); 

    It works for DropDownList because it passes false to allowMultiple when calling SelectInternal .
    Since your ViewData.ModelState empty (since there was no validation on your controller defaultValue ), defaultValue will be null . Then, defaultValue initialized to your model's default value (your case model.IdCurrencyFrom is int , I think), so this will be 0 .

     if (!usedViewData) { if (defaultValue == null) { defaultValue = htmlHelper.ViewData.Eval(fullName); } } 

    We are approaching the exception :) Because, since I mentioned that ListBoxFor only supports multiple selection, it tries to treat defaultValue as IEnumbrable :

     IEnumerable defaultValues = (allowMultiple) ? defaultValue as IEnumerable : new[] { defaultValue }; IEnumerable<string> values = from object value in defaultValues select Convert.ToString(value, CultureInfo.CurrentCulture); 

    And the second line is your ArgumentException, because defaultValues is null .

  • Because it expects defaultValue be enumerable and because the string is enumerable. If you change the type of model.IdCurrencyFrom to string , it will work. But, of course, you will have several options in the user interface, but you will get only the first choice in your model.

+6
source

All Articles