Como você cria uma lista suspensa a partir de uma enumeração no ASP.NET MVC?

671

Estou tentando usar o Html.DropDownListmétodo de extensão, mas não consigo descobrir como usá-lo com uma enumeração.

Digamos que eu tenho uma enumeração como esta:

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

Como faço para criar uma lista suspensa com esses valores usando o Html.DropDownListmétodo de extensão?

Ou é a minha melhor aposta para simplesmente criar um loop for e criar os elementos HTML manualmente?

Kevin Pang
fonte

Respostas:

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

Coloquei a resposta de Rune em um método de extensão:

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

Isso permite que você escreva:

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

de using MyApp.Common

Martin Faartoft
fonte
13
Eu não poderia fazê-lo funcionar, você poderia por favor ajudar. Quando eu faço Post.PostType.ToSelectList (); ele não reconhece a extensão?
Barbaros Alp
3
Também não consegui fazer isso funcionar. Status é sua propriedade Enum na classe de tarefas? Este não é um dos valores enumerados?
Daryl
9
Você pode restringi-lo um pouco com: where T: struct, IConvertible Veja: stackoverflow.com/questions/79126/…
Richard Garside
8
Isso é legal. Se alguém está lutando com a implementação, aqui está como eu fiz. Adicionada uma classe EnumHelpers à pasta HtmlHelpers. Utilizou o código acima. Adicionado o namespace por recomendação do @TodK: <add namespace = "xxx.HtmlHelpers" />. Então eu usei-o em uma página de barbear como tal: @ Html.DropDownListFor (modelo => model.Status, @ Model.Status.ToSelectList ()) HTH
Jeff Borden
6
Observe que nas ASP.NET MVC
versões
359

Eu sei que estou atrasado para a festa, mas achei que você poderia achar essa variante útil, pois essa também permite que você use sequências descritivas em vez de constantes de enumeração no menu suspenso. Para fazer isso, decore cada entrada de enumeração com um atributo [System.ComponentModel.Description].

Por exemplo:

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

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

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

Aqui está o meu 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);
    }

Você pode fazer isso na sua visão:

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

Espero que isso ajude você!

** EDIT 2014-JAN-23: A Microsoft acaba de lançar o MVC 5.1, que agora possui o recurso EnumDropDownListFor. Infelizmente, ele não parece respeitar o atributo [Descrição], portanto o código acima ainda permanece. Consulte a seção Enum nas notas de versão da Microsoft para MVC 5.1.

Atualização: No entanto, ele suporta o atributo Display[Display(Name = "Sample")] , para que você possa usá-lo.

[Atualização - acabei de notar isso, e o código se parece com uma versão estendida do código aqui: https://blogs.msdn.microsoft.com/stuartleeks/2010/05/21/asp-net-mvc-creating-a- dropdownlist-helper-for-enums / , com algumas adições. Nesse caso, a atribuição pareceria justa ;-)]

SimonGoldstone
fonte
28
+1 Achei isso mais útil de todas as respostas aqui. Consegui transformar isso em um pedaço de código altamente reutilizável. Obrigado!
22811 Ed Charbeneau
43
O Visual Studio tem um bug estranho, onde se você não fizer referência System.Web.Mvc.Html, ele diz que DropDownListFornão pode ser encontrado, mas também não pode resolvê-lo. Você tem que fazer manualmente using System.Web.Mvc.Html;. Só para você saber.
Kezzer #
1
Eu tenho uma variante deste, em uma essência que usamos em todos os nossos projectos: gist.github.com/1287511
kamranicus
1
Grande solução, graças, seria ainda melhor se você pode armazenar em cache os resultados de GetEnumDescription
M. Mennan Kara
17
O novo MVC 5.1 EnumDropDownListFor não usa [Description ("")]], mas usa [Display (Name = "")]! Aprecie :)
Supergibbs
195

No ASP.NET MVC 5.1 , eles adicionaram o EnumDropDownListFor()auxiliar, portanto, não há necessidade de extensões personalizadas:

Modelo :

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

Ver :

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

Usando o Assistente de Tags (ASP.NET MVC 6) :

