El elemento ViewData que tiene la clave 'XXX' es de tipo 'System.Int32' pero debe ser de tipo 'IEnumerable <SelectListItem>'

113

Tengo el siguiente modelo de vista

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

y el siguiente método de controlador para crear un nuevo proyecto y asignar 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
}

y en la vista

@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 vista se muestra correctamente, pero al enviar el formulario, aparece el siguiente mensaje de error

InvalidOperationException: el elemento ViewData que tiene la clave 'CategoryID' es de tipo 'System.Int32' pero debe ser de tipo 'IEnumerable <SelectListItem>'.

Se produce el mismo error con el @Html.DropDownList()método, y si paso la lista de selección con un ViewBago ViewData.


fuente

Respuestas:

109

El error significa que el valor de CategoryList es nulo (y como resultado, el DropDownListFor()método espera que el primer parámetro sea de tipo IEnumerable<SelectListItem>).

No está generando una entrada para cada propiedad de cada SelectListItemen CategoryList(y tampoco debería hacerlo), por lo que no SelectListse publican valores para el método del controlador y, por lo tanto, el valor de model.CategoryListen el método POST es null. Si devuelve la vista, primero debe reasignar el valor de CategoryList, tal como lo hizo en el método 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
}

Para explicar el funcionamiento interno (el código fuente se puede ver aquí )

Cada sobrecarga DropDownList()y DropDownListFor()eventualmente llama al siguiente método

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

que comprueba si el selectList(el segundo parámetro de @Html.DropDownListFor()) esnull

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

que a su vez llama

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

que evalúa el primer parámetro de @Html.DropDownListFor() (en este caso 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>"));
}

Debido a que la propiedad CategoryIDes typeof int, no se puede convertir IEnumerable<SelectListItem>y se lanza la excepción (que se define en el MvcResources.resxarchivo como)

<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>
usuario3559349
fuente
8
@Shyju, Sí, lo pregunté y respondí (como wiki de la comunidad) simplemente con el propósito de engañar a muchas otras preguntas similares sobre SO que permanecen sin respuesta o no aceptadas. Pero veo que los votantes de la venganza ya han comenzado - el primero fue menos de 2 segundos después de la publicación - no hay tiempo suficiente para siquiera leerlo y mucho menos la respuesta.
1
Veo. Hay cientos de preguntas con el mismo problema. Por lo general, las personas que hacen esas preguntas no realizan una búsqueda adecuada (o copiaron y pegaron una respuesta existente palabra por palabra, ¡pero no funcionó!). Por lo tanto, no estoy seguro de que esto realmente pueda ayudar. :) Bien escrito por cierto.
Shyju
@Stephen, esta no es la forma correcta en la que preguntas y estás respondiendo
Dilip Oganiya
8
@DilipN, ¿Qué quieres decir con que no es la forma correcta ? En realidad, se anima a SO. Deberías leer esto y dedicar algo de tiempo a meta.
4
@DilipN, porque lo usaré para marcar numerosas preguntas similares como duplicadas que han quedado sin respuesta o respondidas pero no aceptadas para que puedan cerrarse (y para que otros no pierdan el tiempo). También lo he convertido en un wiki de la comunidad para que cualquiera pueda editarlo y mejorarlo con el tiempo.
6

de acuerdo con la respuesta de stephens (user3559349) , esto puede ser útil:

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

o en ProjectVM:

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

Lo más probable es que haya causado algún tipo de error al redirigir a su página y no haya inicializado las listas desplegables de su modelo nuevamente.

Asegúrese de inicializar sus menús desplegables en el constructor del modelo o cada vez antes de enviar dicho modelo a la página.

De lo contrario, deberá mantener el estado de las listas desplegables a través de la bolsa de visualización o de los ayudantes de valor ocultos.

Gavin Rotty
fuente
0

Tuve el mismo problema, obtenía un ModelState no válido cuando intenté publicar el formulario. Para mí, esto se debió a establecer CategoryId en int, cuando lo cambié a string, ModelState era válido y el método Create funcionó como se esperaba.

ardmark
fuente
0

Bien, la respuesta predefinida del póster explicaba claramente por qué ocurrió el error, pero no cómo hacer que funcionara. No estoy seguro de que sea realmente una respuesta, pero me indicó la dirección correcta.

Me encontré con el mismo problema y encontré una manera hábil de resolverlo. Intentaré capturar eso aquí. Descargo de responsabilidad: trabajo en páginas web una vez al año y realmente no sé lo que estoy haciendo la mayor parte del tiempo. Esta respuesta de ninguna manera debe considerarse una respuesta de "experto", pero hace el trabajo con poco trabajo ...

Dado que tengo algún objeto de datos (probablemente un objeto de transferencia de datos), quiero usar una lista desplegable para proporcionar valores válidos para un campo, así:

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

Entonces el ViewModel se ve así:

public class MyDataObjectVM
{
  public int id;

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

El verdadero problema aquí, como @Stephen describió tan elocuentemente anteriormente, es que la lista de selección no se completa en el método POST en el controlador. Entonces sus métodos de controlador se verían así:

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

Ahí tienes. Este NO es un código de trabajo, lo copio / pego y edito para hacerlo simple, pero entiendes la idea. Si los miembros de datos tanto en el modelo de datos original como en el modelo de vista derivado tienen el mismo nombre, UpdateModel () hace un trabajo increíble al completar los datos correctos para usted a partir de los valores de FormCollection.

Estoy publicando esto aquí para poder encontrar la respuesta cuando inevitablemente me vuelva a encontrar con este problema; con suerte, también ayudará a alguien más.

DaveN59
fuente
0

En mi caso, el primer ID en mi lista fue cero, una vez que cambié el ID para comenzar desde 1, funcionó.

JayKayOf4
fuente