Элемент ViewData с ключом «XXX» относится к типу System.Int32, но должен иметь тип IEnumerable <SelectListItem>.

114

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

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.


источник

Ответы:

110

Ошибка означает, что значение CategoryList равно null (и в результате 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>
пользователь3559349
источник
8
@Shyju, Да, я спросил и ответил на него (как на вики сообщества) чисто для того, чтобы обмануть многие другие подобные вопросы по SO, которые остаются без ответа или не принимаются. Но я вижу, что реванш избирателей уже начался - первый был менее чем через 2 секунды после публикации - недостаточно времени, чтобы даже прочитать его, не говоря уже об ответе.
1
Понимаю. Есть 100 вопросов с одной и той же проблемой. Обычно люди, которые задают эти вопросы, не выполняют надлежащего поиска (или они скопировали и вставили существующий ответ слово за словом, но не сработали!) Так что я не уверен, что это действительно может помочь. :) Красиво написано, BTW.
Shyju
@Stephen, это неправильный способ,
ты
8
@DilipN, что значит не правильно ? Это действительно поощряется на SO. Вы должны прочитать это и потратить некоторое время на мета.
4
@DilipN, потому что я буду использовать его, чтобы отмечать многочисленные похожие вопросы как дубликаты, которые либо остались без ответа, либо на них были даны ответы, но они не были приняты, чтобы их можно было закрыть (и чтобы другие не тратили свое время зря). Я также сделал это вики сообщества, чтобы каждый мог со временем редактировать и улучшать ее.
6

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

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

или в ProjectVM:

public class ProjectVM
{
    public ProjectVM()
    {
        CategoryList = new List<SelectListItem>();
    }
    ...
}
Омид-РХ
источник
1

Скорее всего, возникла какая-то ошибка перенаправления на вашу страницу, и вы снова не инициализируете раскрывающиеся списки своей модели.

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

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

Гэвин Ротти
источник
0

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

ardmark
источник
0

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

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

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

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.

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

ДэйвN59
источник
0

В моем случае первый идентификатор в моем списке был нулевым, как только я изменил идентификатор, чтобы начать с 1, это сработало.

JayKayOf4
источник