Comment créer une liste déroulante à partir d'une énumération dans ASP.NET MVC?

671

J'essaie d'utiliser la Html.DropDownListméthode d'extension mais je n'arrive pas à comprendre comment l'utiliser avec une énumération.

Disons que j'ai une énumération comme celle-ci:

public enum ItemTypes
{
    Movie = 1,
    Game = 2,
    Book = 3
}

Comment créer une liste déroulante avec ces valeurs à l'aide de la Html.DropDownListméthode d'extension?

Ou est-ce que je préfère simplement créer une boucle for et créer les éléments Html manuellement?

Kevin Pang
la source

Réponses:

842

Pour MVC v5.1, utilisez Html.EnumDropDownListFor

@Html.EnumDropDownListFor(
    x => x.YourEnumField,
    "Select My Type", 
    new { @class = "form-control" })

Pour MVC v5, utilisez EnumHelper

@Html.DropDownList("MyType", 
   EnumHelper.GetSelectList(typeof(MyType)) , 
   "Select My Type", 
   new { @class = "form-control" })

Pour MVC 5 et inférieur

J'ai roulé la réponse de Rune dans une méthode d'extension:

namespace MyApp.Common
{
    public static class MyExtensions{
        public static SelectList ToSelectList<TEnum>(this TEnum enumObj)
            where TEnum : struct, IComparable, IFormattable, IConvertible
        {
            var values = from TEnum e in Enum.GetValues(typeof(TEnum))
                select new { Id = e, Name = e.ToString() };
            return new SelectList(values, "Id", "Name", enumObj);
        }
    }
}

Cela vous permet d'écrire:

ViewData["taskStatus"] = task.Status.ToSelectList();

par using MyApp.Common

Martin Faartoft
la source
13
Je ne pouvais pas le faire fonctionner, pourriez-vous s'il vous plaît aider. Quand je fais Post.PostType.ToSelectList (); il ne reconnaît pas l'extension?
Barbaros Alp
3
Je n'ai pas pu faire fonctionner cela non plus. Est-ce que Status votre propriété Enum appartient à la classe de tâches? N'est-ce pas l'une des valeurs énumérées?
Daryl
9
Vous pouvez le restreindre un peu avec: où T: struct, IConvertible Voir: stackoverflow.com/questions/79126/…
Richard Garside
8
C'est cool. Si quelqu'un a du mal avec la mise en œuvre, voici comment je l'ai fait. Ajout d'une classe EnumHelpers au dossier HtmlHelpers. Utilisé le code ci-dessus. Ajout de l'espace de noms selon la recommandation @TodK: <add namespace = "xxx.HtmlHelpers" />. Ensuite, je l'ai utilisé dans une page de rasoir comme celle-ci: @ Html.DropDownListFor (model => model.Status, @ Model.Status.ToSelectList ()) HTH
Jeff Borden
6
Notez que dans les ASP.NET MVC
versions
359

Je sais que je suis en retard à la fête à ce sujet, mais j'ai pensé que cette variante pourrait vous être utile, car celle-ci vous permet également d'utiliser des chaînes descriptives plutôt que des constantes d'énumération dans la liste déroulante. Pour ce faire, décorez chaque entrée d'énumération avec un attribut [System.ComponentModel.Description].

Par exemple:

public enum TestEnum
{
  [Description("Full test")]
  FullTest,

  [Description("Incomplete or partial test")]
  PartialTest,

  [Description("No test performed")]
  None
}

Voici mon code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Reflection;
using System.ComponentModel;
using System.Linq.Expressions;

 ...

 private static Type GetNonNullableModelType(ModelMetadata modelMetadata)
    {
        Type realModelType = modelMetadata.ModelType;

        Type underlyingType = Nullable.GetUnderlyingType(realModelType);
        if (underlyingType != null)
        {
            realModelType = underlyingType;
        }
        return realModelType;
    }

    private static readonly SelectListItem[] SingleEmptyItem = new[] { new SelectListItem { Text = "", Value = "" } };

    public static string GetEnumDescription<TEnum>(TEnum value)
    {
        FieldInfo fi = value.GetType().GetField(value.ToString());

        DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);

        if ((attributes != null) && (attributes.Length > 0))
            return attributes[0].Description;
        else
            return value.ToString();
    }

    public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
    {
        return EnumDropDownListFor(htmlHelper, expression, null);
    }

    public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, object htmlAttributes)
    {
        ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
        Type enumType = GetNonNullableModelType(metadata);
        IEnumerable<TEnum> values = Enum.GetValues(enumType).Cast<TEnum>();

        IEnumerable<SelectListItem> items = from value in values
            select new SelectListItem
            {
                Text = GetEnumDescription(value),
                Value = value.ToString(),
                Selected = value.Equals(metadata.Model)
            };

        // If the enum is nullable, add an 'empty' item to the collection
        if (metadata.IsNullableValueType)
            items = SingleEmptyItem.Concat(items);

        return htmlHelper.DropDownListFor(expression, items, htmlAttributes);
    }

Vous pouvez ensuite le faire à votre avis:

@Html.EnumDropDownListFor(model => model.MyEnumProperty)

J'espère que cela vous aide!

** EDIT 2014-JAN-23: Microsoft vient de publier MVC 5.1, qui dispose désormais d'une fonctionnalité EnumDropDownListFor. Malheureusement, il ne semble pas respecter l'attribut [Description], de sorte que le code ci-dessus persiste. Voir la section Enum dans les notes de publication de Microsoft pour MVC 5.1.

Mise à jour: Il prend cependant en charge l' attribut Display[Display(Name = "Sample")] , donc on peut l'utiliser.

