ASP.NET MVC Html.DropDownList SelectedValue

103

Próbowałem, to jest RC1, a następnie zaktualizowałem do RC2, co nie rozwiązało problemu.

// in my controller
ViewData["UserId"] = new SelectList(
    users, 
    "UserId", 
    "DisplayName", 
    selectedUserId.Value); // this has a value

wynik: właściwość SelectedValue jest ustawiana w obiekcie

// in my view
<%=Html.DropDownList("UserId", (SelectList)ViewData["UserId"])%>

wynik: wszystkie oczekiwane opcje są renderowane dla klienta, ale wybrany atrybut nie jest ustawiony. Element w SelectedValue istnieje na liście, ale pierwsza pozycja na liście jest zawsze domyślnie zaznaczona.

Jak mam to robić?

Aktualizacja Dzięki odpowiedzi Johna Feminelli dowiedziałem się, na czym polega problem. „UserId” to właściwość w modelu, do którego jest silnie wpisany mój widok. Gdy Html.DropDownList („UserId” zostanie zmieniona na dowolną inną nazwę oprócz „UserId”, wybrana wartość jest wyświetlana poprawnie.

Powoduje to jednak, że wartość nie jest związana z modelem.

blu
źródło
Czy jesteś pewien, że wartość znajduje się na liście?
John Sheehan
Problem nadal występuje w wersji 1 programu ASP.NET MVC
Mathias F
Co to jest selectedUserId !?
fulvio,
Jak przypuszczasz, aby powiązać wartość podczas aktualizacji, jeśli nazwa twojego wejścia nie jest taka sama jak twoja właściwość?
ryudice

Odpowiedzi:

68

Oto jak naprawiłem ten problem:

Miałem:

Kontroler:

ViewData["DealerTypes"] = Helper.SetSelectedValue(listOfValues, selectedValue) ;

Widok

<%=Html.DropDownList("DealerTypes", ViewData["DealerTypes"] as SelectList)%>

Zmienione przez:

Widok

<%=Html.DropDownList("DealerTypesDD", ViewData["DealerTypes"] as SelectList)%>

Wygląda na to, że DropDown nie może mieć tej samej nazwy, ma nazwę ViewData: S dziwne, ale zadziałało.

Sanchitos
źródło
19
+1 dla „DropDown nie może mieć tej samej nazwy, ma nazwę ViewData” Wielkie dzięki
Daniel Dyson
Dziękuję Ci!!! Właśnie rozwiązałeś mój problem po wielu godzinach zastanawiania się, dlaczego to nie zadziałało :)
Jen
1
Działa to dla wybranej wartości, ale podczas próby zaktualizowania „DealerTypes” w bazie danych pojawia się problem, ponieważ to DropDownList jest teraz powiązany z „DealerTypesDD”, który nie istnieje w modelu. Rozwiązaniem jest dodanie ukrytego pola i htmlAttributes do DropDownList: <input type = "hidden" name = "DealerTypes" id = "DealerTypes" value = "" /> <% = Html.DropDownList ("DealerTypesDD", ViewData [ "DealerTypes"] jako SelectList, nowe {@onchange = "DealerTypes.value = this.value"})%>
Zim,
3
Lepiej jest dodać „DS” do nazwy danych widoku, nie psując w ten sposób wiązania modelu ...
noocyte
1
Co do cholery! Nie jest to naprawione w MVC5 ??? Jako kolejna sztuczka wspomniana przez @Paul Hatcher, skopiuj wybrany identyfikator do ViewData ["DealerTypes"].
jsgoupil
51

Spróbuj tego:

public class Person {
    public int Id { get; set; }
    public string Name { get; set; }
}

I wtedy:

var list = new[] {   
    new Person { Id = 1, Name = "Name1" }, 
    new Person { Id = 2, Name = "Name2" }, 
    new Person { Id = 3, Name = "Name3" } 
};

var selectList = new SelectList(list, "Id", "Name", 2);
ViewData["People"] = selectList;

Html.DropDownList("PeopleClass", (SelectList)ViewData["People"])

Dzięki MVC RC2 otrzymuję:

<select id="PeopleClass" name="PeopleClass">
    <option value="1">Name1</option>
    <option selected="selected" value="2">Name2</option>
    <option value="3">Name3</option>
