ASP.NET MVC Html.DropDownList SelectedValue

103

J'ai essayé ceci est RC1 puis mis à niveau vers RC2 qui n'a pas résolu le problème.

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

résultat: la propriété SelectedValue est définie sur l'objet

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

résultat: toutes les options attendues sont rendues au client, mais l'attribut sélectionné n'est pas défini. L'élément de SelectedValue existe dans la liste, mais le premier élément de la liste est toujours sélectionné par défaut.

Comment dois-je faire ça?

Mise à jour Grâce à la réponse de John Feminella, j'ai découvert quel était le problème. "UserId" est une propriété du modèle dans laquelle ma vue est fortement typée. Lorsque Html.DropDownList ("UserId" est modifié en tout autre nom mais "UserId", la valeur sélectionnée est rendue correctement.

Il en résulte que la valeur n'est pas liée au modèle.

blu
la source
Êtes-vous certain que la valeur figure dans la liste?
John Sheehan
Le problème existe toujours dans la version 1 d'ASP.NET MVC
Mathias F
Qu'est-ce qui est selectedUserId !?
fulvio
Comment êtes-vous supposé lier la valeur lors de la mise à jour si le nom de votre entrée n'est pas le même que votre propriété?
ryudice

Réponses:

68

Voici comment j'ai résolu ce problème:

J'ai eu ce qui suit:

Manette:

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

Vue

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

Modifié par ce qui suit:

Vue

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

Il semble que le DropDown ne doit pas avoir le même nom a le nom ViewData: S bizarre mais cela a fonctionné.

Sanchitos
la source
19
+1 pour "le DropDown ne doit pas avoir le même nom a le nom ViewData" Big Thanks
Daniel Dyson
Je vous remercie!!! Vous venez de résoudre mon problème après de nombreuses heures à vous demander pourquoi cela n'a pas fonctionné :)
Jen
1
Cela fonctionne pour la valeur sélectionnée, mais un problème survient lorsque vous essayez de mettre à jour "DealerTypes" dans la base de données car c'est le DropDownList est maintenant lié à "DealerTypesDD" qui n'existe pas dans le modèle. Une solution de contournement consiste à ajouter un champ masqué et htmlAttributes à la DropDownList: <input type = "hidden" name = "DealerTypes" id = "DealerTypes" value = "" /> <% = Html.DropDownList ("DealerTypesDD", ViewData [ "DealerTypes"] comme SelectList, nouveau {@onchange = "DealerTypes.value = this.value"})%>
Zim
3
Vous feriez mieux d'ajouter `` DS '' au nom de viewdata, donc de ne pas ruiner la liaison du modèle ...
noocyte
1
Que se passe-t-il! Ce n'est pas corrigé dans MVC5 ??? Comme une autre astuce mentionnée par @Paul Hatcher, copiez l'identifiant sélectionné dans ViewData ["DealerTypes"].
jsgoupil
51

Essaye ça:

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

Puis:

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"])

Avec MVC RC2, j'obtiens:

<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
la source
Cela a été utile pour identifier la cause. J'ai mis à jour ma question pour refléter les ajouts.
blu
est le premier modèle? deuxième contrôleur? et troisième en vue c'est clair, mais 2 premiers .. ???
rr
Bonne réponse, mais n'est-il pas préférable d'utiliser un ViewModel?
Jess
@Jess: J'ai écrit cette réponse en mars 2009, il y a plus de 5 ans. Les temps ont changé! N'hésitez pas à mettre à jour. :)
John Feminella
5

Vous pouvez toujours nommer DropDown comme "UserId" et avoir toujours la liaison de modèle fonctionnant correctement pour vous.

La seule condition requise pour que cela fonctionne est que la clé ViewData qui contient la SelectList n'a pas le même nom que la propriété Model que vous souhaitez lier. Dans votre cas particulier, ce serait:

// 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"])%>