[Mise à jour - vient de le remarquer, et le code ressemble à une version étendue du code ici: https://blogs.msdn.microsoft.com/stuartleeks/2010/05/21/asp-net-mvc-creating-a- dropdownlist-helper-for-enums / , avec quelques ajouts. Si c'est le cas, l'attribution semblerait juste ;-)]

SimonGoldstone
la source
28
+1 J'ai trouvé cette réponse des plus utiles ici. J'ai pu transformer cela en un morceau de code hautement réutilisable. Je vous remercie!
Ed Charbeneau
43
Visual Studio a un étrange bogue où si vous ne faites pas référence, System.Web.Mvc.Htmlil dit que DropDownListForcela ne peut pas être trouvé, mais il ne peut pas non plus le résoudre. Vous devez le faire manuellement using System.Web.Mvc.Html;. Alors tu sais.
Kezzer
1
J'en ai une variante dans un résumé que nous utilisons dans tous nos projets: gist.github.com/1287511
kamranicus
1
Une excellente solution, merci, serait encore meilleure si vous pouvez mettre en cache les résultats de GetEnumDescription
M. Mennan Kara
17
Le nouveau MVC 5.1 EnumDropDownListFor n'utilise pas [Description ("")] mais il utilise [Display (Name = "")]! Profitez :)
Supergibbs
195

Dans ASP.NET MVC 5.1 , ils ont ajouté l' EnumDropDownListFor()aide, donc pas besoin d'extensions personnalisées:

Modèle :

public enum MyEnum
{
    [Display(Name = "First Value - desc..")]
    FirstValue,
    [Display(Name = "Second Value - desc...")]
    SecondValue
}

Voir :

@Html.EnumDropDownListFor(model => model.MyEnum)

Utilisation de Tag Helper (ASP.NET MVC 6) :

<select asp-for="@Model.SelectedValue" asp-items="Html.GetEnumSelectList<MyEnum>()">
Ofiris
la source
21
Cela doit être remonté à la première place en quelque sorte
3
Vous devez créer une nouvelle question spécifique à MVC 5.1 et la mettre comme réponse, puis envoyez-moi un lien vers le message afin que je puisse voter pour un favori.
Kevin Heidt
2
Ce que je n'aime pas dans EnumDropDownListFor (), c'est qu'il enregistre dans la base de données la valeur int de l'énumération, pas le texte, donc si jamais vous choisissez d'ajouter un nouvel élément enum, il doit nécessairement aller à la fin de la liste , afin de ne pas perdre la relation entre les valeurs int de la base de données enregistrées et les positions d'origine des éléments d'énumération. Il s'agit d'une restriction inutile si le texte est enregistré. De plus, je peux plutôt regarder la base de données et voir un texte, plutôt que des entiers où je dois ensuite rechercher les valeurs de texte ailleurs. Sinon, cet assistant html est très pratique à utiliser.
Giovanni
2
@Giovanni - vous pouvez spécifier vos propres valeurs numériques.
Tommy
1
@Giovanni Strict design devrait attribuer une valeur à chaque entrée d'énumération (si elle est importante), sinon la valeur ne devrait pas avoir d'importance (et donc placer les nouvelles à la fin ne devrait pas être un problème). La sauvegarde des valeurs int est meilleure lorsqu'il s'agit d'économiser du stockage et d'augmenter les performances (lors de l'exécution de certaines recherches).
King King
130

Je suis tombé sur le même problème, j'ai trouvé cette question et j'ai pensé que la solution fournie par Ash n'était pas ce que je cherchais; Devoir créer le HTML moi-même signifie moins de flexibilité par rapport à la Html.DropDownList()fonction intégrée.

Il s'avère que C # 3 etc. rend cela assez facile. J'ai un enumappelé TaskStatus:

var statuses = from TaskStatus s in Enum.GetValues(typeof(TaskStatus))
               select new { ID = s, Name = s.ToString() };
ViewData["taskStatus"] = new SelectList(statuses, "ID", "Name", task.Status);

Cela crée un bon ol ' SelectListqui peut être utilisé comme vous en avez l'habitude dans la vue:

<td><b>Status:</b></td><td><%=Html.DropDownList("taskStatus")%></td></tr>

Le type anonyme et LINQ rendent cela beaucoup plus élégant à mon humble avis. Aucune infraction, Ash. :)

Rune Jacobsen
la source
bonne réponse! J'espérais que quelqu'un utiliserait linq et la SelectList :) Content d'avoir vérifié ici en premier!
Pure.Krome
1
ID = s me donner le DataTextField pas la valeur? Quelle pourrait être la raison? Merci
Barbaros Alp
1
Rune, j'ai utilisé cette même méthode et le DropDownList REND encore quand il publie sur le serveur, il ne sauvegarde pas la valeur que j'avais sélectionnée.
clockwiseq
5
@BarbarosAlp Pour que l'ID soit un nombre, vous devez convertir l'énumération en un entier:select new { ID = (int)s, Name = s.ToString() };
Keith
C'est la réponse que j'aime le plus en raison de sa simplicité. Dommage que vous n'ayez pas reçu suffisamment de crédit car la réponse sélectionnée a utilisé votre solution.
anar khalilov
63

Voici une meilleure solution encapsulée:

https://www.spicelogic.com/Blog/enum-dropdownlistfor-asp-net-mvc-5

Dites voici votre modèle:

entrez la description de l'image ici

Exemple d'utilisation:

entrez la description de l'image ici

Interface utilisateur générée: entrez la description de l'image ici

Et HTML généré

entrez la description de l'image ici

Instantané du code source de l'extension d'assistance:

entrez la description de l'image ici

Vous pouvez télécharger l'exemple de projet à partir du lien que j'ai fourni.