</select>
John Feminella
źródło
Było to pomocne w wyśledzeniu przyczyny. Zaktualizowałem moje pytanie, aby odzwierciedlić dodatki.
blu
jest pierwszy w modelu? drugi w kontrolerze? a trzeci na widoku to jasne, ale 2 pierwsze .. ???
rr
Dobra odpowiedź, ale czy nie lepiej jest użyć ViewModel?
Jess
@Jess: Napisałem tę odpowiedź w marcu 2009 roku, ponad 5 lat temu. Czasy się zmieniły! Zapraszam do aktualizacji. :)
John Feminella,
5

Nadal możesz nazwać DropDown jako „UserId” i nadal mieć powiązanie modelu działające poprawnie.

Jedynym wymaganiem, aby to zadziałało, jest to, że klucz ViewData zawierający SelectList nie ma takiej samej nazwy jak właściwość Model, którą chcesz powiązać. W twoim konkretnym przypadku byłoby to:

// in my controller
ViewData["Users"] = new SelectList(
    users, 
    "UserId", 
    "DisplayName", 
    selectedUserId.Value); // this has a value

// in my view
<%=Html.DropDownList("UserId", (SelectList)ViewData["Users"])%>

Spowoduje to utworzenie elementu select o nazwie UserId, który ma taką samą nazwę jak właściwość UserId w modelu, a zatem spinacz modelu ustawi go na wartość wybraną w elemencie selekcji HTML wygenerowanym przez pomocnika Html.DropDownList.

Nie jestem pewien, dlaczego ten konkretny konstruktor Html.DropDownList nie wybierze wartości określonej w SelectList po umieszczeniu listy wyboru w ViewData z kluczem równym nazwie właściwości. Podejrzewam, że ma to coś wspólnego ze sposobem, w jaki pomocnik DropDownList jest używany w innych scenariuszach, w których konwencja jest taka, że ​​masz SelectList w ViewData o tej samej nazwie, co właściwość w Twoim modelu. To zadziała poprawnie:

// in my controller
ViewData["UserId"] = new SelectList(
    users, 
    "UserId", 
    "DisplayName", 
    selectedUserId.Value); // this has a value

// in my view
<%=Html.DropDownList("UserId")%>
Rui
źródło
Wielkie dzięki kolego, przede wszystkim odpowiedzi to zadziałało dla mnie, nie wiem dlaczego, ale zadziałało
Lamin Sanneh
Wreszcie! Po godzinach prób w końcu znalazłem twoje „jedyne wymaganie”. Zrobiło to.
SteveCav
2

Jeśli uważamy, że nie jest to błąd, który zespół powinien naprawić, przynajmniej MSDN powinno poprawić dokument. Zagmatwanie naprawdę pochodzi z kiepskiego dokumentu tego. W MSDN wyjaśnia nazwę parametrów jako,

Type: System.String
The name of the form field to return.

Oznacza to po prostu, że ostateczny wygenerowany kod HTML będzie używał tego parametru jako nazwy wybranego wejścia. Ale to w rzeczywistości oznacza coś więcej.

Myślę, że projektant zakłada, że ​​użytkownik użyje modelu widoku do wyświetlenia listy rozwijanej, a także użyje postu z powrotem do tego samego modelu widoku. Ale w wielu przypadkach tak naprawdę nie kierujemy się tym założeniem.

Skorzystaj z powyższego przykładu,

public class Person {
    public int Id { get; set; }
    public string Name { get; set; }
}

Jeśli będziemy postępować zgodnie z założeniem, powinniśmy zdefiniować model widoku dla tego widoku powiązanego z rozwijaną listą

public class PersonsSelectViewModel{
    public string SelectedPersonId,
    public List<SelectListItem> Persons;
}

Ponieważ podczas wysyłania zwrotnego tylko wybrana wartość zostanie odesłana, więc przyjmuje się, że powinna zostać wysłana z powrotem do właściwości modelu SelectedPersonId, co oznacza, że ​​pierwsza nazwa parametru Html.DropDownList powinna mieć wartość „SelectedPersonId”. Dlatego projektant uważa, że ​​podczas wyświetlania widoku modelu w widoku właściwość modelu SelectedPersonId powinna zawierać wartość domyślną tej listy rozwijanej. Nawet jeśli Twoja lista <SelectListItem> Persons już ustawiła flagę Selected, aby wskazać, która z nich jest wybrana / default, tml.DropDownList faktycznie zignoruje to i przebuduje swój własny IEnumerable <SelectListItem> i ustawi domyślny / wybrany element na podstawie nazwy.

