键为&ldquo; XXX&rdquo;的ViewData项的类型为&ldquo; System.Int32&rdquo;,但必须类型为&ldquo; IEnumerable&rdquo;<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:拥有键“ CategoryID”的ViewData项的类型为“ System.Int32”,但必须类型为“ IEnumerable <SelectListItem>”。

使用 @Html.DropDownList() 方法会发生相同的错误,如果我使用 ViewBagViewData 传递SelectList,也会发生相同的错误。




Answer 1 user3559349


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

您还没有产生输入用于每个的每个属性 SelectListItemCategoryList (也不应你),以便为任何值 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 ,因此无法将其 MvcResources.resxIEnumerable<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


最有可能导致某种错误重定向到你的页面,你没有再次初始化你的模型的下拉列表。

确保你在模型的构造函数中或每次发送模型到页面之前,都要初始化你的下拉菜单。

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




Answer 4 ardmark


我也有同样的问题,当我试图发布表单时,我得到一个无效的ModelState。对我来说,这是由于将CategoryId设置为int造成的,当我将其改为字符串时,ModelState是有效的,而Create方法也能正常工作。




Answer 5 DaveN59


OK,海报的罐头整齐地回答解释为什么发生错误,但不知道如何得到它的工作。我不确定这是否是真正的答案,但这确实为我指明了正确的方向。

我也遇到了同样的问题,找到了一个巧妙的方法来解决。我在这里会尝试着捕捉到这个问题。免责声明----我每年都会在网页上工作一次,大多数时候真的不知道自己在做什么。这个答案决不应该被认为是 "专家 "的答案,但它能用很少的工作量完成工作.....

鉴于我有一些数据对象(很可能是一个数据传输对象),我想用下拉列表为一个字段提供有效的值,就像这样。

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值中为你填入合适的数据。

我把这个贴在这里,这样当我不可避免地再次遇到这个问题时,我就能找到答案----希望它也能帮助到其他人。