Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ListBox for ArgumentNullException Parameter name: source

Setup:

I have scaffolded a controller using MvcScaffolding.

For a property, Model.IdCurrencyFrom, the scaffolding created an 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 fine, both with new records, or editing existing ones.

Problem:

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

So I changed the above to:

@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)
    }))

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

Questions:

What is happening?!

Nothing has changed. Switching back to DropDownListFor and it all works fine. Switching to ListBox (as opposed to ListBoxFor) and I get the error.

The model is not null (like I said, it works fine with the DropDownListFor)... and I've run out of ideas.

like image 987
awrigley Avatar asked Aug 22 '11 09:08

awrigley


1 Answers

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

TL;DR; The problem is that ListBoxFor is for multiple selection and it expects an enumerable Model property. Your Model property (model.IdCurrencyFrom) is not an enumerable that's why you get the exception.

Here are my findings:

  1. The ListBoxFor method will render a select element with multiple="multiple" attribute, always. It is hard coded 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 anyway don't want to allow for the user multiple currencies...

  2. 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.
    Because your ViewData.ModelState is empty (because there were no validation occurred in your controller before) the defaultValue will be null. Then defaultValue gets initialized with your model's default value (your case model.IdCurrencyFrom is int I guess) so it will be 0. :

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

    We are getting close to the exception :) Because as I mentioned ListBoxFor only support multiple selection, so it tries to handle 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 in the second line there is your ArgumentException because defaultValues is null.

  3. Because it expects defaultValue to be enumerable and because string is enumerable. If you change the the type of model.IdCurrencyFrom to string it will work. But of course you will have multiple selection on the UI but you will only get the first selection in your model.

like image 170
nemesv Avatar answered Sep 20 '22 19:09

nemesv