Oto kod z asp.net mvc

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

    bool usedViewData = false;

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

    object defaultValue = (allowMultiple) ? htmlHelper.GetModelStateValue(fullName, typeof(string[])) : htmlHelper.GetModelStateValue(fullName, typeof(string));

    // If we haven't already used ViewData to get the entire list of items then we need to
    // use the ViewData-supplied value before using the parameter-supplied value.
    if (defaultValue == null && !String.IsNullOrEmpty(name))
    {
        if (!usedViewData)
        {
            defaultValue = htmlHelper.ViewData.Eval(name);
        }
        else if (metadata != null)
        {
            defaultValue = metadata.Model;
        }
    }

    if (defaultValue != null)
    {
        selectList = GetSelectListWithDefaultValue(selectList, defaultValue, allowMultiple);
    }

    ...

    return tagBuilder.ToMvcHtmlString(TagRenderMode.Normal);
}

Tak więc kod poszedł dalej, nie tylko spróbuje wyszukać nazwę w modelu, ale także w danych viewdata, gdy tylko je znajdzie, odbuduje selectList i zignoruje oryginalny Selected.

Problem w tym, że w wielu przypadkach tak naprawdę nie używamy tego w ten sposób. chcemy po prostu wrzucić selectList z jednym / wieloma elementami Selected set true.

Oczywiście rozwiązanie jest proste, użyj nazwy, której nie ma w modelu ani w viewdata. Jeśli nie może znaleźć dopasowania, użyje oryginalnej listy selectList, a oryginalny Selected będzie mieć wpływ.

Ale nadal uważam, że mvc powinno to poprawić, dodając jeszcze jeden warunek

if ((defaultValue != null) && (!selectList.Any(i=>i.Selected)))
{
    selectList = GetSelectListWithDefaultValue(selectList, defaultValue, allowMultiple);
}

Ponieważ jeśli oryginalna lista selectList miała już jedną Selected, dlaczego miałbyś to zignorować?

Tylko moje myśli.

liuhongbo
źródło
To naprawdę najlepsze wyjaśnienie, jakie do tej pory przeczytałem. Oszczędziło mi wielu bólów głowy. Wszystko, co musiałem zrobić, to ustawić wartość właściwości viewmodel i zadziałało. Dzięki.
Moses Machua,
2

Kod w poprzednim poście MVC 3 nie działa, ale to dobry początek. Naprawię to. Przetestowałem ten kod i działa w MVC 3 Razor C # Ten kod używa wzorca ViewModel do wypełnienia właściwości, która zwraca List<SelectListItem>.

Klasa Model

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Klasa ViewModel

using System.Web.Mvc;

public class ProductListviewModel
{
    public List<SelectListItem> Products { get; set; }
}

Metoda kontrolera

public ViewResult List()
{
    var productList = new List<SelectListItem>();

    foreach (Product p in Products)
    {
        productList.Add(new SelectListItem
        {
            Value = p.ProductId.ToString(),
            Text = "Product: " + p.Name + " " + p.Price.ToString(),
            // To set the selected item use the following code 
            // Note: you should not set every item to selected
            Selected = true
        });
    }

    ProductListViewModel productListVM = new ProductListViewModeld();

    productListVM.Products = productList;

    return View(productListVM);
}

Widok

@model MvcApp.ViewModels.ProductListViewModel

@using (Html.BeginForm())
{
    @Html.DropDownList("Products", Model.Products)
}

Dane wyjściowe HTML będą podobne do

<select id="Products" name="Products">
    <option value="3">Product: Widget 10.00</option>
    <option value="4">Product: Gadget 5.95</option>
</select>

w zależności od tego, jak sformatujesz dane wyjściowe. Mam nadzieję, że to pomoże. Kod działa.

wayne.blackmon
źródło
1
To nie ustawia selectedopcji.
Jess
Aby ustawić wybrany element za pomocą SelectListItem, ustaw właściwość Selected na true.
wayne.blackmon
1

Wygląda na to, że jest to błąd w klasie SelectExtensions, ponieważ sprawdza ona tylko ViewData, a nie model dla wybranego elementu. Tak więc sztuczka polega na skopiowaniu wybranego elementu z modelu do kolekcji ViewData pod nazwą właściwości.

Jest to zaczerpnięte z odpowiedzi, której udzieliłem na forach MVC, mam również pełniejszą odpowiedź w poście na blogu, który używa atrybutu DropDownList Kaziego ...

Biorąc pod uwagę model

public class ArticleType
{
   public Guid Id { get; set; }
   public string Description { get; set; }
}

public class Article
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public ArticleType { get; set; }
}

