Jak utworzyć listę rozwijaną z wyliczenia w ASP.NET MVC?


669

Próbuję użyć Html.DropDownListmetody rozszerzenia, ale nie mogę wymyślić, jak jej użyć z wyliczeniem.

Powiedzmy, że mam takie wyliczenie:

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

Jak przejść do tworzenia listy rozwijanej z tymi wartościami przy użyciu Html.DropDownListmetody rozszerzenia?

Czy może najlepiej jest po prostu utworzyć pętlę for i ręcznie utworzyć elementy HTML?

Odpowiedzi:


841

W przypadku MVC v5.1 użyj Html.EnumDropDownListFor

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

W przypadku MVC v5 użyj EnumHelper

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

Dla MVC 5 i niższych

Zwróciłem odpowiedź Rune'a na metodę rozszerzenia:

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

To pozwala napisać:

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

przez using MyApp.Common


13
Nie mogłem tego zrobić, proszę o pomoc. Kiedy robię Post.PostType.ToSelectList (); nie rozpoznaje rozszerzenia?
Barbaros Alp,

3
Nie mogłem też tego uruchomić. Czy Status jest twoją własnością Enum w klasie zadań? Czy to nie jest jedna z wyliczonych wartości?
Daryl

9
Możesz to nieco ograniczyć za pomocą: gdzie T: struct, IConvertible Patrz: stackoverflow.com/questions/79126/…
Richard Garside

8
To jest fajne. Jeśli ktoś ma problemy z wdrożeniem, oto jak to zrobiłem. Dodano klasę EnumHelpers do folderu HtmlHelpers. Użyłem powyższego kodu. Dodano przestrzeń nazw zgodnie z zaleceniem @TodK: <add namespace = "xxx.HtmlHelpers" />. Następnie użyłem go na stronie maszynki do golenia takiej jak: @ Html.DropDownListFor (model => model.Status, @ Model.Status.ToSelectList ()) HTH
Jeff Borden

6
Zauważ, że w nowszym ASP.NET MVCjest natywny sposób: stackoverflow.com/a/22295360/1361084
Ofiris

359

Wiem, że spóźniłem się na przyjęcie, ale pomyślałem, że ten wariant może ci się przydać, ponieważ pozwala on również na użycie ciągów opisowych zamiast stałych wyliczania w menu rozwijanym. Aby to zrobić, udekoruj każdą pozycję wyliczenia atrybutem [System.ComponentModel.Description].

Na przykład:

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

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

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

Oto mój kod:

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

Możesz to zrobić według własnego uznania:

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

Mam nadzieję, że to ci pomoże!

** EDIT 2014-JAN-23: Microsoft właśnie wydał MVC 5.1, który ma teraz funkcję EnumDropDownListFor. Niestety wydaje się, że nie przestrzega atrybutu [Opis], więc powyższy kod nadal obowiązuje. Patrz sekcja Enum w uwagach do wydania Microsoft dla MVC 5.1.

Aktualizacja: obsługuje jednak atrybut Display[Display(Name = "Sample")] , więc można go użyć.

[Aktualizacja - właśnie to zauważyłem, a kod wygląda jak rozszerzona wersja kodu tutaj: https://blogs.msdn.microsoft.com/stuartleeks/2010/05/21/asp-net-mvc-creating-a- dropdownlist-helper-for-enums / , z kilkoma dodatkami. Jeśli tak, uznanie autorstwa wydaje się uczciwe ;-)]


28
+1 Uważam, że jest to najbardziej przydatna ze wszystkich odpowiedzi tutaj. Byłem w stanie przekształcić to w kod wielokrotnego użytku. Dziękuję Ci!
Ed Charbeneau,

43
Program Visual Studio ma dziwny błąd, w którym jeśli nie odwołujesz się, oznacza System.Web.Mvc.Htmlto, że DropDownListFornie można go znaleźć, ale nie można go rozwiązać. Musisz to zrobić ręcznie using System.Web.Mvc.Html;. Po prostu wiesz.
Kezzer

1
Mam wariant tego w skrócie, którego używamy we wszystkich naszych projektach: gist.github.com/1287511
kamranicus

1
Świetne rozwiązanie, dzięki, byłoby jeszcze lepiej, gdybyś mógł buforować wyniki GetEnumDescription
M. Mennan Kara

17
Nowy EnumDropDownListFor MVC 5.1 nie używa [Opis („”)], ale używa [Wyświetl (Nazwa = „”)]! Enjoy :)
Supergibbs