EDIT: Voici le code:

public static class EnumEditorHtmlHelper
{
    /// <summary>
    /// Creates the DropDown List (HTML Select Element) from LINQ 
    /// Expression where the expression returns an Enum type.
    /// </summary>
    /// <typeparam name="TModel">The type of the model.</typeparam>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="htmlHelper">The HTML helper.</param>
    /// <param name="expression">The expression.</param>
    /// <returns></returns>
    public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TProperty>> expression) 
        where TModel : class
    {
        TProperty value = htmlHelper.ViewData.Model == null 
            ? default(TProperty) 
            : expression.Compile()(htmlHelper.ViewData.Model);
        string selected = value == null ? String.Empty : value.ToString();
        return htmlHelper.DropDownListFor(expression, createSelectList(expression.ReturnType, selected));
    }

    /// <summary>
    /// Creates the select list.
    /// </summary>
    /// <param name="enumType">Type of the enum.</param>
    /// <param name="selectedItem">The selected item.</param>
    /// <returns></returns>
    private static IEnumerable<SelectListItem> createSelectList(Type enumType, string selectedItem)
    {
        return (from object item in Enum.GetValues(enumType)
                let fi = enumType.GetField(item.ToString())
                let attribute = fi.GetCustomAttributes(typeof (DescriptionAttribute), true).FirstOrDefault()
                let title = attribute == null ? item.ToString() : ((DescriptionAttribute) attribute).Description
                select new SelectListItem
                  {
                      Value = item.ToString(), 
                      Text = title, 
                      Selected = selectedItem == item.ToString()
                  }).ToList();
    }
}
Emran Hussain
la source
2
Juste mon opinion, mais je pense que cette réponse est beaucoup plus claire que la réponse acceptée. J'aime particulièrement la possibilité d'utiliser l'attribut Description. J'ai ajouté le code pour que les gens puissent le copier / coller sans le télécharger.
Ben Mills
Appelez la méthode d'extension comme EnumDropDownListFor plutôt que DropDownListFor Utilisation: -> @ Html.EnumDropDownListFor (x => x.Gender)
sandeep talabathula
Pour quelqu'un qui cherche à ajouter un élément supplémentaire "Veuillez sélectionner", renvoyez htmlHelper.DropDownListFor (expression, createSelectList (expression.ReturnType, selected, firstElement), "Please Select");
Sandeep
1
Fonctionne bien! Cependant, sur la page Détails, DisplayFor () affiche la valeur sélectionnée de l'énumération au lieu de la description correspondante. Je suppose que cela appelle une surcharge pour DisplayFor () pour le type enum. Quelqu'un a une solution pour cela?
corix010
48

Html.DropDownListFor ne nécessite qu'un IEnumerable, donc une alternative à la solution de Prise est la suivante. Cela vous permettra d'écrire simplement:

@Html.DropDownListFor(m => m.SelectedItemType, Model.SelectedItemType.ToSelectList())

[Où SelectedItemType est un champ de votre modèle de type ItemTypes et votre modèle n'est pas nul]

De plus, vous n'avez pas vraiment besoin de généraliser la méthode d'extension car vous pouvez utiliser enumValue.GetType () plutôt que typeof (T).

EDIT: La solution de Simon intégrée ici aussi, et inclus la méthode d'extension ToDescription.

public static class EnumExtensions
{
    public static IEnumerable<SelectListItem> ToSelectList(this Enum enumValue)
    {
        return from Enum e in Enum.GetValues(enumValue.GetType())
               select new SelectListItem
               {
                   Selected = e.Equals(enumValue),
                   Text = e.ToDescription(),
                   Value = e.ToString()
               };
    }

    public static string ToDescription(this Enum value)
    {
        var attributes = (DescriptionAttribute[])value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
        return attributes.Length > 0 ? attributes[0].Description : value.ToString();
    }
}
Zaid Masud
la source
Ne fonctionne pas pour moi ('System.NullReferenceException: référence d'objet non définie sur une instance d'un objet.') ... Mon 'modèle' est nul ... a probablement quelque chose à voir avec 'GetNonNullableModelType' que Simon a inclus
apprenant
@Cristi, vous avez raison, cette solution n'est pas destinée à être utilisée dans une condition où votre modèle est nul. J'essaie d'éviter une telle conception en général et de m'initialiser sur un modèle "vide" lorsque c'est le cas.
Zaid Masud
Eh bien, je suis nouveau sur asp mvc, mais j'ai assez d'expérience en .Net. Merci, je vais vérifier ce que vous proposiez. Btw votre extension ToDescription est bien en dehors de la portée 'Enum'. Je suppose que cela va bien pour l '«objet» lui-même. C'est ce que j'ai utilisé lorsque j'ai pris le code de Simon et l'ai nettoyé un peu plus.
Apprenant
@Cristi, il est difficile de comprendre ce que vous entendez par "bien en dehors de la portée" Enum "", mais il semble que vous disiez que la méthode d'extension ToDescription n'est pas fortement typée dans l'énumération ItemTypes? Ceci est intentionnel et rend la méthode d'extension génériquement utilisable par toutes les énumérations. Si vous la comparez à une méthode d'extension générique, chaque approche présente des avantages et des inconvénients. En particulier, si vous générez, vous ne pouvez pas le contraindre uniquement aux énumérations.
Zaid Masud
1
Super, merci. J'ai changé value.ToString pour utiliser une extension FromCamelCase au cas où il n'y aurait pas de description. C'est comme ça que je roule :)
Valamas
33

Donc sans fonctions d'extension si vous cherchez simple et facile .. c'est ce que j'ai fait

<%= Html.DropDownListFor(x => x.CurrentAddress.State, new SelectList(Enum.GetValues(typeof(XXXXX.Sites.YYYY.Models.State))))%>

où XXXXX.Sites.YYYY.Models.State est une énumération

Il vaut probablement mieux faire une fonction d'aide, mais lorsque le temps est court, cela fera le travail.

Marty Trenouth
la source
Bien, cela a fonctionné en remplissant la liste déroulante, mais comment définissez-vous la valeur sélectionnée par défaut dans la syntaxe Razor pour Html.DropDownListFor? Je souhaite afficher un tableau avec des zones de liste déroulante d'énumérations et je dois également définir la valeur sélectionnée en fonction de ce qu'elle était auparavant.
Johncl
2
Doit pouvoir passer un deuxième paramètre avec la valeur sélectionnée à la nouvelle fonction SelectList (IEnumerable, object). Documentation MSDN: msdn.microsoft.com/en-us/library/dd460123.aspx
Marty Trenouth
23

En développant les réponses de Prize et Rune, si vous souhaitez que l'attribut value de vos éléments de liste sélectionnés soit mappé sur la valeur entière du type Enumeration, plutôt que sur la valeur de chaîne, utilisez le code suivant:

public static SelectList ToSelectList<T, TU>(T enumObj) 
    where T : struct
    where TU : struct
{
    if(!typeof(T).IsEnum) throw new ArgumentException("Enum is required.", "enumObj");

    var values = from T e in Enum.GetValues(typeof(T))
                 select new { 
                    Value = (TU)Convert.ChangeType(e, typeof(TU)),
                    Text = e.ToString() 
                 };

    return new SelectList(values, "Value", "Text", enumObj);
}

Au lieu de traiter chaque valeur d'énumération comme un objet TEnum, nous pouvons la traiter comme un objet, puis la convertir en entier pour obtenir la valeur non encadrée.

Remarque: J'ai également ajouté une contrainte de type générique pour restreindre les types pour lesquels cette extension est disponible aux seules structures (type de base d'Enum), et une validation de type au moment de l'exécution qui garantit que la structure transmise est bien une Enum.