<select asp-for="@Model.SelectedValue" asp-items="Html.GetEnumSelectList<MyEnum>()">
Ofiris
fonte
21
Isso precisa ser colidido até ao primeiro lugar de alguma forma
3
Você deve criar uma nova pergunta específica para o MVC 5.1 e colocá-la como resposta. Em seguida, envie-me um link para a postagem para que eu possa aprovar uma favorita.
21415 Kevin Heidt
2
O que eu não gosto em EnumDropDownListFor () é que ele salva no banco de dados o valor int da enumeração, não o texto; portanto, se você optar por adicionar um novo item de enumeração, ele deve necessariamente ir ao final da lista , para não perder a relação dos valores int dos bancos de dados salvos com as posições originais dos itens da enumeração. Essa é uma restrição desnecessária se o texto for salvo. Além disso, eu prefiro olhar para o banco de dados e ver um texto, em vez de ints, onde preciso procurar os valores do texto em outro lugar. Caso contrário, este auxiliar html é muito conveniente de usar.
Giovanni
2
@Giovanni - você pode especificar seus próprios valores numéricos.
Tommy
1
@Giovanni O design estrito deve atribuir valor para cada entrada de enum (se for importante), caso contrário, o valor não deve importar (portanto, colocar os novos no final não deve ser um problema). Salvar valores int é melhor quando se trata de salvar armazenamento e aumentar o desempenho (ao realizar alguma pesquisa).
King King
130

Encontrei o mesmo problema, encontrei essa pergunta e pensei que a solução fornecida por Ash não era o que eu estava procurando; Ter que criar o HTML sozinho significa menos flexibilidade em comparação com a Html.DropDownList()função interna.

Acontece que C # 3 etc. torna isso muito fácil. Eu tenho um enumchamado 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);

Isso cria uma boa SelectListaparência que pode ser usada como você está acostumado na exibição:

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

O tipo anônimo e o LINQ tornam esse IMHO muito mais elegante. Sem querer ofender, Ash. :)

Rune Jacobsen
fonte
boa resposta! Eu estava esperando que alguém usasse o linq e o SelectList :) Fico feliz que verifiquei aqui primeiro!
22310 Pure.Krome
1
ID = s me dê o DataTextField, não o valor? O que pode ser a razão ? Obrigado
Barbaros Alp
1
Rune, usei esse mesmo método e o DropDownList DOES ainda é renderizado quando é postado no servidor, mas não salva o valor que eu selecionei.
clockwiseq
5
@BarbarosAlp Para ID para ser um número que você vai precisar para lançar o enum para um int:select new { ID = (int)s, Name = s.ToString() };
Keith
Esta é a resposta que eu mais gosto devido à sua simplicidade. Pena que você não recebeu crédito suficiente, pois a resposta selecionada usou sua solução.
Anar khalilov
63

Aqui está uma solução encapsulada melhor:

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

Diga aqui está o seu modelo:

insira a descrição da imagem aqui

Uso da amostra:

insira a descrição da imagem aqui

UI gerada: insira a descrição da imagem aqui

E gerado HTML

insira a descrição da imagem aqui

A captura instantânea do código-fonte da extensão auxiliar:

insira a descrição da imagem aqui

Você pode baixar o projeto de amostra no link que forneci.

EDIT: Aqui está o 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
fonte
2
Apenas minha opinião, mas acho que essa resposta é muito mais limpa do que a resposta aceita. Eu particularmente gosto da opção de usar o atributo Descrição. Eu adicionei o código para que as pessoas possam copiar / colar sem fazer o download.
Ben Mills
Chamar o método de extensão como EnumDropDownListFor ao invés de Uso DropDownListFor: -> @ Html.EnumDropDownListFor (x => x.Gender)
sandeep talabathula
Para alguém procurando adicionar mais um elemento "Please Select", retorne htmlHelper.DropDownListFor (expression, createSelectList (expression.ReturnType, selected, firstElement), "Please Select");
Sandeep
1
Funciona bem! No entanto, na página Detalhes, o DisplayFor () mostra o valor selecionado da enumeração em vez da descrição correspondente. Suponho que isso exija uma sobrecarga para DisplayFor () para o tipo enum. Alguém tem solução para isso?
Corix010
48

Html.DropDownListFor requer apenas um IEnumerable, portanto, uma alternativa à solução do Prise é a seguinte. Isso permitirá que você escreva simplesmente:

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

[Onde SelectedItemType é um campo no seu modelo do tipo ItemTypes e seu modelo não é nulo]

Além disso, você realmente não precisa generalizar o método de extensão, pois pode usar enumValue.GetType () em vez de typeof (T).

