The ViewData item that has the key 'XXX' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'
Asked Answered
S

6

143

I have the following view model

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

and the following controller method to create a new Project and assign a 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
}

and in the view

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

The view displays correctly but when submitting the form, I get the following error message

InvalidOperationException: The ViewData item that has the key 'CategoryID' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'.

The same error occurs using the @Html.DropDownList() method, and if I pass the SelectList using a ViewBag or ViewData.

Skipton answered 19/12, 2015 at 1:26 Comment(0)
S
139

The error means that the value of CategoryList is null (and as a result the DropDownListFor() method expects that the first parameter is of type IEnumerable<SelectListItem>).

You are not generating an input for each property of each SelectListItem in CategoryList (and nor should you) so no values for the SelectList are posted to the controller method, and therefore the value of model.CategoryList in the POST method is null. If you return the view, you must first reassign the value of CategoryList, just as you did in the GET method.

public ActionResult Create(ProjectVM model)
{
    if (!ModelState.IsValid)
    {
        model.CategoryList = new SelectList(db.Categories, "ID", "Name"); // add this
        return View(model);
    }
    // Save and redirect
}

To explain the inner workings (the source code can be seen here)

Each overload of DropDownList() and DropDownListFor() eventually calls the following method

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

which checks if the selectList (the second parameter of @Html.DropDownListFor()) is 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;
}

which in turn calls

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

which evaluates the the first parameter of @Html.DropDownListFor() (in this case 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>"));
}

Because property CategoryID is typeof int, it cannot be cast to IEnumerable<SelectListItem> and the exception is thrown (which is defined in the MvcResources.resx file 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>
Skipton answered 19/12, 2015 at 1:27 Comment(9)
@Shyju, Yes, I asked and answered it (as community wiki) purely for the purposes of dupe-hammering many other similar question on SO which remain unanswered or unaccepted. But I see the revenge voters have already started - the first one was less than 2 seconds after posting - not enough time to even read it let alone the answer.Skipton
I see. There are 100's of questions with the same problem. Usually the people who ask those questions does not do proper search ( or they copied and pasted an existing answer word by word, but did not work!) So i am not sure this might really help. :) Nicely written BTW.Til
@Stephen this is not right way u are asking and u are answeringBaritone
@DilipN, What do you mean not the right way? Its actually encouraged on SO. You should read this and spend some time on meta.Skipton
Yet if you know answer then why to ask ?Baritone
@DilipN, Because I will use it to mark numerous similar questions as a duplicates which have either been either been left unanswered, or answered but not accepted so they can be closed out (and so others do not waste their time). I have also made it a community wiki so anyone can edit and improve it over time.Skipton
Ok but i am still not getting youBaritone
I am having the same issue but my selectList is in EditorTemplate and I am also loading it again but still the same error , How can pass the model to the EditorTemplate?Cartierbresson
@LearningPal, You have not given enough info but this answer might helpSkipton
C
7

according to stephens (user3559349) answer, this can be useful:

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

or in ProjectVM:

public class ProjectVM
{
    public ProjectVM()
    {
        CategoryList = new List<SelectListItem>();
    }
    ...
}
Calf answered 25/11, 2018 at 12:59 Comment(0)
C
2

Most Likely Caused some sort of error redirecting to your page and you not initializing your model's drop down lists again.

Make sure that you initialize your drop downs in either the model's constructor or every time before you send said model to the page.

Otherwise you will need to maintain the state of the drop down lists either through the view bag or through the hidden value helpers.

Counts answered 20/5, 2019 at 18:52 Comment(0)
S
1

OK, the poster's canned answer neatly explained why the error occurred, but not how to get it to work. I'm not sure that's really an answer, but it did point me in the right direction.

I ran into the same issue and found a slick way to resolve it. I'll try to capture that here. Disclaimer - I work on web pages once a year or so and really don't know what I'm doing most of the time. This answer should in no way be considered an "expert" answer, but it does the job with little work...

Given that I have some data object (most likely a Data Transfer Object) that I want to use a drop-down list to supply valid values for a field, like so:

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

Then the ViewModel looks like this:

public class MyDataObjectVM
{
  public int id;

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

The real problem here, as @Stephen so eloquently described above, is the select list isn't populated on the POST method in the controller. So your controller methods would look like this:

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

There you have it. This is NOT working code, I copy/pasted and edited to make it simple, but you get the idea. If the data members in both the original data model and the derived view model have the same name, UpdateModel() does an awesome job of filling in just the right data for you from the FormCollection values.

I'm posting this here so I can find the answer when I inevitably run into this issue again -- hopefully it will help someone else out as well.

Stercoraceous answered 4/6, 2019 at 21:44 Comment(0)
A
0

I had the same problem, I was getting an invalid ModelState when I tried to post the form. For me, this was caused by setting CategoryId to int, when I changed it to string the ModelState was valid and the Create method worked as expected.

Auria answered 15/11, 2018 at 14:2 Comment(0)
B
0

In my case the first ID in my list was zero, once I changed the ID to start from 1, it worked.

Busterbustle answered 18/11, 2019 at 16:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.