195

W ASP.NET MVC 5.1 dodano EnumDropDownListFor()pomocnika, więc nie ma potrzeby niestandardowych rozszerzeń:

Model :

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

Zobacz :

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

Za pomocą Tag Helper (ASP.NET MVC 6) :

<select asp-for="@Model.SelectedValue" asp-items="Html.GetEnumSelectList<MyEnum>()">

21

3
Powinieneś utworzyć nowe pytanie, które jest specyficzne dla MVC 5.1 i podać to jako odpowiedź, a następnie wysłać mi link do postu, abym mógł głosować na ulubionego.
Kevin Heidt

2
W EnumDropDownListFor () nie podoba mi się to, że zapisuje w DB wartość int wyliczenia, a nie tekst, więc jeśli kiedykolwiek zdecydujesz się dodać nowy element wyliczenia, musi koniecznie znajdować się na końcu listy , aby nie stracić związku zapisanych wartości int bazy danych z pierwotnymi pozycjami elementów wyliczanych. Jest to niepotrzebne ograniczenie, jeśli tekst zostanie zapisany. Ponadto wolę patrzeć na db i zobaczyć tekst, a nie ints, gdzie muszę szukać wartości tekstowych gdzie indziej. W przeciwnym razie ten pomocnik HTML jest bardzo wygodny w użyciu.
Giovanni

2
@ Giovanni - możesz określić własne wartości liczbowe.
Tommy

1
@ Giovanni Strict design powinien przypisywać wartość do każdego wpisu wyliczeniowego (jeśli jest to ważne), w przeciwnym razie wartość nie powinna mieć znaczenia (a więc umieszczenie nowych na końcu nie powinno stanowić problemu). Zapisywanie wartości int jest lepsze, jeśli chodzi o oszczędzanie pamięci i zwiększenie wydajności (podczas wyszukiwania).
King King

130

Natknąłem się na ten sam problem, znalazłem to pytanie i pomyślałem, że rozwiązanie zapewnione przez Asha nie było tym, czego szukałem; Samo tworzenie kodu HTML oznacza mniejszą elastyczność niż funkcja wbudowana Html.DropDownList().

Okazuje się, że C # 3 itp. Czyni to całkiem łatwym. Mam enumnazywa 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);

To tworzy dobre stare, SelectListktóre mogą być używane tak, jak jesteś przyzwyczajony w widoku:

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

Anonimowy typ i LINQ czyni to o wiele bardziej eleganckim IMHO. Bez przestępstwa, Ash. :)


dobra odpowiedź! miałem nadzieję, że ktoś użyje linq i SelectList :) Cieszę się, że sprawdziłem tutaj pierwszy!
Pure.Krome

1
ID = s podaj mi DataTextField, a nie wartość? Co może być przyczyną? Dziękuję
Barbaros Alp

1
Rune, użyłem tej samej metody i renderuje DropDownList DOES, ale gdy wysyła wiadomość na serwer, nie zapisuje wartości, którą wybrałem.
zgodnie z ruchem wskazówek zegara q

5
@BarbarosAlp Aby identyfikator był liczbą, musisz wyrzucić enum do int:select new { ID = (int)s, Name = s.ToString() };
Keith

To odpowiedź podoba mi się najbardziej ze względu na jej prostotę. Szkoda, że ​​nie otrzymałeś wystarczającej ilości środków, ponieważ wybrana odpowiedź wykorzystała twoje rozwiązanie.
anar khalilov

63

Oto lepiej zamknięte rozwiązanie:

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

Powiedz tutaj, jest twój model:

wprowadź opis zdjęcia tutaj

Przykładowe użycie:

wprowadź opis zdjęcia tutaj

Wygenerowany interfejs użytkownika: wprowadź opis zdjęcia tutaj