EDIT: Solução integrada do Simon aqui também, e incluiu o método de extensão 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
fonte
Não funciona para mim ('System.NullReferenceException: referência de objeto não definida para uma instância de um objeto.') ... Meu 'Model' é nulo ... provavelmente tem algo a ver com 'GetNonNullableModelType' que Simon possui. incluído
Aluno
@Cristi, você está certo de que esta solução não se destina a ser usada em uma condição em que seu modelo é nulo. Eu tento evitar esse design em geral e inicializo para um modelo "Vazio" quando esse é o caso.
Zaid Masud 26/03
Bem, eu sou novo no asp mvc, mas tenho bastante experiência em .Net. Obrigado, vou analisar o que você estava sugerindo. Entre sua extensão ToDescription está muito fora do escopo 'Enum'. Eu acho que vai bem para o 'Objeto' em si. Foi isso que usei quando peguei o código de Simon e o limpei um pouco mais.
Apr
@ Cristi, é difícil entender o que você quer dizer com "muito fora do escopo 'Enum'", mas parece que você está dizendo que o método de extensão ToDescription não está fortemente tipado no enum ItemTypes? Isso é intencional e torna o método de extensão genericamente utilizável por todas as enumerações. Se você o estiver comparando a um método de extensão genérico, existem prós e contras de cada abordagem. Em particular, se você gera, não pode restringir apenas as enumerações.
Zaid Masud 27/03
1
Ótimo, com agradecimentos. Alterei value.ToString para usar uma extensão FromCamelCase, caso não houvesse descrição. É assim que eu rolo :)
214 Valamas
33

Portanto, sem as funções de extensão, se você estiver procurando por algo simples e fácil .. Foi o que eu fiz

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

onde XXXXX.Sites.YYYY.Models.State é uma enumeração

Provavelmente é melhor executar a função auxiliar, mas quando o tempo é curto, isso fará o trabalho.

Marty Trenouth
fonte
Bom, isso funcionou ao preencher o menu suspenso, mas como você define o valor selecionado padrão na sintaxe do Razor para Html.DropDownListFor? Quero mostrar uma tabela com caixas de combinação de enumerações e preciso definir o valor selecionado também de acordo com o que era antes.
Johncl
2
Deve poder passar um segundo parâmetro com o valor selecionado para a nova função SelectList (IEnumerable, objeto). Documentação do MSDN: msdn.microsoft.com/en-us/library/dd460123.aspx
Marty Trenouth
23

Expandindo as respostas do Prize e do Rune, se você deseja que o atributo value dos itens da sua lista de seleção seja mapeado para o valor inteiro do tipo Enumeração, em vez do valor da string, use o seguinte 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);
}

Em vez de tratar cada valor de enumeração como um objeto TEnum, podemos tratá-lo como um objeto e, em seguida, convertê-lo em número inteiro para obter o valor sem caixa.

Nota: Também adicionei uma restrição de tipo genérico para restringir os tipos para os quais essa extensão está disponível apenas para estruturas (tipo base do Enum) e uma validação de tipo em tempo de execução que garante que a estrutura transmitida seja realmente um Enum.

Atualização 10/23/12: Adicionado parâmetro de tipo genérico para o tipo subjacente e problema corrigido de não compilação que afeta o .NET 4+.

