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 l'envoi du formulaire, le message d'erreur suivant s'affiche.
InvalidOperationException: l'élément ViewData ayant la clé 'CategoryID' est de type 'System.Int32' mais doit être de type 'IEnumerable <SelectListItem>'.
La même erreur se produit à l'aide de la méthode @Html.DropDownList()
et si je transmets la liste de sélection à l'aide de ViewBag
ou ViewData
.
L'erreur signifie que la valeur de CategoryList
est null (et que, par conséquent, la méthode DropDownListFor()
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 SelectListItem
dans CategoryList
(et vous ne devriez pas le faire), aucune valeur de SelectList
n'est donc enregistrée dans la méthode du contrôleur. Par conséquent, la valeur de model.CategoryList
dans la méthode POST est null
. Si vous retournez la vue, vous devez d'abord réaffecter la valeur 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
}
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 la selectList
(le deuxième paramètre de @Html.DropDownListFor()
) est 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;
}
qui appelle à son tour
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>"));
}
Comme la propriété CategoryID
est typeof int
, elle ne peut pas être convertie en IEnumerable<SelectListItem>
et l'exception est levée (définie dans le fichier MvcResources.resx
par:)
<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>
selon stephen answer, 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>();
}
...
}
Très probablement Causé une sorte d'erreur de redirection vers votre page et vous n'initialisez pas à nouveau 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.
Dans le cas contraire, vous devrez conserver l'état des listes déroulantes soit via la vue, soit via les aides aux valeurs cachées.
OK, la réponse en conserve de l’affiche a été parfaitement expliquée pourquoi l’erreur est survenue, 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 simple de le résoudre. Je vais essayer de capturer cela ici. Déni de responsabilité - Je travaille sur des pages Web environ une fois par an 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 (probablement un objet de transfert de données), 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 renseignée sur la méthode POST du 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 un code qui fonctionne, j'ai copié/collé et édité pour simplifier les choses, 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ée ont le même nom, UpdateModel () effectue un travail formidable en remplissant les données qui vous conviennent à partir des valeurs de FormCollection.
Je poste ceci ici afin que je puisse trouver la réponse lorsque je me heurterai inévitablement à nouveau à ce problème - j'espère que cela aidera également quelqu'un d'autre.
J'ai eu le même problème, je recevais un ModelState invalide lorsque j'ai essayé de poster le formulaire. Pour moi, cela a été causé par la définition de CategoryId sur int. Lorsque je l'ai modifiée en chaîne, ModelState était valide et la méthode Create fonctionnait comme prévu.