Mise à jour 23/10/12: Ajout d'un paramètre de type générique pour le type sous-jacent et correction d'un problème de non-compilation affectant .NET 4+.

Nathan Taylor
la source
Merci! C'était la réponse dont j'avais besoin. Je stocke la valeur entière d'un Enum sous forme de colonne dans la base de données et cette solution semble fonctionner parfaitement.
grimus
mais que faire si vous stockez un char et non un int? c'est mon cas. évidemment, je pourrais changer (int) en (char) mais que diriez-vous de faire ce générique aussi. comment faire ça?
Stefanvds
@Stefandvds C'est une grande question en ce qui concerne la conversion vers le type représenté correct. Sur la base des tests que je viens de réaliser, il semblerait que la seule façon d'y parvenir serait de spécifier le type réel comme autre paramètre de type. ToSelectList<TEnum, TEnumValue>(this TEnum enumObj) { ... }
Nathan Taylor
@Stefandvds Voir cette question .
Nathan Taylor
Si les valeurs de votre énumération sont int, vous pouvez simplement utiliser Value = Convert.ToInt32(e). (int)ene compile pas. :(
Andrew
11

Pour résoudre le problème d'obtention du numéro au lieu du texte en utilisant la méthode d'extension de Prise.

public static SelectList ToSelectList<TEnum>(this TEnum enumObj)
{
  var values = from TEnum e in Enum.GetValues(typeof(TEnum))
               select new { ID = (int)Enum.Parse(typeof(TEnum),e.ToString())
                         , Name = e.ToString() };

  return new SelectList(values, "Id", "Name", enumObj);
}
ceedee
la source
C'est ce que je cherchais, même si c'est plus laid que je ne le pensais. Je me demande pourquoi Visual Studio ne vous laissera pas directement jeté eà int.
Andrew
Ou vous pouvez simplement utiliser ID = Convert.ToInt32(e).
Andrew
11

Un moyen super facile de le faire - sans toutes les extensions qui semblent exagérées est la suivante:

Votre énumération:

    public enum SelectedLevel
    {
       Level1,
       Level2,
       Level3,
       Level4
    }

À l'intérieur de votre contrôleur, liez l'énumération à une liste:

    List<SelectedLevel> myLevels = Enum.GetValues(typeof(SelectedLevel)).Cast<SelectedLevel>().ToList();

Après cela, jetez-le dans un ViewBag:

    ViewBag.RequiredLevel = new SelectList(myLevels);

Enfin, liez-le simplement à la vue:

    @Html.DropDownList("selectedLevel", (SelectList)ViewBag.RequiredLevel, new { @class = "form-control" })

C'est de loin le moyen le plus simple que j'ai trouvé et ne nécessite aucune extension ou quelque chose d'aussi fou.

MISE À JOUR : Voir le commentaire d'Andrews ci-dessous.

Louie Bacaj
la source
3
Cela ne fonctionne que si vous n'avez attribué aucune valeur à votre énumération. Si vous l'aviez Level1 = 1, alors la valeur de la liste déroulante serait "Level1"au lieu de 1.
Andrew
11

La meilleure solution que j'ai trouvée pour cela était de combiner ce blog avec la réponse de Simon Goldstone .

Cela permet d'utiliser l'énumération dans le modèle. L'idée consiste essentiellement à utiliser une propriété entière ainsi que l'énumération et à émuler la propriété entière.

Utilisez ensuite l'attribut [System.ComponentModel.Description] pour annoter le modèle avec votre texte d'affichage et utilisez une extension "EnumDropDownListFor" dans votre vue.

Cela rend la vue et le modèle très lisibles et maintenables.

Modèle:

public enum YesPartialNoEnum
{
    [Description("Yes")]
    Yes,
    [Description("Still undecided")]
    Partial,
    [Description("No")]
    No
}

//........

[Display(Name = "The label for my dropdown list")]
public virtual Nullable<YesPartialNoEnum> CuriousQuestion{ get; set; }
public virtual Nullable<int> CuriousQuestionId
{
    get { return (Nullable<int>)CuriousQuestion; }
    set { CuriousQuestion = (Nullable<YesPartialNoEnum>)value; }
}

Vue:

@using MyProject.Extensions
{
//...
    @Html.EnumDropDownListFor(model => model.CuriousQuestion)
//...
}

Extension (directement à partir de la réponse de Simon Goldstone , incluse ici pour être complète):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.ComponentModel;
using System.Reflection;
using System.Linq.Expressions;
using System.Web.Mvc.Html;

namespace MyProject.Extensions
{
    //Extension methods must be defined in a static class
    public static class MvcExtensions
    {
        private static Type GetNonNullableModelType(ModelMetadata modelMetadata)
        {
            Type realModelType = modelMetadata.ModelType;

            Type underlyingType = Nullable.GetUnderlyingType(realModelType);
            if (underlyingType != null)
            {
                realModelType = underlyingType;
            }
            return realModelType;
        }

        private static readonly SelectListItem[] SingleEmptyItem = new[] { new SelectListItem { Text = "", Value = "" } };

        public static string GetEnumDescription<TEnum>(TEnum value)
        {
            FieldInfo fi = value.GetType().GetField(value.ToString());

            DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);

            if ((attributes != null) && (attributes.Length > 0))
                return attributes[0].Description;
            else
                return value.ToString();
        }

        public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
        {
            return EnumDropDownListFor(htmlHelper, expression, null);
        }

        public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, object htmlAttributes)
        {
            ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
            Type enumType = GetNonNullableModelType(metadata);
            IEnumerable<TEnum> values = Enum.GetValues(enumType).Cast<TEnum>();

            IEnumerable<SelectListItem> items = from value in values
                                                select new SelectListItem
                                                {
                                                    Text = GetEnumDescription(value),
                                                    Value = value.ToString(),
                                                    Selected = value.Equals(metadata.Model)
                                                };

            // If the enum is nullable, add an 'empty' item to the collection
            if (metadata.IsNullableValueType)
                items = SingleEmptyItem.Concat(items);

            return htmlHelper.DropDownListFor(expression, items, htmlAttributes);
        }
    }
}
Nick Evans
la source
Cela ne fonctionne pas, MVC 4 Razor. Dans la vue ou le runtime, error = "L'appel est ambigu entre les méthodes ou propriétés suivantes LDN.Extensions.MvcExtensions.EnumDropDownListFor <MyModel, LDN.Models.YesPartialNoEnum?> (System.Web.Mvc.HtmlHelper <MyModel>, System .Linq.Expressions.Expression <System.Func <MyModel, LDN.Models.YesPartialNoEnum? >>) 'et .... "et cette même méthode exacte avec les mêmes accessoires répétés à nouveau (pas assez de caractères autorisés ici).
Marc
9