I wygenerowany HTML

wprowadź opis zdjęcia tutaj

Migawka Kod źródłowy rozszerzenia pomocnika:

wprowadź opis zdjęcia tutaj

Możesz pobrać przykładowy projekt z linku, który podałem.

EDYCJA: Oto kod:

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

2
Po prostu moja opinia, ale myślę, że ta odpowiedź jest znacznie bardziej przejrzysta niż odpowiedź zaakceptowana. Szczególnie podoba mi się opcja użycia atrybutu Opis. Dodałem kod, aby ludzie mogli go skopiować / wkleić bez pobierania.
Ben Mills,

Wywołaj metodę rozszerzenia jako EnumDropDownListFor zamiast DropDownListFor do użycia: -> @ Html.EnumDropDownListFor (x => x.Gender)
sandeep talabathula

Dla kogoś, kto chce dodać jeszcze jeden element „Please Select”, zwróć htmlHelper.DropDownListFor (wyrażenie, createSelectList (expression.ReturnType, zaznaczone, firstElement), „Proszę wybrać”);
Sandeep

1
Działa w porządku! Jednak na stronie Szczegóły DisplayFor () pokazuje wybraną wartość wyliczenia zamiast odpowiedniego opisu. Podejrzewam, że to wywołuje przeciążenie DisplayFor () dla typu wyliczeniowego. Czy ktoś ma na to rozwiązanie?
corix010

48

Html.DropDownListFor wymaga tylko IEnumerable, więc alternatywa dla rozwiązania Prise jest następująca. Pozwoli ci to po prostu napisać:

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

[Gdzie SelectedItemType jest polem w twoim modelu typu ItemTypes, a twój model jest różny od null]

Poza tym tak naprawdę nie trzeba generalizować metody rozszerzenia, ponieważ można użyć enumValue.GetType () zamiast typeof (T).

EDYCJA: Zintegrowane tutaj rozwiązanie Simona i uwzględniająca metodę rozszerzenia 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();
    }
}

Nie działa dla mnie („System.NullReferenceException: Odwołanie do obiektu nie jest ustawione na instancję obiektu.”) ... Mój „Model” ma wartość null ... prawdopodobnie ma coś wspólnego z „GetNonNullableModelType”, który ma Simon. w
cenie

@Cristi, masz rację, to rozwiązanie nie jest przeznaczone do stosowania w warunkach, w których Twój model ma wartość zerową. Staram się w ogóle unikać takiego projektu i w takim przypadku inicjuję model „pusty”.
Zaid Masud

Cóż, jestem nowy w asp mvc, ale mam dość doświadczenie w .Net. Dziękuję, przyjrzę się temu, co sugerowałeś. Poza tym twoje rozszerzenie ToDescription znacznie wykracza poza zakres „Enum”. Myślę, że dobrze pasuje do samego „obiektu”. Tego właśnie użyłem, gdy wziąłem kod Simona i trochę go wyczyściłem.
Uczeń

@ Cristi trudno jest zrozumieć, co rozumiesz przez „daleko poza zakresem„ Enum ”, ale brzmi to tak, jakbyś powiedział, że metoda rozszerzenia ToDescription nie jest ściśle wpisana do wyliczenia ItemTypes? Jest to celowe i sprawia, że ​​metoda rozszerzenia jest ogólnie dostępna dla wszystkich wyliczeń. Jeśli porównujesz to z ogólną metodą rozszerzenia, istnieją zalety i wady każdego podejścia. W szczególności, jeśli generecize, nie można ograniczyć się tylko do wyliczeń.
Zaid Masud

1
Świetnie, dzięki. Zmieniłem value.ToString, aby użyć rozszerzenia FromCamelCase na wypadek braku opisu. Tak się toczę :)
Valamas

33

Więc bez funkcji rozszerzenia, jeśli szukasz prostego i łatwego .. To właśnie zrobiłem

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

gdzie XXXXX.Sites.YYYY.Models.State jest enum

Prawdopodobnie lepiej jest wykonać funkcję pomocnika, ale gdy będzie mało czasu, wykona to zadanie.


