Подтвердить что ты не робот

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

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

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.

4b9b3361

Ответ 1

Ошибка означает, что значение 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 as)

<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>

Ответ 2

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

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

или в ProjectVM:

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

Ответ 3

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

Ответ 4

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

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

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

Ответ 5

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

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

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

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()
{
  return new MyDataObjectVM
  {
    int id = 0,
    string 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.

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