¿Cómo se crea una lista desplegable a partir de una enumeración en ASP.NET MVC?

671

Estoy tratando de usar el Html.DropDownListmétodo de extensión, pero no puedo entender cómo usarlo con una enumeración.

Digamos que tengo una enumeración como esta:

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

¿Cómo hago para crear un menú desplegable con estos valores usando el Html.DropDownListmétodo de extensión?

¿O es mi mejor apuesta simplemente crear un bucle for y crear los elementos HTML manualmente?

Kevin Pang
fuente

Respuestas:

842

Para MVC v5.1 use Html.EnumDropDownListFor

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

Para MVC v5 use EnumHelper

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

Para MVC 5 y menos

Puse la respuesta de Rune en un método de extensión:

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

Esto te permite escribir:

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

por using MyApp.Common

Martin Faartoft
fuente
13
No pude hacerlo funcionar, ¿podrías ayudarme? Cuando hago Post.PostType.ToSelectList (); ¿No reconoce la extensión?
Barbaros Alp
3
No pude hacer que esto funcione tampoco. ¿Es Status su propiedad Enum en la clase de tarea? ¿No es este uno de los valores enumerados?
Daryl
99
Puede restringirlo un poco con: where T: struct, IConvertible See: stackoverflow.com/questions/79126/…
Richard Garside
8
Esto es genial Si alguien tiene problemas con la implementación, así es como lo hice. Se agregó una clase EnumHelpers a la carpeta HtmlHelpers. Usó el código anterior. Se agregó el espacio de nombres según la recomendación de @TodK: <add namespace = "xxx.HtmlHelpers" />. Luego lo usé en una página de afeitar como esta: @ Html.DropDownListFor (model => model.Status, @ Model.Status.ToSelectList ()) HTH
Jeff Borden
66
Tenga en cuenta que en los más nuevos ASP.NET MVChay una forma nativa: stackoverflow.com/a/22295360/1361084
Ofiris
359

Sé que llego tarde a la fiesta en esto, pero pensé que podría encontrar esta variante útil, ya que esta también le permite usar cadenas descriptivas en lugar de constantes de enumeración en el menú desplegable. Para hacer esto, decore cada entrada de enumeración con un atributo [System.ComponentModel.Description].

Por ejemplo:

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

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

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

Aquí está mi código:

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

Luego puede hacer esto desde su punto de vista:

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

¡Espero que esto te ayude!

** EDITAR 2014-ENE-23: Microsoft acaba de lanzar MVC 5.1, que ahora tiene una función EnumDropDownListFor. Lamentablemente, no parece respetar el atributo [Descripción], por lo que el código anterior sigue en pie. Consulte la sección Enum en las notas de la versión de Microsoft para MVC 5.1.

Actualización: sin embargo, admite el atributo Display[Display(Name = "Sample")] , por lo que uno puede usarlo.

[Actualización: acabo de notar esto y el código parece una versión extendida del código aquí: https://blogs.msdn.microsoft.com/stuartleeks/2010/05/21/asp-net-mvc-creating-a- dropdownlist-helper-for-enums / , con un par de adiciones. Si es así, la atribución parecería justa ;-)]

SimonGoldstone
fuente
28
+1 Encontré la más útil de todas las respuestas aquí. Pude convertir esto en un código altamente reutilizable. ¡Gracias!
Ed Charbeneau
43
Visual Studio tiene un error extraño en el que si no hace referencia System.Web.Mvc.Html, dice que DropDownListForno se puede encontrar, pero tampoco puede resolverlo. Tienes que hacer manualmente using System.Web.Mvc.Html;. Solo así que ya sabes.
Kezzer
1
Tengo una variante de esto en una esencia que utilizamos en todos nuestros proyectos: gist.github.com/1287511
kamranicus
1
Gran solución, gracias, sería aún mejor si puede almacenar en caché los resultados de GetEnumDescription
M. Mennan Kara
17
¡El nuevo MVC 5.1 EnumDropDownListFor no usa [Descripción ("")] pero sí usa [Display (Name = "")]! Disfruta :)
Supergibbs
195

En ASP.NET MVC 5.1 , agregaron el EnumDropDownListFor()ayudante, por lo que no se necesitan extensiones personalizadas:

Modelo :

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

Vista :

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

Usando Tag Helper (ASP.NET MVC 6) :