Fajnie, to działało, wypełniając menu rozwijane, ale jak ustawić domyślną wybraną wartość w składni Razor dla Html.DropDownListFor? Chcę pokazać tabelę z polami kombinacji wyliczeń i muszę również ustawić wybraną wartość zgodnie z tym, co było wcześniej.
Johncl

2
Powinien być w stanie przekazać drugi parametr z wybraną wartością do nowej funkcji SelectList (IEnumerable, object). Dokumentacja MSDN: msdn.microsoft.com/en-us/library/dd460123.aspx
Marty Trenouth

23

Rozwijając odpowiedzi na nagrodę i Rune, jeśli chcesz, aby atrybut wartości wybranych elementów listy odwzorował na wartość całkowitą typu wyliczenia, a nie na ciąg znaków, użyj następującego kodu:

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

Zamiast traktować każdą wartość wyliczenia jako obiekt TEnum, możemy traktować ją jako obiekt, a następnie rzutować ją na liczbę całkowitą, aby uzyskać wartość rozpakowaną.

Uwaga: Dodałem również ogólne ograniczenie typu, aby ograniczyć typy, dla których to rozszerzenie jest dostępne tylko dla struktur (typ podstawowy Enum), oraz sprawdzanie poprawności typu w czasie wykonywania, które zapewnia, że ​​przekazywana struktura jest rzeczywiście Enum.

Aktualizacja 23.10.12: Dodano parametr typu ogólnego dla typu bazowego i naprawiono problem braku kompilacji wpływający na .NET 4+.


Dzięki! To była odpowiedź, której potrzebowałem. Przechowuję wartość całkowitą Enum jako kolumnę w bazie danych i wydaje się, że to rozwiązanie działa idealnie.
grimus

ale co, jeśli przechowujesz char, a nie int? co jest moim przypadkiem. oczywiście mógłbym zmienić (int) na (char), ale co powiesz na zrobienie tego generycznego. jak to zrobić?
Stefanvds

@Stefandvds To świetne pytanie dotyczące rzutowania na poprawnie reprezentowany typ. Na podstawie testów, które właśnie przeprowadziłem, wydaje się, że jedynym sposobem na osiągnięcie tego byłoby określenie rzeczywistego typu jako innego parametru typu. ToSelectList<TEnum, TEnumValue>(this TEnum enumObj) { ... }
Nathan Taylor,


Jeśli wartości twojego wyliczenia są int, możesz po prostu użyć Value = Convert.ToInt32(e). (int)enie kompiluje się. :(
Andrew,

11

Aby rozwiązać problem uzyskania numeru zamiast tekstu za pomocą metody rozszerzenia 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);
}

Właśnie tego szukałem, chociaż jest to o wiele brzydsze, niż myślałem, że powinno być. Zastanawiam się, dlaczego Visual Studio nie pozwoli Ci bezpośrednio oddane edo int.
Andrew

Lub możesz po prostu użyć ID = Convert.ToInt32(e).
Andrew,

11

Bardzo łatwy sposób to zrobić - bez wszystkich rozszerzeń, które wydają się przesadne, jest to:

Twoje wyliczenie:

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

Wewnątrz kontrolera powiąż Enum z Listą:

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

Następnie wrzuć go do torby ViewBag:

    ViewBag.RequiredLevel = new SelectList(myLevels);

Na koniec po prostu połącz go z widokiem:

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

Jest to zdecydowanie najłatwiejszy sposób, jaki znalazłem i nie wymaga żadnych rozszerzeń ani niczego tak szalonego.

AKTUALIZACJA : Zobacz komentarz Andrewsa poniżej.


3
Działa to tylko wtedy, gdy nie przypisałeś żadnej wartości swojemu wyliczeniu. Jeśli tak Level1 = 1, to wartość rozwijana byłaby "Level1"zamiast 1.
Andrew,

11

Najlepszym rozwiązaniem, jakie znalazłem, było połączenie tego bloga z odpowiedzią Simona Goldstone'a .

Pozwala to na użycie wyliczenia w modelu. Zasadniczo chodzi o to, aby użyć zarówno liczby całkowitej, jak i wyliczenia i emulować tę liczbę.