Vous souhaitez utiliser quelque chose comme Enum.GetValues

Garry Shutler
la source
8
@Html.DropDownListFor(model => model.Type, Enum.GetNames(typeof(Rewards.Models.PropertyType)).Select(e => new SelectListItem { Text = e }))
Mr. Pumpkin
la source
Bien! Comment obtenir de la valeur et du texte d'énumération de cette manière? Je veux dire que j'ai SomeEnum {some1 = 1, some2 = 2} J'ai besoin d'obtenir des nombres (1, 2) pour la valeur et du texte (some1, some2) pour le texte de la liste de sélection
Dmitresky
7

Il s'agit des réponses Rune & Prize modifiées pour utiliser la intvaleur Enum comme ID.

Exemple d'énumération:

public enum ItemTypes
{
    Movie = 1,
    Game = 2,
    Book = 3
}

Méthode d'extension:

    public static SelectList ToSelectList<TEnum>(this TEnum enumObj)
    {
        var values = from TEnum e in Enum.GetValues(typeof(TEnum))
                     select new { Id = (int)Enum.Parse(typeof(TEnum), e.ToString()), Name = e.ToString() };

        return new SelectList(values, "Id", "Name", (int)Enum.Parse(typeof(TEnum), enumObj.ToString()));
    }

Échantillon d'utilisation:

 <%=  Html.DropDownList("MyEnumList", ItemTypes.Game.ToSelectList()) %>

N'oubliez pas d'importer l'espace de noms contenant la méthode d'extension

<%@ Import Namespace="MyNamespace.LocationOfExtensionMethod" %>

Exemple de HTML généré:

<select id="MyEnumList" name="MyEnumList">
    <option value="1">Movie</option>
    <option selected="selected" value="2">Game</option>
    <option value="3">Book </option>
</select>

Notez que l'élément que vous utilisez pour appeler ToSelectListle est l'élément sélectionné.

M. Flibble
la source
Ou vous pouvez simplement utiliser Id = Convert.ToInt32(e).
Andrew
6

Ceci est une version pour Razor:

@{
    var itemTypesList = new List<SelectListItem>();
    itemTypesList.AddRange(Enum.GetValues(typeof(ItemTypes)).Cast<ItemTypes>().Select(
                (item, index) => new SelectListItem
                {
                    Text = item.ToString(),
                    Value = (index).ToString(),
                    Selected = Model.ItemTypeId == index
                }).ToList());
 }


