Элемент ViewData с ключом &laquo;XXX&raquo; имеет тип &laquo;System.Int32&raquo;, но должен иметь тип &laquo;IEnumerable&raquo;.<selectlistitem>'</selectlistitem>

c# asp.net-mvc


У меня есть следующая модель вида

public class ProjectVM
{
    ....
    [Display(Name = "Category")]
    [Required(ErrorMessage = "Please select a category")]
    public int CategoryID { get; set; }
    public IEnumerable<SelectListItem> CategoryList { get; set; }
    ....
}

и следующий метод контроллера для создания нового проекта и назначения Category

public ActionResult Create()
{
    ProjectVM model = new ProjectVM
    {
        CategoryList = new SelectList(db.Categories, "ID", "Name")
    }
    return View(model);
}

public ActionResult Create(ProjectVM model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    // Save and redirect
}

и,на вид

@model ProjectVM
....
@using (Html.BeginForm())
{
    ....
    @Html.LabelFor(m => m.CategoryID)
    @Html.DropDownListFor(m => m.CategoryID, Model.CategoryList, "-Please select-")
    @Html.ValidationMessageFor(m => m.CategoryID)
    ....
    <input type="submit" value="Create" />
}

Вид отображается корректно,но при отправке формы я получаю следующее сообщение об ошибке

InvalidOperationException: элемент ViewData, имеющий ключ «CategoryID», имеет тип «System.Int32», но должен иметь тип «IEnumerable <SelectListItem>».

Та же ошибка возникает при использовании метода @Html.DropDownList() , и если я передаю SelectList, используя ViewBag или ViewData .




Answer 1 user3559349


Ошибка означает, что значение CategoryList равно нулю (и в результате метод DropDownListFor() ожидает, что первый параметр имеет тип IEnumerable<SelectListItem> ).

Вы не генерируете входные данные для каждого свойства каждого SelectListItem в CategoryList (и не должны), поэтому никакие значения для SelectList не публикуются в методе контроллера, и поэтому значение model.CategoryList в методе POST равно null . Если вы возвращаете представление, вы должны сначала переназначить значение CategoryList , как вы это делали в методе GET.

public ActionResult Create(ProjectVM model)
{
    if (!ModelState.IsValid)
    {
        model.CategoryList = new SelectList(db.Categories, "ID", "Name"); // add this
        return View(model);
    }
    // Save and redirect
}

Чтобы объяснить внутреннюю работу (исходный код можно увидеть здесь )

Каждая перегрузка DropDownList() и DropDownListFor() конечном итоге вызывает следующий метод

private static MvcHtmlString SelectInternal(this HtmlHelper htmlHelper, ModelMetadata metadata,
  string optionLabel, string name, IEnumerable<SelectListItem> selectList, bool allowMultiple,
  IDictionary<string, object> htmlAttributes)

которая проверяет , является ли selectList (второй параметр @Html.DropDownListFor() ) является null

// If we got a null selectList, try to use ViewData to get the list of items.
if (selectList == null)
{
    selectList = htmlHelper.GetSelectData(name);
    usedViewData = true;
}

что,в свою очередь,вызывает

private static IEnumerable<SelectListItem> GetSelectData(this HtmlHelper htmlHelper, string name)

который оценивает первый параметр @Html.DropDownListFor() (в данном случае CategoryID )

....
o = htmlHelper.ViewData.Eval(name);
....
IEnumerable<SelectListItem> selectList = o as IEnumerable<SelectListItem>;
if (selectList == null)
{
    throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, 
        MvcResources.HtmlHelper_WrongSelectDataType,
        name, o.GetType().FullName, "IEnumerable<SelectListItem>"));
}

Поскольку свойство CategoryID является typeof int , оно не может быть приведено к IEnumerable<SelectListItem> и генерируется исключение (которое определено в файле MvcResources.resx как)

<data name="HtmlHelper_WrongSelectDataType" xml:space="preserve">
    <value>The ViewData item that has the key '{0}' is of type '{1}' but must be of type '{2}'.</value>