Następnie użyj atrybutu [System.ComponentModel.Description] do opisania modelu tekstem wyświetlanym i użyj w widoku rozszerzenia „EnumDropDownListFor”.

To sprawia, że ​​widok i model są bardzo czytelne i łatwe do utrzymania.

Model:

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

Widok:

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

Rozszerzenie (bezpośrednio z odpowiedzi Simona Goldstone'a , dołączone tutaj dla kompletności):

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

To nie działa, MVC 4 Razor. W widoku lub w środowisku wykonawczym błąd = "Wywołanie jest dwuznaczne między następującymi metodami lub właściwościami LDN.Extensions.MvcExtensions.EnumDropDownListFor <MyModel, LDN.Models.YesPartialNoEnum?> (System.Web.Mvc.HtmlHelper <MyModel> .Linq.Expressions.Expression <System.Func <MyModel, LDN.Models.YesPartialNoEnum? >>) 'i .... ”i ta sama metoda z tymi samymi rekwizytami powtórzona ponownie (niedozwolona liczba znaków).
Marc


8
@Html.DropDownListFor(model => model.Type, Enum.GetNames(typeof(Rewards.Models.PropertyType)).Select(e => new SelectListItem { Text = e }))

Dobrze! Jak w ten sposób uzyskać wartość i tekst z enum? Mam na myśli, że mam SomeEnum {some1 = 1, some2 = 2} Muszę uzyskać liczby (1, 2) dla wartości i tekstu (some1, some2) dla tekstu listy wyboru
Dmitresky

7

To jest odpowiedź na Runę i Nagrodę zmienioną, aby użyć intwartości Enum jako identyfikatora.

Przykładowy Enum:

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

Metoda przedłużenia:

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

Próbka użycia:

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

Pamiętaj, aby zaimportować przestrzeń nazw zawierającą metodę Extension

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

Próbka wygenerowanego HTML:

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

Pamiętaj, że element, którego używasz do wywoływania, ToSelectListto wybrany element.


Lub możesz po prostu użyć Id = Convert.ToInt32(e).
Andrew

6

To jest wersja 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)

Działa to tylko wtedy, gdy wyliczenie składa się z ciągłych wartości zaczynających się od 0. Wyliczenie Flagi nie działałoby z tym. Jednak twórcze wykorzystanie zindeksowanego Select.
Suncat2000

6

W .NET Core możesz po prostu użyć tego:

@Html.DropDownListFor(x => x.Foo, Html.GetEnumSelectList<MyEnum>())

1
Lub z pomocnikiem tagów <select asp-for="Model.Foo" class="form-control" asp-items="@Html.GetEnumSelectList<MyEnum>()"></select>.
Pascal R.

tak, mówię, że pomocnicy tagów są jeszcze lepsi, ponieważ format jest bliższy czystemu
HTMLowi

Możesz także to zrobić @ Html.DropDownListFor (x => x.Foo, Html.GetEnumSelectList (typeof (FooEnum)))
Fereydoon Barikzehy


5

Opierając się na odpowiedzi Simona, podobne podejście polega na tym, aby wartości Enum były wyświetlane z pliku zasobów zamiast w atrybucie opisu w samym Enum. Jest to pomocne, jeśli twoja witryna musi być renderowana w więcej niż jednym języku, a jeśli miałbyś mieć konkretny plik zasobów dla Enums, możesz pójść o krok dalej i mieć tylko wartości Enum w swoim Enum i odwoływać się do nich z rozszerzenia przez konwencja taka jak [EnumName] _ [EnumValue] - ostatecznie mniej pisania!

Rozszerzenie wygląda wtedy następująco:

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

Zasoby w pliku Enums.Resx wyglądające jak ItemTypes_Movie: Film

Inną rzeczą, którą lubię, jest to, że zamiast wywoływać metodę rozszerzenia bezpośrednio, wolę wywoływać ją za pomocą @ Html.EditorFor (x => x.MyProperty), lub najlepiej mieć całą formę w jednym zgrabnym @ Html.EditorForModel (). Aby to zrobić, zmieniam szablon łańcucha, aby wyglądał tak

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