<select asp-for="@Model.SelectedValue" asp-items="Html.GetEnumSelectList<MyEnum>()">
Ofiris
fuente
21
Esto debe ser elevado al primer lugar de alguna manera
3
Debería crear una nueva pregunta que sea específica para MVC 5.1 y poner esto como respuesta, luego enviarme un enlace a la publicación para que pueda votar a un favorito.
Kevin Heidt
2
Lo que no me gusta de EnumDropDownListFor () es que guarda en la base de datos el valor int de la enumeración, no el texto, por lo que si alguna vez elige agregar un nuevo elemento de enumeración, necesariamente debe ir al final de la lista , para no perder la relación de los valores int de la base de datos guardada con las posiciones originales de los elementos de enumeración. Esa es una restricción innecesaria si se guarda el texto. Además, prefiero poder mirar el db y ver un texto, en lugar de ints donde luego tengo que buscar los valores del texto en otro lugar. De lo contrario, este ayudante html es muy conveniente de usar.
Giovanni
2
@Giovanni: puede especificar sus propios valores numéricos.
Tommy
1
@Giovanni El diseño estricto debe asignar un valor para cada entrada de enumeración (si es importante), de lo contrario, el valor no debería importar (y, por lo tanto, colocar los nuevos al final no debería ser un problema). Guardar valores int es mejor cuando se trata de guardar almacenamiento y aumentar el rendimiento (al realizar alguna búsqueda).
Rey Rey
130

Me encontré con el mismo problema, encontré esta pregunta y pensé que la solución provista por Ash no era lo que estaba buscando; Tener que crear el HTML yo mismo significa menos flexibilidad en comparación con la Html.DropDownList()función incorporada.

Resulta que C # 3 etc. hace que esto sea bastante fácil. Tengo un enumllamado 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);

Esto crea un buen ol ' SelectListque se puede usar como está acostumbrado en la vista:

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

El tipo anónimo y LINQ lo hacen mucho más elegante en mi humilde opinión. Sin ofender, Ash. :)

Rune Jacobsen
fuente
¡buena respuesta! esperaba que alguien usara linq y SelectList :) Me alegro de haberlo revisado aquí primero.
Pure.Krome
1
ID = s me da el DataTextField no el valor? Cuál podría ser la razón ? Gracias
Barbaros Alp
1
Rune, utilicé este mismo método y el DropDownList SÍ renderiza aún cuando se publica en el servidor, no guarda el valor que había seleccionado.
clockwiseq
55
@BarbarosAlp Para que la ID sea un número, necesitarás enviar la enumeración a un int:select new { ID = (int)s, Name = s.ToString() };
Keith
Esta es la respuesta que más me gusta por su simplicidad. Es una pena que no haya recibido suficiente crédito ya que la respuesta seleccionada utilizó su solución.
anar khalilov
63

Aquí hay una mejor solución encapsulada:

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

Digamos que aquí está tu modelo:

ingrese la descripción de la imagen aquí

Uso de muestra:

ingrese la descripción de la imagen aquí

IU generada: ingrese la descripción de la imagen aquí

Y HTML generado

ingrese la descripción de la imagen aquí

La instantánea del código fuente de la extensión auxiliar:

ingrese la descripción de la imagen aquí

Puede descargar el proyecto de muestra desde el enlace que proporcioné.

EDITAR: Aquí está el código:

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
fuente
2
Solo mi opinión, pero creo que esta respuesta es mucho más limpia que la respuesta aceptada. Particularmente me gusta la opción de usar el atributo Descripción. Agregué el código para que la gente pueda copiarlo / pegarlo sin descargarlo.
Ben Mills
Llame al método de extensión como EnumDropDownListFor en lugar de DropDownListFor Usage: -> @ Html.EnumDropDownListFor (x => x.Gender)
sandeep talabathula
Para alguien que busca agregar un elemento más "Seleccione" devuelva htmlHelper.DropDownListFor (expresión, createSelectList (expresión.ReturnType, selected, firstElement), "Seleccione");
Sandeep
1
¡Funciona bien! Sin embargo, en la página Detalles, DisplayFor () muestra el valor seleccionado de la enumeración en lugar de la descripción correspondiente. Supongo que esto requiere una sobrecarga para DisplayFor () para el tipo de enumeración. ¿Alguien tiene solución para esto?
corix010
48

Html.DropDownListFor solo requiere un IEnumerable, por lo que una alternativa a la solución de Prise es la siguiente. Esto te permitirá simplemente escribir:

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

[Donde SelectedItemType es un campo en su modelo de tipo ItemTypes, y su modelo no es nulo]