</data>



Answer 2 Omid-RH


согласно Stephens (user3559349) ответ , это может быть полезно:

@Html.DropDownListFor(m => m.CategoryID, Model.CategoryList ?? new List<SelectListItem>(), "-Please select-")

или в ProjectVM:

public class ProjectVM
{
    public ProjectVM()
    {
        CategoryList = new List<SelectListItem>();
    }
    ...
}



Answer 3 Gavin Rotty


Mostly Likely Причинила некоторую ошибку при перенаправлении на вашу страницу,и вы больше не инициализируете выпадающие списки вашей модели.

Убедитесь,что вы инициализируете падения либо в конструкторе модели,либо каждый раз,прежде чем отправлять указанную модель на страницу.

В противном случае вам придется поддерживать состояние выпадающих списков либо через пакет просмотра,либо через помощники скрытых значений.




Answer 4 ardmark


У меня была та же проблема,я получал недействительное ModelState,когда пытался разместить форму.Для меня это было вызвано установкой CategoryId в int,когда я изменил его на строку,ModelState был действителен,а метод Create работал,как и ожидалось.




Answer 5 DaveN59


Хорошо, стандартный ответ автора аккуратно объяснил, почему произошла ошибка, а не как заставить ее работать. Я не уверен, что это действительно ответ, но он направил меня в правильном направлении.

Я столкнулся с той же проблемой и нашел хитрый способ ее решения.Я попробую запечатлеть это здесь.Отказ от ответственности-я работаю на веб-страницах раз в год или около того и действительно не знаю,что я делаю большую часть времени.Этот ответ ни в коем случае не должен считаться "экспертным" ответом,но он делает работу с небольшим объемом работы....

Учитывая,что у меня есть какой-то объект данных (скорее всего,объект передачи данных),я хочу использовать выпадающий список,чтобы предоставить действительные значения для поля,например,так:

public class MyDataObject
{
  public int id;
  public string StrValue;
}

Тогда ViewModel выглядит вот так:

public class MyDataObjectVM
{
  public int id;

  public string StrValue;
  public List<SectListItem> strValues;
}

Настоящая проблема здесь,как и в @Stephen,так красноречиво описанном выше,заключается в том,что список выбора не заполняется на POST-методе в контроллере.Поэтому ваши методы контроллера будут выглядеть так:

// GET
public ActionResult Create()
{
  var dataObjectVM = GetNewMyDataObjectVM();
  return View(dataObjectVM); // I use T4MVC, don't you?
}

private MyDataObjectVM GetNewMyDataObjectVM(MyDataObjectVM model = null)
{
  return new MyDataObjectVM
  {
    int id = model?.Id ?? 0,
    string StrValue = model?.StrValue ?? "", 
    var strValues = new List<SelectListItem> 
      { 
        new SelectListItem {Text = "Select", Value = ""},
        new SelectListITem {Text = "Item1", Value = "Item1"},
        new SelectListItem {Text = "Item2", Value = "Item2"}
      };
  };
}

// POST
public ActionResult Create(FormCollection formValues)
{
  var dataObject = new MyDataObject();

  try
  {
    UpdateModel(dataObject, formValues);
    AddObjectToObjectStore(dataObject);

    return RedirectToAction(Actions.Index);
  }
  catch (Exception ex)
  {
    // fill in the drop-down list for the view model
    var dataObjectVM = GetNewMyDataObjectVM();
    ModelState.AddModelError("", ex.Message);

    return View(dataObjectVM);
  )
}

Вот оно.Это не рабочий код,я скопировал/распастил и отредактировал его,чтобы сделать простым,но вы поняли идею.Если члены данных как в исходной модели данных,так и в производной модели представления имеют одно и то же имя,UpdateModel()делает потрясающую работу по заполнению как раз тех данных,которые вам нужны из значений FormCollection.

Я размещаю это здесь,чтобы найти ответ,когда я неизбежно столкнусь с этой проблемой снова-надеюсь,это поможет и кому-то другому.