Cela produira un élément de sélection nommé UserId, qui porte le même nom que la propriété UserId dans votre modèle et par conséquent, le classeur de modèle le définira avec la valeur sélectionnée dans l'élément de sélection du html généré par l'assistant Html.DropDownList.

Je ne sais pas pourquoi ce constructeur Html.DropDownList particulier ne sélectionne pas la valeur spécifiée dans SelectList lorsque vous placez la liste de sélection dans ViewData avec une clé égale au nom de la propriété. Je soupçonne que cela a quelque chose à voir avec la façon dont l'assistant DropDownList est utilisé dans d'autres scénarios, où la convention est que vous avez une SelectList dans ViewData avec le même nom que la propriété de votre modèle. Cela fonctionnera correctement:

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

// in my view
<%=Html.DropDownList("UserId")%>
Rui
la source
Merci beaucoup mon pote, par-dessus toutes les réponses, cela a fonctionné pour moi, je ne sais pas pourquoi mais ça l'a fait
Lamin Sanneh
Finalement! Après des heures d'essais, j'ai finalement trouvé votre "seule exigence". Cela l'a fait.
SteveCav le
2

Si nous ne pensons pas que ce soit un bogue que l'équipe devrait corriger, MSDN devrait au moins améliorer le document. La confusion vient vraiment du mauvais document à ce sujet. Dans MSDN , il explique le nom des paramètres comme suit :

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

Cela signifie simplement que le code HTML final qu'il génère utilisera ce paramètre comme nom de l'entrée sélectionnée. Mais cela signifie en fait plus que cela.

Je suppose que le concepteur suppose que l'utilisateur utilisera un modèle de vue pour afficher la liste déroulante, utilisera également la publication dans le même modèle de vue. Mais dans de nombreux cas, nous ne suivons pas vraiment cette hypothèse.

Utilisez l'exemple ci-dessus,

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

Si nous suivons l'hypothèse, nous devrions définir un modèle de vue pour cette vue associée à la liste déroulante

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

Parce que lors de la publication, seule la valeur sélectionnée sera publiée, elle suppose donc qu'elle doit être renvoyée à la propriété du modèle SelectedPersonId, ce qui signifie que le premier nom de paramètre de Html.DropDownList doit être «SelectedPersonId». Ainsi, le concepteur pense que lors de l'affichage de la vue du modèle dans la vue, la propriété du modèle SelectedPersonId doit contenir la valeur par défaut de cette liste déroulante. Même si votre liste <SelectListItem> Persons a déjà défini l'indicateur Selected pour indiquer lequel est sélectionné / par défaut, le tml.DropDownList l'ignorera et reconstruira son propre IEnumerable <SelectListItem> et définira l'élément par défaut / sélectionné en fonction du nom.

Voici le code de 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);
}

Ainsi, le code est allé plus loin, il essaie non seulement de rechercher le nom dans le modèle, mais également dans les données de vue, dès qu'il en trouve un, il reconstruit la selectList et ignore votre Sélection d'origine.

Le problème est que dans bien des cas, nous ne l'utilisons pas vraiment de cette façon. nous voulons simplement lancer une liste de sélection avec un / plusieurs élément (s) sélectionné (s) défini sur true.

Bien sûr, la solution est simple, utilisez un nom qui ne figure ni dans le modèle ni dans les données de vue. Lorsqu'il ne trouve pas de correspondance, il utilisera la liste de sélection d'origine et la sélection d'origine prendra effet.

Mais je pense toujours que mvc devrait l'améliorer en ajoutant une condition supplémentaire

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

Parce que, si la liste de sélection d'origine en a déjà eu une, pourquoi l'ignoreriez-vous?

Juste mes pensées.

liuhongbo
la source
C'est vraiment la meilleure explication que j'ai lue jusqu'à présent. M'a sauvé beaucoup de maux de tête. Tout ce que j'avais à faire était de définir la valeur de la propriété viewmodel et cela a fonctionné. Merci.
Moses Machua
2