@Html.DropDownList("ItemTypeId", itemTypesList)
user550950
la source
Cela ne fonctionnera que si votre énumération se compose de valeurs contiguës commençant par 0. Une énumération de drapeaux ne fonctionnerait pas avec cela. Utilisation créative du Select indexé.
Suncat2000
6

Dans .NET Core, vous pouvez simplement utiliser ceci:

@Html.DropDownListFor(x => x.Foo, Html.GetEnumSelectList<MyEnum>())
Âge d'or
la source
1
Ou avec l'aide de balise <select asp-for="Model.Foo" class="form-control" asp-items="@Html.GetEnumSelectList<MyEnum>()"></select>.
Pascal R.
oui, je dirais que les aides aux balises sont encore meilleures car le format est plus proche du HTML pur;)
GoldenAge
Vous pouvez également le faire @ Html.DropDownListFor (x => x.Foo, Html.GetEnumSelectList (typeof (FooEnum)))
Fereydoon Barikzehy
5

En s'appuyant sur la réponse de Simon, une approche similaire consiste à obtenir les valeurs Enum à afficher à partir d'un fichier de ressources, plutôt que dans un attribut de description dans l'Enum lui-même. Cela est utile si votre site doit être rendu dans plusieurs langues et si vous avez un fichier de ressources spécifique pour Enums, vous pouvez aller plus loin et avoir juste des valeurs Enum dans votre Enum et les référencer à partir de l'extension en une convention telle que [EnumName] _ [EnumValue] - finalement moins de frappe!

L'extension ressemble alors à:

public static IHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> html, Expression<Func<TModel, TEnum>> expression)
{            
    var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);

    var enumType = Nullable.GetUnderlyingType(metadata.ModelType) ?? metadata.ModelType;

    var enumValues = Enum.GetValues(enumType).Cast<object>();

    var items = from enumValue in enumValues                        
                select new SelectListItem
                {
                    Text = GetResourceValueForEnumValue(enumValue),
                    Value = ((int)enumValue).ToString(),
                    Selected = enumValue.Equals(metadata.Model)
                };


    return html.DropDownListFor(expression, items, string.Empty, null);
}

private static string GetResourceValueForEnumValue<TEnum>(TEnum enumValue)
{
    var key = string.Format("{0}_{1}", enumValue.GetType().Name, enumValue);

    return Enums.ResourceManager.GetString(key) ?? enumValue.ToString();
}

Les ressources du fichier Enums.Resx ressemblant à ItemTypes_Movie: Film

Une autre chose que j'aime faire est, au lieu d'appeler directement la méthode d'extension, je préfère l'appeler avec un @ Html.EditorFor (x => x.MyProperty), ou idéalement juste avoir le formulaire entier, dans un seul @ Html.EditorForModel (). Pour ce faire, je change le modèle de chaîne pour ressembler à ceci

@using MVCProject.Extensions

@{
    var type = Nullable.GetUnderlyingType(ViewData.ModelMetadata.ModelType) ?? ViewData.ModelMetadata.ModelType;

    @(typeof (Enum).IsAssignableFrom(type) ? Html.EnumDropDownListFor(x => x) : Html.TextBoxFor(x => x))
}

Si cela vous intéresse, j'ai mis une réponse beaucoup plus détaillée ici sur mon blog:

http://paulthecyclist.com/2013/05/24/enum-dropdown/

PaulTheCyclist
la source
5

Eh bien, je suis vraiment en retard à la fête, mais pour ce que ça vaut, j'ai blogué sur ce sujet même où je crée une EnumHelperclasse qui permet une transformation très facile.

http://jnye.co/Posts/4/creating-a-dropdown-list-from-an-enum-in-mvc-and-c%23

Dans votre contrôleur:

//If you don't have an enum value use the type
ViewBag.DropDownList = EnumHelper.SelectListFor<MyEnum>();

//If you do have an enum value use the value (the value will be marked as selected)    
ViewBag.DropDownList = EnumHelper.SelectListFor(MyEnum.MyEnumValue);

À votre avis:

@Html.DropDownList("DropDownList")
@* OR *@
@Html.DropDownListFor(m => m.Property, ViewBag.DropDownList as SelectList, null)

La classe d'assistance:

public static class EnumHelper
{
    // Get the value of the description attribute if the   
    // enum has one, otherwise use the value.  
    public static string GetDescription<TEnum>(this TEnum value)
    {
        var fi = value.GetType().GetField(value.ToString());

        if (fi != null)
        {
            var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);

            if (attributes.Length > 0)
            {
                return attributes[0].Description;
            }
        }

        return value.ToString();
    }

    /// <summary>
    /// Build a select list for an enum
    /// </summary>
    public static SelectList SelectListFor<T>() where T : struct
    {
        Type t = typeof(T);
        return !t.IsEnum ? null
                         : new SelectList(BuildSelectListItems(t), "Value", "Text");
    }

    /// <summary>
    /// Build a select list for an enum with a particular value selected 
    /// </summary>
    public static SelectList SelectListFor<T>(T selected) where T : struct
    {
        Type t = typeof(T);
        return !t.IsEnum ? null
                         : new SelectList(BuildSelectListItems(t), "Text", "Value", selected.ToString());
    }

    private static IEnumerable<SelectListItem> BuildSelectListItems(Type t)
    {
        return Enum.GetValues(t)
                   .Cast<Enum>()
                   .Select(e => new SelectListItem { Value = e.ToString(), Text = e.GetDescription() });
    }
}
NinjaNye
la source
4

Je suis très en retard sur celui-ci, mais je viens de trouver un moyen vraiment cool de le faire avec une seule ligne de code, si vous êtes heureux d'ajouter le package Melody NuGet Unconstrained (une jolie petite bibliothèque de Jon Skeet).