Además, en realidad no es necesario que genérico el método de extensión, ya que puede usar enumValue.GetType () en lugar de typeof (T).

EDITAR: la solución integrada de Simon aquí también, y el método de extensión ToDescription incluido.

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
fuente
No funciona para mí ('System.NullReferenceException: referencia de objeto no establecida en una instancia de un objeto') ... Mi 'Modelo' es nulo ... probablemente tenga algo que ver con 'GetNonNullableModelType' que Simon tiene. incluido
Estudiante
@Cristi, tienes razón, esta solución no está diseñada para usarse en una condición en la que tu modelo sea nulo. Intento evitar ese diseño en general e inicializar a un modelo "Vacío" cuando ese es el caso.
Zaid Masud
Bueno, soy nuevo en asp mvc, pero tengo bastante experiencia en .Net. Gracias, investigaré lo que estabas sugiriendo. Por cierto, su extensión ToDescription está muy fuera del alcance de 'Enum'. Supongo que va bien para el 'Objeto' en sí. Esto es lo que usé cuando tomé el código de Simon y lo limpié un poco más.
Estudiante
@Cristi, es difícil entender lo que quieres decir con "muy fuera del alcance de 'Enum'", pero parece que estás diciendo que el método de extensión ToDescription no está fuertemente tipeado en la enumeración ItemTypes. Esto es intencional y hace que el método de extensión sea usable genéricamente por todas las enumeraciones. Si lo está comparando con un método de extensión genérico, hay ventajas y desventajas de cada enfoque. En particular, si generecize no puede limitarlo solo a las enumeraciones.
Zaid Masud
1
Genial, gracias. Cambié value.ToString para usar una extensión FromCamelCase en caso de que no hubiera una descripción. Así es como ruedo :)
Valamas
33

Entonces, sin funciones de extensión, si estás buscando simple y fácil ... Esto es lo que hice

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

donde XXXXX.Sites.YYYY.Models.State es una enumeración

Probablemente sea mejor hacer una función auxiliar, pero cuando el tiempo es corto, esto hará el trabajo.

Marty Trenouth
fuente
Bueno, esto funcionó al completar el menú desplegable, pero ¿cómo se configura el valor predeterminado seleccionado en la sintaxis de Razor para Html.DropDownListFor? Quiero mostrar una tabla con cuadros combinados de enumeraciones y también necesito establecer el valor seleccionado de acuerdo con lo que era antes.
Johncl
2
Debería poder pasar un segundo parámetro con el valor seleccionado a la nueva función SelectList (IEnumerable, object). Documentación de MSDN: msdn.microsoft.com/en-us/library/dd460123.aspx
Marty Trenouth
23

Ampliando las respuestas de Premio y Runa, si desea que el atributo de valor de los elementos de su lista de selección se asigne al valor entero del tipo Enumeración, en lugar del valor de cadena, use el siguiente código:

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

En lugar de tratar cada valor de Enumeration como un objeto TEnum, podemos tratarlo como un objeto y luego convertirlo en un entero para obtener el valor sin caja.

Nota: También agregué una restricción de tipo genérico para restringir los tipos para los que esta extensión está disponible solo para estructuras (tipo base de Enum), y una validación de tipo en tiempo de ejecución que garantiza que la estructura que se pasa es realmente una Enum.

Actualización 23/10/12: Se agregó un parámetro de tipo genérico para el tipo subyacente y se solucionó el problema de no compilación que afecta a .NET 4+.

