具有键“XXX”的 ViewData 项的类型为“System.Int32”,但必须是“IEnumerable<SelectListItem>”类型。

141

我有以下视图模型

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: 具有键“CategoryID”的 ViewData 项的类型为“System.Int32”,但必须是“IEnumerable<SelectListItem>”类型。

使用 @Html.DropDownList() 方法或者使用 ViewBagViewData 传递 SelectList 都会出现同样的错误。

6个回答

137

这个错误意味着CategoryList的值为null(因此DropDownListFor()方法期望第一个参数是IEnumerable<SelectListItem>类型)。

您不应该为CategoryList中每个SelectListItem的每个属性生成输入(也不应该),因此没有SelectList的值被提交到控制器方法,因此POST方法中model.CategoryList的值为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)

检查 @Html.DropDownListFor() 的第二个参数 selectList 是否为 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 的类型为 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>

9
@Shyju,是的,我提出并回答了这个问题(作为社区维基),纯粹是为了在SO上打击许多类似的未回答或未被接受的问题。但是,我发现报复性投票者已经开始了——第一个出现在发布后不到2秒钟——甚至没有足够的时间来阅读问题和答案。 - user3559349
1
我明白了。有数百个问题都有相同的问题。通常那些提问的人没有进行适当的搜索(或者他们复制并粘贴了一个现有的答案,但是并没有起作用!)所以我不确定这可能真的有帮助。 :) 顺便说一句,写得很好。 - Shyju
10
@DilipN,你说的“not the right way”是什么意思?这在SO上实际上是受到鼓励的。你应该阅读 这篇文章 并花些时间在 meta 上了解一下。 - user3559349
6
@DilipN,我将使用它来标记许多类似的问题作为重复问题,这些问题要么没有得到答案,要么已经得到答案但未被采纳,以便它们可以被关闭(这样其他人就不会浪费时间了)。我还将其设为社区维基,以便任何人在后期都可以编辑和改进它。 - user3559349
我遇到了相同的问题,但我的 selectList 在 EditorTemplate 中,我也重新加载了它,但仍然出现相同的错误,如何将模型传递给 EditorTemplate? - LearningPal
显示剩余4条评论

7

根据stephens(user3559349)的回答,这可能会有用:

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

或者在ProjectVM中:

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

2

很可能是由于某种错误重定向到您的页面并且您没有重新初始化模型的下拉列表。

请确保在模型的构造函数中或每次将该模型发送到页面之前初始化下拉列表。

否则,您需要通过视图包或隐藏值助手来维护下拉列表的状态。

"Original Answer"翻译成"最初的回答"。


1

好的,这位海报的标准答案清楚地解释了错误发生的原因,但没有说明如何让它工作。我不确定这是否真正是一个答案,但它确实指引了我正确的方向。

我也遇到了同样的问题,并找到了一种巧妙的解决方法。我将在这里尝试捕捉它。免责声明 - 我每年只会处理网页一次,大部分时间都不知道自己在做什么。这个答案绝不应被视为“专家”答案,但可以用很少的工作完成任务...

假设我有一些数据对象(很可能是数据传输对象),我想使用下拉列表来为字段提供有效值,就像这样:

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值中为您填充恰当的数据。

我在这里发布它,以便在我不可避免地再次遇到此问题时可以找到答案--希望它也能帮助别人。


0

我曾经遇到过同样的问题,当我尝试提交表单时,ModelState 无效。对我来说,这是由于将 CategoryId 设置为 int 导致的,当我将其更改为 string 时,ModelState 就变为有效了,并且 Create 方法按预期工作。


0
在我的情况下,列表中的第一个 ID 是零,一旦我将 ID 更改为从 1 开始,它就起作用了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接