Le code dans le précédent article MVC 3 ne fonctionne pas mais c'est un bon début. Je le réparerai. J'ai testé ce code et il fonctionne dans MVC 3 Razor C # Ce code utilise le modèle ViewModel pour remplir une propriété qui renvoie un fichier List<SelectListItem>.

La classe Model

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

La classe ViewModel

using System.Web.Mvc;

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

La méthode du contrôleur

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

La vue

@model MvcApp.ViewModels.ProductListViewModel

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

La sortie HTML sera quelque chose comme

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

selon la façon dont vous formatez la sortie. J'espère que ça aide. Le code fonctionne.

wayne.blackmon
la source
1
Cela ne définit pas l' selectedoption.
Jess
Afin de définir l'élément sélectionné à l'aide de SelectListItem, définissez la propriété Selected sur true.
wayne.blackmon
1

Cela semble être un bogue dans la classe SelectExtensions car il ne vérifiera que les ViewData plutôt que le modèle pour l'élément sélectionné. L'astuce consiste donc à copier l'élément sélectionné du modèle dans la collection ViewData sous le nom de la propriété.

Ceci est tiré de la réponse que j'ai donnée sur les forums MVC, j'ai également une réponse plus complète dans un article de blog qui utilise l'attribut DropDownList de Kazi ...

Étant donné un modèle

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

et un modèle de vue de base de

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

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

Ensuite, nous écrivons un modèle d'éditeur DropDownList comme suit.

<%@ 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()) %>

Cela fonctionnera également si vous changez ArticleType dans le modèle de vue en SelectListItem, bien que vous deviez implémenter un convertisseur de type selon le blog de Kazi et l'enregistrer pour forcer le classeur à le traiter comme un type simple.

Dans votre contrôleur, nous avons alors ...

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
la source
Merci pour la réponse, je vais devoir vérifier cela.
blu
0

Le problème est que les dropbox ne fonctionnent pas de la même manière que les listbox, du moins comme l'attend la conception ASP.NET MVC2: une dropbox n'autorise que zéro ou une valeur, car les listbox peuvent avoir une sélection de valeurs multiples. Donc, étant stricte avec HTML, cette valeur ne devrait pas être dans la liste d'options comme indicateur "sélectionné", mais dans l'entrée elle-même.

Consultez l'exemple suivant:

<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>

Le combo a l'option sélectionnée, mais a également son attribut de valeur défini . Donc, si vous voulez qu'ASP.NET MVC2 rende une boîte de dépôt et ait également une valeur spécifique sélectionnée (c'est-à-dire des valeurs par défaut, etc.), vous devez lui donner une valeur dans le rendu, comme ceci:

// in my view             
<%=Html.DropDownList("UserId", selectListItems /* (SelectList)ViewData["UserId"]*/, new { @Value = selectedUser.Id } /* Your selected value as an additional HTML attribute */)%>
Dalotodo
la source
0

Dans ASP.NET MVC 3, vous pouvez simplement ajouter votre liste à 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;

... puis référencez-le par son nom dans votre vue rasoir ...

@Html.DropDownList("options")

Vous n'avez pas à "utiliser" manuellement la liste dans l'appel DropDownList. En procédant de cette façon, définissez correctement la valeur sélectionnée pour moi aussi.

Avertissement:

  1. Je n'ai pas essayé cela avec le moteur d'affichage des formulaires Web, mais cela devrait également fonctionner.
  2. Je n'ai pas testé cela dans la v1 et la v2, mais cela pourrait fonctionner.
John Bubriski
la source
0

J'ai réussi à obtenir le résultat souhaité, mais avec une approche légèrement différente. Dans la liste déroulante, j'ai utilisé le modèle, puis je l'ai référencé. Je ne sais pas si c'était ce que vous recherchiez.

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

Model.Matters.FeeStructures ci-dessus est mon identifiant, qui pourrait être votre valeur de l'élément qui devrait être sélectionné.

Jukes
la source