Nathan Taylor
fuente
¡Gracias! Esta fue la respuesta que necesitaba. Estoy almacenando el valor entero de Enum como una columna en la base de datos y esta solución parece estar funcionando perfectamente.
grimus
pero ¿qué pasa si estás almacenando un char y no un int? cual es mi caso obviamente podría cambiar (int) a (char) pero ¿qué tal hacer esto genérico también? ¿como hacer eso?
Stefanvds
@Stefandvds Esta es una gran pregunta con respecto al envío al tipo representado correcto. Según las pruebas que acabo de realizar, parecería que la única forma en que podría lograr esto sería especificando el tipo real como otro parámetro de tipo. ToSelectList<TEnum, TEnumValue>(this TEnum enumObj) { ... }
Nathan Taylor
@Stefandvds Vea esta pregunta .
Nathan Taylor
Si los valores de su enumeración son int, simplemente puede usar Value = Convert.ToInt32(e). (int)eno compila :(
Andrew
11

Para resolver el problema de obtener el número en lugar del texto usando el método de extensión 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
fuente
Eso es lo que estaba buscando, aunque es más feo de lo que pensaba que debía ser. Me pregunto por qué Visual Studio no va a dejar que lances directamente ea int.
Andrew
O simplemente podrías usarlo ID = Convert.ToInt32(e).
Andrew
11

Una forma súper fácil de hacer esto, sin todas las extensiones que parecen excesivas, es esta:

Tu enumeración:

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

Dentro de su controlador, vincule Enum a una Lista:

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

Después de eso, tíralo a una ViewBag:

    ViewBag.RequiredLevel = new SelectList(myLevels);

Finalmente, simplemente vincúlelo a la Vista:

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

Esta es, con mucho, la forma más fácil que encontré y no requiere extensiones ni nada tan loco.

ACTUALIZACIÓN : Vea el comentario de Andrews a continuación.

revs Louie Bacaj
fuente
3
Esto solo funciona si no ha asignado ningún valor a su enumeración. Si lo hubiera hecho Level1 = 1, entonces el valor del menú desplegable sería en "Level1"lugar de 1.
Andrew
11

La mejor solución que encontré para esto fue combinar este blog con la respuesta de Simon Goldstone .

Esto permite el uso de la enumeración en el modelo. Esencialmente, la idea es usar una propiedad entera así como la enumeración y emular la propiedad entera.

Luego use el atributo [System.ComponentModel.Description] para anotar el modelo con su texto de visualización, y use una extensión "EnumDropDownListFor" en su vista.

Esto hace que tanto la vista como el modelo sean muy legibles y mantenibles.

Modelo:

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

Ver:

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

Extensión (directamente de la respuesta de Simon Goldstone , incluida aquí para completar):

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
fuente
Esto no funciona, MVC 4 Razor. En la vista o tiempo de ejecución, error = "La llamada es ambigua entre los siguientes métodos o propiedades 'LDN.Extensions.MvcExtensions.EnumDropDownListFor <MyModel, LDN.Models.YesPartialNoEnum?> (System.Web.Mvc.HtmlHelper <MyModel>, System .Linq.Expressions.Expression <System.Func <MyModel, LDN.Models.YesPartialNoEnum? >>) 'y .... "y ese mismo método exacto con los mismos accesorios repetidos nuevamente (no se permiten suficientes caracteres aquí).
Marc
8
@Html.DropDownListFor(model => model.Type, Enum.GetNames(typeof(Rewards.Models.PropertyType)).Select(e => new SelectListItem { Text = e }))
Señor calabaza
fuente
¡Bueno! ¿Cómo obtener valor y texto de enum de esta manera? Quiero decir que tengo SomeEnum {some1 = 1, some2 = 2} Necesito obtener números (1, 2) para el valor y texto (some1, some2) para el texto de selectlist
Dmitresky
7

Estas son las respuestas de Rune & Prize alteradas para usar el intvalor Enum como ID.

Enum de muestra:

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

Método de extensión:

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

Muestra de uso:

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

Recuerde importar el espacio de nombres que contiene el método de extensión

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

Muestra de HTML generado:

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

Tenga en cuenta que el elemento que utiliza para activar ToSelectListes el elemento seleccionado.

Señor flibble
fuente
O simplemente podrías usarlo Id = Convert.ToInt32(e).
Andrew
6

Esta es la versión para 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)
usuario550950
fuente
Eso funcionará solo si su enumeración consta de valores contiguos que comienzan con 0. Una enumeración de indicadores no funcionaría con esto. Sin embargo, el uso creativo de la selección indexada.
Suncat2000
6

En .NET Core puedes usar esto:

@Html.DropDownListFor(x => x.Foo, Html.GetEnumSelectList<MyEnum>())
Edad de oro
fuente
1
O con la etiqueta de ayuda <select asp-for="Model.Foo" class="form-control" asp-items="@Html.GetEnumSelectList<MyEnum>()"></select>.
Pascal R.
Sí, diría que los ayudantes de etiqueta son aún mejores ya que el formato está más cerca del HTML puro;)
GoldenAge
También puede hacer esto @ Html.DropDownListFor (x => x.Foo, Html.GetEnumSelectList (typeof (FooEnum)))
Fereydoon Barikzehy
5