i podstawowy model widoku

public class ArticleModel
{
     public Guid Id { get; set; }
     public string Name { get; set; }

     [UIHint("DropDownList")]
     public Guid ArticleType { get; set; }
}

Następnie piszemy szablon edytora DropDownList w następujący sposób.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<script runat="server">  
    IEnumerable<SelectListItem> GetSelectList()
    {
        var metaData = ViewData.ModelMetadata;
        if (metaData == null)
        {
            return null;
        }

        var selected = Model is SelectListItem ? ((SelectListItem) Model).Value : Model.ToString();
        ViewData[metaData.PropertyName] = selected;

        var key = metaData.PropertyName + "List";
        return (IEnumerable<SelectListItem>)ViewData[key];
    }
</script>
<%= Html.DropDownList(null, GetSelectList()) %>

To zadziała również, jeśli zmienisz ArticleType w modelu widoku na SelectListItem, chociaż musisz zaimplementować konwerter typów zgodnie z blogiem Kazi i zarejestrować go, aby zmusić spinacz do traktowania tego jako prostego typu.

W Twoim kontrolerze mamy wtedy ...

public ArticleController 
{
     ...
     public ActionResult Edit(int id)
     {
          var entity = repository.FindOne<Article>(id);
          var model = builder.Convert<ArticleModel>(entity);

          var types = repository.FindAll<ArticleTypes>();
          ViewData["ArticleTypeList"] = builder.Convert<SelectListItem>(types);

          return VIew(model);
     }
     ...
}
Paul Hatcher
źródło
Dzięki za odpowiedź, będę musiał to sprawdzić.
blu
0

Problem polega na tym, że dropboxy nie działają tak samo jak listboxy, przynajmniej tak, jak oczekuje projekt ASP.NET MVC2: Dropbox dopuszcza tylko zero lub jedną wartość, ponieważ listy mogą mieć wiele wartości. Tak więc, będąc ścisłym w HTML, ta wartość nie powinna znajdować się na liście opcji jako flaga „selected”, ale w samym wejściu.

Zobacz poniższy przykład:

<select id="combo" name="combo" value="id2">
  <option value="id1">This is option 1</option>
  <option value="id2" selected="selected">This is option 2</option>
  <option value="id3">This is option 3</option>
</select>

<select id="listbox" name="listbox" multiple>
  <option value="id1">This is option 1</option>
  <option value="id2" selected="selected">This is option 2</option>
  <option value="id3">This is option 3</option>
</select>

Kombinacja ma wybraną opcję, ale ma również ustawiony atrybut wartości . Tak więc, jeśli chcesz, aby ASP.NET MVC2 renderował dropbox, a także miał wybraną określoną wartość (tj. Wartości domyślne itp.), Powinieneś nadać jej wartość w renderowaniu, na przykład:

// in my view             
<%=Html.DropDownList("UserId", selectListItems /* (SelectList)ViewData["UserId"]*/, new { @Value = selectedUser.Id } /* Your selected value as an additional HTML attribute */)%>
dalotodo
źródło
0

W ASP.NET MVC 3 możesz po prostu dodać swoją listę do ViewData ...

var options = new List<SelectListItem>();

options.Add(new SelectListItem { Value = "1", Text = "1" });
options.Add(new SelectListItem { Value = "2", Text = "2" });
options.Add(new SelectListItem { Value = "3", Text = "3", Selected = true });

ViewData["options"] = options;

... a następnie odwołaj się do tego po nazwie w widoku brzytwy ...

@Html.DropDownList("options")

Nie musisz ręcznie „używać” listy w wywołaniu DropDownList. Robiąc to w ten sposób, również poprawnie ustaw dla mnie wybraną wartość.

Zrzeczenie się:

  1. Nie próbowałem tego z silnikiem przeglądania formularzy internetowych, ale powinno też działać.
  2. Nie testowałem tego w wersjach v1 i v2, ale może to zadziałać.
John Bubriski
źródło
0

Udało mi się uzyskać pożądany efekt, ale przy nieco innym podejściu. Na liście rozwijanej użyłem modelu, a następnie odniosłem się do niego. Nie jestem pewien, czy tego właśnie szukałeś.

@Html.DropDownList("Example", new SelectList(Model.FeeStructures, "Id", "NameOfFeeStructure", Model.Matters.FeeStructures))

Model.Matters.FeeStructures w powyższym miejscu to mój identyfikator, który może być wartością przedmiotu, który należy wybrać.

Jukes
źródło