Cette solution est meilleure car:

  1. Il garantit (avec des contraintes de type génériques) que la valeur est vraiment une valeur d'énumération (en raison de la mélodie non contrainte)
  2. Il évite la boxe inutile (en raison de la mélodie sans contrainte)
  3. Il met en cache toutes les descriptions pour éviter d'utiliser la réflexion sur chaque appel (en raison de la mélodie sans contrainte)
  4. C'est moins de code que les autres solutions!

Voici donc les étapes pour que cela fonctionne:

  1. Dans la console du gestionnaire de packages, «Install-Package UnconstrainedMelody»
  2. Ajoutez une propriété sur votre modèle comme ceci:

    //Replace "YourEnum" with the type of your enum
    public IEnumerable<SelectListItem> AllItems
    {
        get
        {
            return Enums.GetValues<YourEnum>().Select(enumValue => new SelectListItem { Value = enumValue.ToString(), Text = enumValue.GetDescription() });
        }
    }

Maintenant que la liste de SelectListItem est exposée sur votre modèle, vous pouvez utiliser @ Html.DropDownList ou @ Html.DropDownListFor en utilisant cette propriété comme source.

nootn
la source
+1 pour l'utilisation du code de Jon Skeet :), mais je plaisante
Vamsi
3

Un autre correctif à cette méthode d'extension - la version actuelle n'a pas sélectionné la valeur actuelle de l'énumération. J'ai fixé la dernière ligne:

public static SelectList ToSelectList<TEnum>(this TEnum enumObj) where TEnum : struct
    {
        if (!typeof(TEnum).IsEnum) throw new ArgumentException("An Enumeration type is required.", "enumObj");

        var values = from TEnum e in Enum.GetValues(typeof(TEnum))
                       select new
                       {
                           ID = (int)Enum.Parse(typeof(TEnum), e.ToString()),
                           Name = e.ToString()
                       };


        return new SelectList(values, "ID", "Name", ((int)Enum.Parse(typeof(TEnum), enumObj.ToString())).ToString());
    }
justabuzz
la source
3

Si vous souhaitez ajouter une prise en charge de la localisation, remplacez simplement la méthode s.toString () par quelque chose comme ceci:

ResourceManager rManager = new ResourceManager(typeof(Resources));
var dayTypes = from OperatorCalendarDay.OperatorDayType s in Enum.GetValues(typeof(OperatorCalendarDay.OperatorDayType))
               select new { ID = s, Name = rManager.GetString(s.ToString()) };

Ici, le typeof (Resources) est la ressource que vous souhaitez charger, puis vous obtenez la chaîne localisée, également utile si votre énumérateur a des valeurs avec plusieurs mots.

brafales
la source
3

Ceci est ma version de la méthode d'assistance. J'utilise ceci:

var values = from int e in Enum.GetValues(typeof(TEnum))
             select new { ID = e, Name = Enum.GetName(typeof(TEnum), e) };

Au lieu de:

var values = from TEnum e in Enum.GetValues(typeof(TEnum))
           select new { ID = (int)Enum.Parse(typeof(TEnum),e.ToString())
                     , Name = e.ToString() };

C'est ici:

public static SelectList ToSelectList<TEnum>(this TEnum self) where TEnum : struct
    {
        if (!typeof(TEnum).IsEnum)
        {
            throw new ArgumentException("self must be enum", "self");
        }

        Type t = typeof(TEnum);

        var values = from int e in Enum.GetValues(typeof(TEnum))
                     select new { ID = e, Name = Enum.GetName(typeof(TEnum), e) };

        return new SelectList(values, "ID", "Name", self);
    }
Vadim Sentiaev
la source
3

Vous pouvez également utiliser mes HtmlHelpers personnalisés dans Griffin.MvcContrib. Le code suivant:

@Html2.CheckBoxesFor(model => model.InputType) <br />
@Html2.RadioButtonsFor(model => model.InputType) <br />
@Html2.DropdownFor(model => model.InputType) <br />

Génère:

entrez la description de l'image ici

https://github.com/jgauffin/griffin.mvccontrib

jgauffin
la source
3

Je voudrais répondre à cette question d'une manière différente où, l'utilisateur n'a rien à faire dans controllerouLinq expression. Par ici...

j'ai un ENUM

public enum AccessLevelEnum
    {
        /// <summary>
        /// The user cannot access
        /// </summary>
        [EnumMember, Description("No Access")]
        NoAccess = 0x0,

        /// <summary>
        /// The user can read the entire record in question
        /// </summary>
        [EnumMember, Description("Read Only")]
        ReadOnly = 0x01,

        /// <summary>
        /// The user can read or write
        /// </summary>
        [EnumMember, Description("Read / Modify")]
        ReadModify = 0x02,

        /// <summary>
        /// User can create new records, modify and read existing ones
        /// </summary>
        [EnumMember, Description("Create / Read / Modify")]
        CreateReadModify = 0x04,

        /// <summary>
        /// User can read, write, or delete
        /// </summary>
        [EnumMember, Description("Create / Read / Modify / Delete")]
        CreateReadModifyDelete = 0x08,

        /*/// <summary>
        /// User can read, write, or delete
        /// </summary>
        [EnumMember, Description("Create / Read / Modify / Delete / Verify / Edit Capture Value")]
        CreateReadModifyDeleteVerify = 0x16*/
    }

Maintenant, je peux simplement créer un dropdownen utilisant ceci enum.

@Html.DropDownList("accessLevel",new SelectList(AccessLevelEnum.GetValues(typeof(AccessLevelEnum))),new { @class = "form-control" })

OU

@Html.DropDownListFor(m=>m.accessLevel,new SelectList(AccessLevelEnum.GetValues(typeof(AccessLevelEnum))),new { @class = "form-control" })