Nathan Taylor
fonte
Obrigado! Essa era a resposta que eu precisava. Estou armazenando o valor inteiro de um Enum como uma coluna no banco de dados e esta solução parece estar funcionando perfeitamente.
Grimus #
mas e se você estiver armazenando um caractere e não um int? qual é o meu caso. Obviamente eu poderia mudar (int) para (char), mas que tal fazer esse genérico também. Como fazer isso?
Stefanvds
@Stefandvds Essa é uma ótima pergunta em relação à conversão para o tipo representado correto. Com base nos testes que acabei de executar, parece que a única maneira de conseguir isso seria especificando o tipo real como outro parâmetro de tipo. ToSelectList<TEnum, TEnumValue>(this TEnum enumObj) { ... }
Nathan Taylor
@Stefandvds Veja esta pergunta .
Nathan Taylor
Se os valores da sua enumeração são int, você pode simplesmente usar Value = Convert.ToInt32(e). (int)enão compila. :( #
Andrew Andrew
11

Para resolver o problema de obter o número em vez do texto usando o método de extensão do 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
fonte
Era isso que eu estava procurando, embora seja um pouco mais feio do que eu pensava que precisava. Eu me pergunto por que Visual Studio não vai deixar você diretamente lançados epara int.
Andrew
Ou você pode simplesmente usar ID = Convert.ToInt32(e).
Andrew
11

Uma maneira super fácil de fazer isso - sem todo o material de extensão que parece exagero é o seguinte:

Sua enumeração:

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

Dentro do seu controlador, vincule o Enum a uma lista:

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

Depois disso, jogue-o em um ViewBag:

    ViewBag.RequiredLevel = new SelectList(myLevels);

Por fim, basta vinculá-lo à View:

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

Essa é, de longe, a maneira mais fácil que encontrei e não requer extensões nem nada tão louco.

ATUALIZAÇÃO : Veja o comentário de Andrews abaixo.

Louie Bacaj
fonte
3
Isso funciona apenas se você não atribuiu nenhum valor à sua enumeração. Se você tivesse Level1 = 1, o valor da lista suspensa seria em "Level1"vez de 1.
Andrew
11

A melhor solução que encontrei para isso foi combinar este blog com a resposta de Simon Goldstone .

Isso permite o uso da enumeração no modelo. Essencialmente, a idéia é usar uma propriedade inteira, bem como a enumeração, e emular a propriedade inteira.

Em seguida, use o atributo [System.ComponentModel.Description] para anotar o modelo com o texto de exibição e use uma extensão "EnumDropDownListFor" em sua exibição.

Isso torna a visualização e o modelo muito legíveis e fáceis de manter.

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

Visão:

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

Extensão (diretamente da resposta de Simon Goldstone , incluída aqui 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
fonte
Isso não funciona, MVC 4 Razor. Na exibição ou no tempo de execução, error = "A chamada é ambígua entre os seguintes métodos ou propriedades 'LDN.Extensions.MvcExtensions.EnumDropDownListFor <MyModel, LDN.Models.YesPartialNoEnum?> (System.Web.Mvc.HtmlHelper <MyModel>, System .Linq.Expressions.Expression <System.Func <MyModel, LDN.Models.YesPartialNoEnum? >>) 'e .... "e esse mesmo método com os mesmos adereços repetidos novamente (não são permitidos caracteres suficientes aqui).
Marc
9

Você quer usar algo como Enum.GetValues

Garry Shutler
fonte
8
@Html.DropDownListFor(model => model.Type, Enum.GetNames(typeof(Rewards.Models.PropertyType)).Select(e => new SelectListItem { Text = e }))
Mr. Pumpkin
fonte
Boa! Como obter valor e texto do enum dessa maneira? Quero dizer que tenho SomeEnum {some1 = 1, some2 = 2} preciso obter números (1, 2) para valor e texto (some1, some2) para texto da lista de seleção
Dmitresky
7

Estas são as respostas do Rune & Prêmio alteradas para usar o intvalor Enum como o ID.

Enum de amostra:

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

Método de extensão:

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

Amostra de uso:

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

Lembre-se de importar o espaço para nome que contém o método Extension

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

Amostra de HTML gerado:

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

Observe que o item usado para ligar ToSelectListé o item selecionado.

Mr. Flibble
fonte
Ou você pode simplesmente usar Id = Convert.ToInt32(e).
Andrew
6

Esta é a versão do 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
fonte
Isso funcionará apenas se sua enumeração consistir em valores contíguos começando com 0. Uma enumeração de sinalizadores não funcionaria com isso. Uso criativo do Select indexado, no entanto.
precisa saber é o seguinte
6

No .NET Core, você pode apenas usar isso:

@Html.DropDownListFor(x => x.Foo, Html.GetEnumSelectList<MyEnum>())
Era de ouro
fonte
1
Ou com o auxiliar de marca <select asp-for="Model.Foo" class="form-control" asp-items="@Html.GetEnumSelectList<MyEnum>()"></select>.
Pascal R.
sim, eu diria que os auxiliares de tags são ainda melhores, pois o formato está mais próximo do HTML puro;)
GoldenAge
Além disso, você pode fazer isso @ Html.DropDownListFor (x => x.Foo, Html.GetEnumSelectList (typeof (FooEnum)))
Fereydoon Barikzehy
5

Com base na resposta de Simon, uma abordagem semelhante é fazer com que os valores de Enum sejam exibidos em um arquivo de Recurso, em vez de em um atributo de descrição dentro do próprio Enum. Isso é útil se o seu site precisar ser renderizado em mais de um idioma e se você tiver um arquivo de recurso específico para o Enums, poderá dar um passo adiante e ter apenas valores de Enum no seu Enum e referenciá-los a partir da extensão por uma convenção como [EnumName] _ [EnumValue] - em última análise, menos digitação!

A extensão se parece com:

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 no arquivo Enums.Resx parecidos com ItemTypes_Movie: Film

Outra coisa que gosto de fazer é: em vez de chamar o método de extensão diretamente, prefiro chamá-lo com um @ Html.EditorFor (x => x.MyProperty) ou, idealmente, ter apenas o formulário inteiro, em um único @ Html.EditorForModel (). Para fazer isso, altero o modelo da string para ficar assim

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

Se isso lhe interessa, coloquei uma resposta muito mais detalhada aqui no meu blog:

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

PaulTheCyclist
fonte
5

Bem, estou muito atrasado para a festa, mas pelo que vale a pena, escrevi em um blog sobre esse assunto pelo qual crio um EnumHelper classe que permite uma transformação muito fácil.

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

No seu 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);