Basándose en la respuesta de Simon, un enfoque similar es hacer que los valores de Enum se muestren desde un archivo de Recursos, en lugar de en un atributo de descripción dentro del Enum. Esto es útil si su sitio necesita ser presentado en más de un idioma y si tuviera un archivo de recursos específico para Enums, podría ir un paso más allá y tener solo los valores de Enum, en su Enum y hacer referencia a ellos desde la extensión una convención como [EnumName] _ [EnumValue] - ¡en última instancia, menos tipeo!

La extensión se ve así:

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

Recursos en el archivo Enums.Resx que se parecen a ItemTypes_Movie: Film

Otra cosa que me gusta hacer es, en lugar de llamar al método de extensión directamente, prefiero llamarlo con un @ Html.EditorFor (x => x.MyProperty), o idealmente tener todo el formulario, en una ordenada @ Html.EditorForModel (). Para hacer esto, cambio la plantilla de cadena para que se vea así

@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 esto le interesa, he puesto una respuesta mucho más detallada aquí en mi blog:

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

PaulTheCyclist
fuente
5

Bueno, realmente llego tarde a la fiesta, pero por lo que vale, he blogueado sobre este mismo tema mediante el cual creo una EnumHelperclase que permite una transformación muy fácil.

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

En tu controlador:

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

En tu vista:

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

La clase auxiliar:

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

Llegué muy tarde en este caso, pero acabo de encontrar una forma realmente genial de hacerlo con una línea de código, si está contento de agregar la Melodía sin restricciones paquete NuGet sin restricciones (una biblioteca pequeña y agradable de Jon Skeet).

Esta solución es mejor porque:

  1. Asegura (con restricciones de tipo genérico) que el valor realmente es un valor de enumeración (debido a Melodía sin restricciones)
  2. Evita el boxeo innecesario (debido a la melodía sin restricciones)
  3. Almacena en caché todas las descripciones para evitar usar la reflexión en cada llamada (debido a Melodía sin restricciones)
  4. ¡Es menos código que las otras soluciones!

Entonces, estos son los pasos para que esto funcione:

  1. En Package Manager Console, "Install-Package UnconstrainedMelody"
  2. Agregue una propiedad en su modelo así:

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

Ahora que tiene la lista de elementos SelectListItem expuestos en su modelo, puede usar @ Html.DropDownList o @ Html.DropDownListFor usando esta propiedad como fuente.

nootn
fuente
+1 por usar el código de Jon Skeet :), aunque es broma
Vamsi
3

Otra solución a este método de extensión: la versión actual no seleccionó el valor actual de la enumeración. Arreglé la última línea:

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

Si desea agregar soporte de localización, simplemente cambie el método s.toString () a algo como esto:

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

Aquí el tipo de (Recursos) es el recurso que desea cargar, y luego obtiene la Cadena localizada, también útil si su enumerador tiene valores con varias palabras.

brafales
fuente
3

Esta es mi versión del método auxiliar. Yo uso esto:

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

En lugar de eso:

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

Aquí está:

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

También puede usar mis HtmlHelpers personalizados en Griffin.MvcContrib. El siguiente código:

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

Genera:

ingrese la descripción de la imagen aquí

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

jgauffin
fuente
3

Me gustaría responder a esta pregunta de una manera diferente donde, el usuario no necesita hacer nada controlleroLinq expresión. De esta manera...

tengo 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*/
    }

Ahora puedo simplemente crear un dropdownusando esto enum.

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

O

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

Si desea seleccionar un índice, intente esto

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

Aquí lo he usado AccessLevelEnum.NoAccesscomo un parámetro adicional para la selección predeterminada del menú desplegable.

gdmanandamohon
fuente
3

Encontré una respuesta aquí . Sin embargo, algunas de mis enumeraciones tienen [Description(...)]atributo, por lo que modifiqué el código para proporcionar soporte para eso:

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

Espero que ayude.

Alkasai
fuente
Quiero devolver un miembro de type = DropdownList. Soy bueno con el
atributo
2

@ Simon Goldstone: Gracias por su solución, se puede aplicar perfectamente en mi caso. El único problema es que tuve que traducirlo a VB. Pero ahora está hecho y para ahorrar tiempo a otras personas (en caso de que lo necesiten) lo puse aquí:

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 Lo usas así:

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

Terminé creando métodos de extensión para hacer lo que es esencialmente la respuesta de aceptación aquí. La última mitad del Gist trata con Enum específicamente.

https://gist.github.com/3813767

Nick Albrecht
fuente
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
fuente
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
fuente
Creo que esta no es una respuesta válida, no está usando la enumeración para completar el menú desplegable.
Andrew