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.List开发者_如何学编程BoxFor(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.
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:
The ListBoxFor method will render a
select
element withmultiple="multiple"
attribute, always. It is hard coded inSystem.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...
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 callingSelectInternal
.
Because yourViewData.ModelState
is empty (because there were no validation occurred in your controller before) thedefaultValue
will benull
. ThendefaultValue
gets initialized with your model's default value (your casemodel.IdCurrencyFrom
isint
I guess) so it will be0
. :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
asIEnumbrable
: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
isnull
.Because it expects
defaultValue
to be enumerable and because string is enumerable. If you change the the type ofmodel.IdCurrencyFrom
tostring
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.
精彩评论