L'élément ViewData qui a la clé 'XXX' est de type 'System.Int32' mais doit être de type 'IEnumerable <SelectListItem>'

113

J'ai le modèle de vue suivant

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

et la méthode de contrôleur suivante pour créer un nouveau projet et attribuer un 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
}

et dans la vue

@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" />
}

La vue s'affiche correctement mais lors de la soumission du formulaire, j'obtiens le message d'erreur suivant

InvalidOperationException: l'élément ViewData qui a la clé 'CategoryID' est de type 'System.Int32' mais doit être de type 'IEnumerable <SelectListItem>'.

La même erreur se produit en utilisant la @Html.DropDownList()méthode, et si je passe la SelectList en utilisant un ViewBagou ViewData.


la source

Réponses:

109

L'erreur signifie que la valeur de CategoryList est nulle (et par conséquent, la DropDownListFor()méthode s'attend à ce que le premier paramètre soit de type IEnumerable<SelectListItem>).

Vous ne générez pas d'entrée pour chaque propriété de chaque SelectListItemdans CategoryList(et vous ne devriez pas non plus) donc aucune valeur pour le SelectListn'est publiée dans la méthode du contrôleur, et par conséquent la valeur de model.CategoryListdans la méthode POST est null. Si vous renvoyez la vue, vous devez d'abord réaffecter la valeur de CategoryList, comme vous l'avez fait dans la méthode 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
}

Pour expliquer le fonctionnement interne (le code source peut être vu ici )

Chaque surcharge de DropDownList()et DropDownListFor()appelle finalement la méthode suivante

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

qui vérifie si le selectList(le deuxième paramètre de @Html.DropDownListFor()) estnull

// 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;
}

qui à son tour appelle

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

qui évalue le premier paramètre de @Html.DropDownListFor()(dans ce cas 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>"));
}

Étant donné que la propriété CategoryIDest de typeof int, elle ne peut pas être convertie en IEnumerable<SelectListItem>et l'exception est levée (qui est définie dans le MvcResources.resxfichier comme)

<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>
user3559349
la source
8
@Shyju, Oui, je l'ai posé et répondu (en tant que wiki de la communauté) uniquement dans le but de dupe-marteler de nombreuses autres questions similaires sur SO qui restent sans réponse ou non acceptées. Mais je vois que les électeurs de vengeance ont déjà commencé - le premier était moins de 2 secondes après la publication - pas assez de temps pour même le lire et encore moins la réponse.
1
Je vois. Il y a des centaines de questions avec le même problème. Habituellement, les personnes qui posent ces questions ne font pas une recherche appropriée (ou elles ont copié et collé une réponse existante mot par mot, mais cela n'a pas fonctionné!). Je ne suis donc pas sûr que cela puisse vraiment aider. :) Bien écrit BTW.
Shyju
@Stephen ce n'est pas la bonne façon que vous demandez et vous répondez
Dilip Oganiya
8
@DilipN, que voulez-vous dire pas la bonne façon ? C'est en fait encouragé sur SO. Vous devriez lire ceci et passer du temps sur meta.
4
@DilipN, Parce que je vais l'utiliser pour marquer de nombreuses questions similaires comme des doublons qui ont soit été laissés sans réponse, soit répondu mais non acceptés afin qu'ils puissent être fermés (et que les autres ne perdent pas leur temps). J'en ai également fait un wiki communautaire pour que tout le monde puisse le modifier et l'améliorer au fil du temps.
6

selon la réponse de stephens (user3559349) , cela peut être utile:

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

ou dans ProjectVM:

public class ProjectVM
{
    public ProjectVM()
    {
        CategoryList = new List<SelectListItem>();
    }
    ...
}
Omid-RH
la source
1

Le plus probable a causé une sorte d'erreur de redirection vers votre page et vous ne réinitialisez pas les listes déroulantes de votre modèle.

Assurez-vous d'initialiser vos listes déroulantes dans le constructeur du modèle ou à chaque fois avant d'envoyer ledit modèle à la page.

Sinon, vous devrez maintenir l'état des listes déroulantes via le sac de vue ou via les assistants de valeur masqués.

Gavin Rotty
la source
0

J'ai eu le même problème, j'obtenais un ModelState invalide lorsque j'ai essayé de publier le formulaire. Pour moi, cela a été causé par la définition de CategoryId sur int, lorsque je l'ai changé en string, ModelState était valide et la méthode Create fonctionnait comme prévu.

ardmark
la source
0

OK, la réponse standardisée de l'affiche expliquait parfaitement pourquoi l'erreur s'est produite, mais pas comment la faire fonctionner. Je ne suis pas sûr que ce soit vraiment une réponse, mais cela m'a orienté dans la bonne direction.

J'ai rencontré le même problème et j'ai trouvé un moyen astucieux de le résoudre. Je vais essayer de capturer cela ici. Avertissement - Je travaille sur des pages Web une fois par an environ et je ne sais vraiment pas ce que je fais la plupart du temps. Cette réponse ne doit en aucun cas être considérée comme une réponse "experte", mais elle fait le travail avec peu de travail ...

Étant donné que j'ai un objet de données (très probablement un objet de transfert de données) que je souhaite utiliser une liste déroulante pour fournir des valeurs valides pour un champ, comme ceci:

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

Ensuite, le ViewModel ressemble à ceci:

public class MyDataObjectVM
{
  public int id;

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

Le vrai problème ici, comme @Stephen l'a si éloquemment décrit ci-dessus, est que la liste de sélection n'est pas remplie sur la méthode POST dans le contrôleur. Ainsi, vos méthodes de contrôleur ressembleraient à ceci:

// 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);
  )
}

Voilà. Ce n'est PAS du code fonctionnel, j'ai copié / collé et édité pour le rendre simple, mais vous voyez l'idée. Si les membres de données du modèle de données d'origine et du modèle de vue dérivé ont le même nom, UpdateModel () fait un travail impressionnant en remplissant uniquement les bonnes données pour vous à partir des valeurs FormCollection.

Je poste ceci ici afin que je puisse trouver la réponse lorsque je rencontre inévitablement à nouveau ce problème - j'espère que cela aidera également quelqu'un d'autre.

DaveN59
la source
0

Dans mon cas, le premier identifiant de ma liste était zéro, une fois que j'ai changé l'identifiant pour qu'il commence à 1, cela fonctionnait.

JayKayOf4
la source