Element ViewData, który ma klucz „XXX”, jest typu „System.Int32”, ale musi być typu „IEnumerable <SelectListItem>”

114

Mam następujący model widoku

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

i następującą metodę kontrolera, aby utworzyć nowy projekt i przypisać plik 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
}

i w widoku

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

Widok wyświetla się poprawnie, ale podczas przesyłania formularza pojawia się następujący komunikat o błędzie

InvalidOperationException: Element ViewData, który ma klucz „CategoryID”, jest typu „System.Int32”, ale musi być typu „IEnumerable <SelectListItem>”.

Ten sam błąd występuje przy użyciu @Html.DropDownList()metody i jeśli przekażę SelectList za pomocą ViewBaglub ViewData.


źródło

Odpowiedzi:

110

Błąd oznacza, że ​​wartość CategoryList jest null (w wyniku czego DropDownListFor()metoda oczekuje, że pierwszy parametr jest typu IEnumerable<SelectListItem>).

Nie generujesz danych wejściowych dla każdej właściwości każdej SelectListItemz CategoryList(i nie powinieneś), więc żadne wartości dla metody nie SelectListsą wysyłane do metody kontrolera, a zatem wartość model.CategoryListw metodzie POST to null. Jeśli zwrócisz widok, musisz najpierw ponownie przypisać wartość CategoryList, tak jak w przypadku metody 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
}

Aby wyjaśnić wewnętrzne działanie (kod źródłowy można zobaczyć tutaj )

Każde przeciążenie DropDownList()i DropDownListFor()ostatecznie wywołuje następującą metodę

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

który sprawdza, czy selectList(drugi parametr @Html.DropDownListFor()) jestnull

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

co z kolei woła

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

który ocenia pierwszy parametr @Html.DropDownListFor()(w tym przypadku 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>"));
}

Ponieważ właściwość CategoryIDjest typeof int, nie można jej rzutować na IEnumerable<SelectListItem>i zgłaszany jest wyjątek (który jest zdefiniowany w MvcResources.resxpliku jako)

<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
źródło
8
@Shyju, Tak, zapytałem i odpowiedziałem (jako wiki społeczności) wyłącznie w celu oszukania wielu innych podobnych pytań na SO, które pozostają bez odpowiedzi lub nie zostały zaakceptowane. Ale widzę, że wyborcy zemsty już się rozpoczęli - pierwszy był mniej niż 2 sekundy po wysłaniu - za mało czasu, aby w ogóle go przeczytać, nie mówiąc już o odpowiedzi.
1
Widzę. Istnieją setki pytań z tym samym problemem. Zwykle osoby, które zadają te pytania, nie wyszukują prawidłowo (lub skopiowały i wkleiły istniejącą odpowiedź słowo po słowie, ale nie zadziałało!). Więc nie jestem pewien, czy to może naprawdę pomóc. :) Przy okazji ładnie napisane.
Shyju
@Stephen to nie jest właściwy sposób, w jaki pytasz i odpowiadasz
Dilip Oganiya
8
@DilipN, co masz na myśli, mówiąc niewłaściwy sposób ? Jego faktycznie zachęca na SO. Powinieneś to przeczytać i poświęcić trochę czasu na meta.
4
@DilipN, Ponieważ użyję go do oznaczenia wielu podobnych pytań jako duplikatów, na które albo pozostawiono bez odpowiedzi, albo udzielono odpowiedzi, ale nie zaakceptowano, aby można je było zamknąć (aby inni nie marnowali czasu). Stworzyłem również wiki społeczności, aby każdy mógł go edytować i ulepszać z czasem.
6

według Stephens (user3559349) odpowiedzi , to może być użyteczne:

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

lub w ProjectVM:

public class ProjectVM
{
    public ProjectVM()
    {
        CategoryList = new List<SelectListItem>();
    }
    ...
}
Omid-RH
źródło
1

Najprawdopodobniej spowodowało jakiś błąd podczas przekierowywania na twoją stronę i nie inicjalizowałeś ponownie rozwijanych list modelu.

Upewnij się, że zainicjowałeś swoje listy rozwijane w konstruktorze modelu lub za każdym razem przed wysłaniem tego modelu na stronę.

W przeciwnym razie będziesz musiał utrzymywać stan list rozwijanych za pomocą worka widoku lub pomocników ukrytych wartości.

Gavin Rotty
źródło
0

Miałem ten sam problem, otrzymywałem nieprawidłowy ModelState, kiedy próbowałem wysłać formularz. Dla mnie było to spowodowane ustawieniem CategoryId na int, kiedy zmieniłem go na ciąg, modelState był prawidłowy, a metoda Create działała zgodnie z oczekiwaniami.

znak zapałki
źródło
0

OK, gotowa odpowiedź nadawcy dokładnie wyjaśniła, dlaczego wystąpił błąd, ale nie wyjaśniła , jak go uruchomić. Nie jestem pewien, czy to naprawdę odpowiedź, ale wskazało mi to właściwy kierunek.

Napotkałem ten sam problem i znalazłem sprytny sposób, aby go rozwiązać. Spróbuję to tutaj uchwycić. Zastrzeżenie - pracuję nad stronami internetowymi mniej więcej raz w roku i naprawdę nie wiem, co robię przez większość czasu. Tej odpowiedzi nie należy w żaden sposób uważać za odpowiedź „eksperta”, ale spełnia ona swoje zadanie przy niewielkim nakładzie pracy ...

Biorąc pod uwagę, że mam obiekt danych (najprawdopodobniej obiekt transferu danych), którego chcę użyć listy rozwijanej, aby podać prawidłowe wartości dla pola, na przykład:

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

Wtedy ViewModel wygląda następująco:

public class MyDataObjectVM
{
  public int id;

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

Prawdziwym problemem tutaj, tak elokwentnie opisanym powyżej @Stephen, jest to, że lista wyboru nie jest wypełniana w metodzie POST w kontrolerze. Więc twoje metody kontrolera wyglądałyby tak:

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

Masz to. To NIE działa kod, kopiowałem / wklejałem i edytowałem, aby było to proste, ale masz pomysł. Jeśli elementy członkowskie danych zarówno w oryginalnym modelu danych, jak i w wyprowadzonym modelu widoku mają tę samą nazwę, funkcja UpdateModel () wykonuje niesamowitą robotę, wypełniając tylko odpowiednie dane z wartości FormCollection.

Publikuję to tutaj, aby móc znaleźć odpowiedź, gdy nieuchronnie ponownie napotkam ten problem - mam nadzieję, że pomoże to również komuś innemu.

DaveN59
źródło
0

W moim przypadku pierwszy identyfikator na mojej liście był zerowy, kiedy zmieniłem identyfikator na 1, zadziałało.

JayKayOf4
źródło