O item ViewData que possui a chave 'XXX' é do tipo 'System.Int32', mas deve ser do tipo 'IEnumerable <SelectListItem>'

113

Eu tenho o seguinte modelo de visão

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

e o seguinte método de controlador para criar um novo projeto e atribuir um 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
}

e na 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" />
}

A visualização é exibida corretamente, mas ao enviar o formulário, recebo a seguinte mensagem de erro

InvalidOperationException: O item ViewData que possui a chave 'CategoryID' é do tipo 'System.Int32', mas deve ser do tipo 'IEnumerable <SelectListItem>'.

O mesmo erro ocorre usando o @Html.DropDownList()método, e se eu passar na SelectList usando um ViewBagou ViewData.


fonte

Respostas:

109

O erro significa que o valor de CategoryList é nulo (e, como resultado, o DropDownListFor()método espera que o primeiro parâmetro seja do tipo IEnumerable<SelectListItem>).

Você não está gerando uma entrada para cada propriedade de cada SelectListItemem CategoryList(e nem deveria), portanto, nenhum valor para o SelectListé postado no método do controlador e, portanto, o valor de model.CategoryListno método POST é null. Se você retornar a visualização, deverá primeiro reatribuir o valor de CategoryList, assim como fez no 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 o funcionamento interno (o código-fonte pode ser visto aqui )

Cada sobrecarga de DropDownList()e, DropDownListFor()eventualmente, chama o seguinte 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 verifica se o selectList(o segundo parâmetro de @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;
}

que por sua vez chama

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

que avalia o primeiro parâmetro de @Html.DropDownListFor()(neste 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>"));
}

Como a propriedade CategoryIDé typeof int, ela não pode ser lançada IEnumerable<SelectListItem>e a exceção é lançada (que é definida no MvcResources.resxarquivo 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>
user3559349
fonte
8
@Shyju, Sim, eu perguntei e respondi (como wiki da comunidade) puramente com o propósito de enganar muitas outras perguntas semelhantes no SO que permanecem sem resposta ou não aceitas. Mas vejo que os eleitores da vingança já começaram - o primeiro foi menos de 2 segundos após a postagem - não há tempo suficiente para sequer ler, muito menos a resposta.
1
Entendo. Existem centenas de perguntas com o mesmo problema. Normalmente as pessoas que fazem essas perguntas não fazem uma pesquisa adequada (ou copiaram e colaram uma resposta existente palavra por palavra, mas não funcionou!) Portanto, não tenho certeza se isso pode realmente ajudar. :) Bem escrito BTW.
Shyju
@Stephen, este não é o jeito certo que você está perguntando e você está respondendo
Dilip Oganiya
8
@DilipN, o que você quer dizer com não da maneira certa ? É realmente incentivado no SO. Você deve ler isso e passar algum tempo no meta.
4
@DilipN, porque vou usá-lo para marcar várias perguntas semelhantes como duplicatas que não foram respondidas ou foram respondidas, mas não foram aceitas para que possam ser fechadas (e outros não percam seu tempo). Eu também fiz um wiki da comunidade para que qualquer pessoa possa editá-lo e melhorá-lo com o tempo.
6

de acordo com stephens (usuário3559349) resposta , isso pode ser útil:

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

ou no ProjectVM:

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

Provavelmente causou algum tipo de erro ao redirecionar para sua página e você não inicializar as listas suspensas de seu modelo novamente.

Certifique-se de inicializar seus menus suspensos no construtor do modelo ou todas as vezes antes de enviar o referido modelo para a página.

Caso contrário, você precisará manter o estado das listas suspensas por meio do view bag ou por meio dos auxiliares de valores ocultos.

Gavin Rotty
fonte
0

Eu tive o mesmo problema, estava recebendo um ModelState inválido quando tentei postar o formulário. Para mim, isso foi causado pela configuração de CategoryId como int, quando mudei para string, o ModelState era válido e o método Create funcionou conforme o esperado.

Ardmark
fonte
0

OK, a resposta pronta do pôster explicou perfeitamente por que o erro ocorreu, mas não como fazê-lo funcionar. Não tenho certeza se isso é realmente uma resposta, mas me apontou na direção certa.

Encontrei o mesmo problema e encontrei uma maneira engenhosa de resolvê-lo. Vou tentar capturar isso aqui. Isenção de responsabilidade - trabalho em páginas da web uma vez por ano ou mais e realmente não sei o que estou fazendo na maioria das vezes. Esta resposta não deve de forma alguma ser considerada uma resposta de "especialista", mas faz o trabalho com pouco trabalho ...

Visto que tenho algum objeto de dados (provavelmente um objeto de transferência de dados), desejo usar uma lista suspensa para fornecer valores válidos para um campo, como:

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

Então o ViewModel se parece com isto:

public class MyDataObjectVM
{
  public int id;

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

O verdadeiro problema aqui, como @Stephen tão eloquentemente descrito acima, é que a lista de seleção não é preenchida no método POST no controlador. Portanto, seus métodos de controle seriam assim:

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

Aí está. Este código NÃO está funcionando, eu copiei / colei e editei para torná-lo simples, mas você entendeu. Se os membros de dados no modelo de dados original e no modelo de exibição derivado têm o mesmo nome, UpdateModel () faz um trabalho incrível de preencher apenas os dados certos para você a partir dos valores de FormCollection.

Estou postando isso aqui para poder encontrar a resposta quando inevitavelmente me deparar com este problema novamente - espero que ajude outra pessoa também.

DaveN59
fonte
0

No meu caso, o primeiro ID na minha lista era zero, uma vez que mudei o ID para começar de 1, funcionou.

JayKayOf4
fonte