Jeśli Cię to interesuje, zamieściłem tu na blogu o wiele bardziej szczegółową odpowiedź:

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


5

Cóż, jestem naprawdę spóźniony na imprezę, ale o ile warto, napisałem blog na ten temat, dzięki czemu tworzę EnumHelperzajęcia, które umożliwiają bardzo łatwą transformację.

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

W twoim kontrolerze:

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

Twoim zdaniem:

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

Klasa pomocnicza:

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

4

Bardzo się spóźniłem, ale znalazłem naprawdę fajny sposób, aby to zrobić za pomocą jednego wiersza kodu, jeśli z przyjemnością dodasz pakiet Unconstrained Melody NuGet (ładna, mała biblioteka Jona Skeeta).

To rozwiązanie jest lepsze, ponieważ:

  1. Zapewnia (z ogólnymi ograniczeniami typu), że wartość naprawdę jest wartością wyliczaną (z powodu nieograniczonej melodii)
  2. Unika niepotrzebnego boksowania (z powodu niepohamowanej melodii)
  3. Przechowuje w pamięci podręcznej wszystkie opisy, aby uniknąć refleksji przy każdym wywołaniu (z powodu nieograniczonej melodii)
  4. Jest mniej kodu niż inne rozwiązania!

Oto kroki, które pozwolą Ci to uruchomić:

  1. W konsoli Menedżera pakietów „Instaluj pakiet bez ograniczeń”
  2. Dodaj właściwość do swojego modelu w taki sposób:

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

Teraz, gdy masz już listę SelectListItem w swoim modelu, możesz użyć @ Html.DropDownList lub @ Html.DropDownListFor, używając tej właściwości jako źródła.


+1 za użycie kodu Jona Skeeta :), żartuję tylko
Vamsi

3

Kolejna poprawka do tej metody rozszerzenia - bieżąca wersja nie wybrała bieżącej wartości wyliczenia. Naprawiłem ostatnią linię:

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

3

Jeśli chcesz dodać obsługę lokalizacji, po prostu zmień metodę s.toString () na coś takiego:

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

Tutaj typof (Zasoby) to zasób, który chcesz załadować, a następnie otrzymasz zlokalizowany ciąg, przydatny również, jeśli twój moduł wyliczający ma wartości z wieloma słowami.


3

To jest moja wersja metody pomocniczej. Używam tego:

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

Zamiast tego:

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

Oto on:

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

3

Możesz także użyć mojego niestandardowego HtmlHelpers w Griffin.MvcContrib. Poniższy kod:

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

Generuje:

wprowadź opis zdjęcia tutaj

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


3

Chciałbym odpowiedzieć na to pytanie w inny sposób, w którym użytkownik nie musi nic robić w controllerlubLinq wyrażać. Tą drogą...

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

Teraz mogę po prostu utworzyć dropdownza pomocą tego enum.

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

LUB

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

Jeśli chcesz wybrać indeks, spróbuj tego

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

Tutaj użyłem AccessLevelEnum.NoAccessjako dodatkowego parametru do domyślnego wyboru menu rozwijanego.


3

Znalazłem odpowiedź tutaj . Jednak niektóre moje wyliczenia mają [Description(...)]atrybut, więc zmodyfikowałem kod, aby zapewnić obsługę tego:

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

Mam nadzieję, że to pomaga.


Chcę zwrócić członka typu = DropdownList. Jestem dobry z Text = DescriptionAttribute, ale trudno mi uzyskać wartość int z Value
NanaFadanvis

2

@ Simon Goldstone: Dzięki za twoje rozwiązanie, może być idealnie zastosowane w moim przypadku. Jedynym problemem jest to, że musiałem przetłumaczyć go na VB. Ale teraz jest to zrobione i aby zaoszczędzić czas innych ludzi (na wypadek, gdyby tego potrzebowali), umieszczam to tutaj:

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

Koniec Używaj go w ten sposób:

@Html.EnumDropDownListFor(Function(model) (model.EnumField))


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

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

})

Myślę, że to nie jest poprawna odpowiedź, w ogóle nie używa wyliczenia do wypełnienia listy rozwijanej.
Andrew
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.