Na sua opinião:

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

A classe 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
fonte
4

Estou muito atrasado com este, mas encontrei uma maneira muito legal de fazer isso com uma linha de código, se você estiver feliz em adicionar a melodia irrestrita pacote NuGet (uma pequena e agradável biblioteca de Jon Skeet).

Esta solução é melhor porque:

  1. Ele garante (com restrições de tipo genérico) que o valor realmente é um valor enum (devido à melodia irrestrita)
  2. Evita boxe desnecessário (devido à melodia irrestrita)
  3. Ele armazena em cache todas as descrições para evitar o uso de reflexões em todas as chamadas (devido à melodia irrestrita)
  4. É menos código que as outras soluções!

Então, aqui estão as etapas para fazer isso funcionar:

  1. No Console do Gerenciador de Pacotes, "Install-Package UnconstrainedMelody"
  2. Adicione uma propriedade ao seu modelo da seguinte forma:

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

Agora que você tem a Lista de SelectListItem exposta no seu modelo, pode usar o @ Html.DropDownList ou @ Html.DropDownListFor usando essa propriedade como fonte.

nootn
fonte
+1 por usar o código de Jon Skeet :), apenas brincando com um bom
Vamsi
3

Outra correção para esse método de extensão - a versão atual não selecionou o valor atual da enumeração. Eu consertei a última linha:

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

Se você deseja adicionar suporte à localização, basta alterar o método s.toString () para algo como isto:

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

Aqui, o typeof (Resources) é o recurso que você deseja carregar e, em seguida, você obtém a String localizada, também útil se o seu enumerador tiver valores com várias palavras.

Brafales
fonte
3

Esta é a minha versão do método auxiliar. Eu uso isso:

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

Ao invés disso:

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

Aqui 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
fonte
3

Você também pode usar meus HtmlHelpers personalizados no Griffin.MvcContrib. O código a seguir:

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

Gera:

insira a descrição da imagem aqui

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

jgauffin
fonte
3

Gostaria de responder a essa pergunta de uma maneira diferente em que o usuário não precisa fazer nada controllerouLinq expressão. Por aqui...

eu tenho um 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*/
    }

Agora eu simplesmente crio um dropdownusando isso 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" })

Se você deseja selecionar um índice, tente este

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

Aqui eu usei AccessLevelEnum.NoAccesscomo um parâmetro extra para o padrão, selecionando a lista suspensa.

gdmanandamohon
fonte
3

Encontrei uma resposta aqui . No entanto, algumas das minhas enumerações têm [Description(...)]atributo, por isso modifiquei o código para fornecer suporte para isso:

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

Alkasai
fonte
Desejo retornar um membro do tipo = DropdownList. Eu sou bom com o Text = DescriptionAttribute, mas acho que é difícil obter o valor int do valor #
NanaFadanvis
2

@ Simon Goldstone: Obrigado pela sua solução, ela pode ser perfeitamente aplicada no meu caso. O único problema é que tive que traduzi-lo para o VB. Mas agora está pronto e para economizar tempo de outras pessoas (caso elas precisem), coloquei aqui:

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

End Você usa assim:

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

Acabei criando métodos de extensão para fazer o que é essencialmente a resposta aceita aqui. A última metade do Gist lida com Enum especificamente.

https://gist.github.com/3813767

Nick Albrecht
fonte
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
fonte
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
fonte
Eu acho que essa não é uma resposta válida, não está usando a enumeração para preencher a lista suspensa.
Andrew