Si vous souhaitez sélectionner un index, essayez ceci

@Html.DropDownListFor(m=>m.accessLevel,new SelectList(AccessLevelEnum.GetValues(typeof(AccessLevelEnum)) , AccessLevelEnum.NoAccess ),new { @class = "form-control" })

Ici, j'ai utilisé AccessLevelEnum.NoAccesscomme paramètre supplémentaire pour sélectionner par défaut la liste déroulante.

gdmanandamohon
la source
3

J'ai trouvé une réponse ici . Cependant, certaines de mes énumérations ont un [Description(...)]attribut, j'ai donc modifié le code pour prendre en charge cela:

    enum Abc
    {
        [Description("Cba")]
        Abc,

        Def
    }


    public static MvcHtmlString EnumDropDownList<TEnum>(this HtmlHelper htmlHelper, string name, TEnum selectedValue)
    {
        IEnumerable<TEnum> values = Enum.GetValues(typeof(TEnum))
            .Cast<TEnum>();

        List<SelectListItem> items = new List<SelectListItem>();
        foreach (var value in values)
        {
            string text = value.ToString();

            var member = typeof(TEnum).GetMember(value.ToString());
            if (member.Count() > 0)
            {
                var customAttributes = member[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
                if (customAttributes.Count() > 0)
                {
                    text = ((DescriptionAttribute)customAttributes[0]).Description;
                }
            }

            items.Add(new SelectListItem
            {
                Text = text,
                Value = value.ToString(),
                Selected = (value.Equals(selectedValue))
            });
        }

        return htmlHelper.DropDownList(
            name,
            items
            );
    }

J'espère que cela pourra aider.

Alkasai
la source
Je veux renvoyer un membre de type = DropdownList. Je suis bon avec Text = DescriptionAttribute mais j'ai du mal à obtenir la valeur int de Value
NanaFadanvis
2

@Simon Goldstone: Merci pour votre solution, elle peut parfaitement être appliquée dans mon cas. Le seul problème est que j'ai dû le traduire en VB. Mais maintenant c'est fait et pour gagner du temps aux autres (au cas où ils en auraient besoin) je le mets ici:

Imports System.Runtime.CompilerServices
Imports System.ComponentModel
Imports System.Linq.Expressions

Public Module HtmlHelpers
    Private Function GetNonNullableModelType(modelMetadata As ModelMetadata) As Type
        Dim realModelType = modelMetadata.ModelType

        Dim underlyingType = Nullable.GetUnderlyingType(realModelType)

        If Not underlyingType Is Nothing Then
            realModelType = underlyingType
        End If

        Return realModelType
    End Function

    Private ReadOnly SingleEmptyItem() As SelectListItem = {New SelectListItem() With {.Text = "", .Value = ""}}

    Private Function GetEnumDescription(Of TEnum)(value As TEnum) As String
        Dim fi = value.GetType().GetField(value.ToString())

        Dim attributes = DirectCast(fi.GetCustomAttributes(GetType(DescriptionAttribute), False), DescriptionAttribute())

        If Not attributes Is Nothing AndAlso attributes.Length > 0 Then
            Return attributes(0).Description
        Else
            Return value.ToString()
        End If
    End Function

    <Extension()>
    Public Function EnumDropDownListFor(Of TModel, TEnum)(ByVal htmlHelper As HtmlHelper(Of TModel), expression As Expression(Of Func(Of TModel, TEnum))) As MvcHtmlString
        Return EnumDropDownListFor(htmlHelper, expression, Nothing)
    End Function

    <Extension()>
    Public Function EnumDropDownListFor(Of TModel, TEnum)(ByVal htmlHelper As HtmlHelper(Of TModel), expression As Expression(Of Func(Of TModel, TEnum)), htmlAttributes As Object) As MvcHtmlString
        Dim metaData As ModelMetadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData)
        Dim enumType As Type = GetNonNullableModelType(metaData)
        Dim values As IEnumerable(Of TEnum) = [Enum].GetValues(enumType).Cast(Of TEnum)()

        Dim items As IEnumerable(Of SelectListItem) = From value In values
            Select New SelectListItem With
            {
                .Text = GetEnumDescription(value),
                .Value = value.ToString(),
                .Selected = value.Equals(metaData.Model)
            }

        ' If the enum is nullable, add an 'empty' item to the collection
        If metaData.IsNullableValueType Then
            items = SingleEmptyItem.Concat(items)
        End If

        Return htmlHelper.DropDownListFor(expression, items, htmlAttributes)
    End Function
End Module

Fin Vous l'utilisez comme ceci:

@Html.EnumDropDownListFor(Function(model) (model.EnumField))
Michal B.
la source
2

J'ai fini par créer des méthodes d'extension pour faire ce qui est essentiellement la réponse acceptée ici. La dernière moitié du Gist traite spécifiquement d'Enum.

https://gist.github.com/3813767

Nick Albrecht
la source
2
@Html.DropdownListFor(model=model->Gender,new List<SelectListItem>
{
 new ListItem{Text="Male",Value="Male"},
 new ListItem{Text="Female",Value="Female"},
 new ListItem{Text="--- Select -----",Value="-----Select ----"}
}
)
Shahnawaz
la source
2
@Html.DropDownListFor(model => model.MaritalStatus, new List<SelectListItem> 
{  

new SelectListItem { Text = "----Select----", Value = "-1" },


new SelectListItem { Text = "Marrid", Value = "M" },


 new SelectListItem { Text = "Single", Value = "S" }

})
Vicky
la source
Je pense que ce n'est pas une réponse valable, il n'utilise pas du tout l'énumération pour remplir la liste déroulante.
Andrew