Exclude/Remove Value from MVC 5.1 EnumDropDownListFor
Asked Answered
T

5

47

I have a list of enums that I am using for a user management page. I'm using the new HtmlHelper in MVC 5.1 that allows me to create a dropdown list for Enum values. I now have a need to remove the Pending value from the list, this value will only ever be set programatically and should never be set by the user.

Enum:

public enum UserStatus
{
    Pending = 0,
    Limited = 1,
    Active = 2
}

View:

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

Is there anyway, either overriding the current control, or writing a custom HtmlHelper that would allow me to specify an enum, or enums to exclude from the resulting list? Or would you suggest I do something client side with jQuery to remove the value from the dropdown list once it has been generated?

Thanks!

Teasel answered 25/11, 2014 at 17:28 Comment(1)
You could copy the MVC source code for EnumDropDownListFor here and here and modify the signature to include a parameter that is a collection of excluded values, then in the EnumHelper.GetSelectList() method, ignore items that are in the excluded values.Arthropod
F
51

You could construct a drop down list:

@{ // you can put the following in a back-end method and pass through ViewBag
   var selectList = Enum.GetValues(typeof(UserStatus))
                        .Cast<UserStatus>()
                        .Where(e => e != UserStatus.Pending)
                        .Select(e => new SelectListItem 
                            { 
                                Value = ((int)e).ToString(),
                                Text = e.ToString()
                            });
}
@Html.DropDownListFor(m => m.Status, selectList)
Forename answered 26/11, 2014 at 9:21 Comment(7)
This worked perfectly, but when I tried to move the code to the back end and pass it in using the Viewbag, there was an error being thrown saying that it could not contain any dynamically constructed content. I assume this is due to the fact the Viewbag is dynamically created at runtime, moving the code to View sorted the issue and I can see the correct value is being passed back on the POST. Thanks! :)Teasel
In the back end doing ViewBag.SelectList = selectList; and in the front doing @Html.DropDownListFor(m => m.Status, (IEnumerable<SelectListItem>)ViewBag.SelectList) should work (from memory!)Forename
Your memory was correct! So it was the cast in front of the Viewbag that was missing then when I attempted it the first time? I assume this tells the Razor View what to expect once that Viewbag item has been created?Teasel
Exactly! If you use a strongly typed ViewModel instead of the dynamic ViewBag then you wouldn't need the cast.Forename
Well, looks like I'm learning! I have the View strongly typed to the User model, the UserStatus is a public enum within that User class so to my knowledge I can't have the view strongly typed to 2 models can I? It's not huge issue, more just me being curious now, you've already been an awesome help with this issue alreadyTeasel
What you'd do is create a class which has properties the same as the properties that you want to expose from your user class with SelectList there too and map over your model to this new ViewModel.Forename
I think this will not work properly with "Display(Name="")" attribute !!!1Sulfurous
S
32

Modified from @dav_i's answer.

This is not perfect, but it is what I am using. Below is an extension to HtmlHelper. The extension method will look like EnumDropDownListFor from ASP.NET, and use DisplayAttribute if there is any applied to the Enum value.

/// <summary>
/// Returns an HTML select element for each value in the enumeration that is
/// represented by the specified expression and predicate.
/// </summary>
/// <typeparam name="TModel">The type of the model.</typeparam>
/// <typeparam name="TEnum">The type of the value.</typeparam>
/// <param name="htmlHelper">The HTML helper instance that this method extends.</param>
/// <param name="expression">An expression that identifies the object that contains the properties to display.</param>
/// <param name="optionLabel">The text for a default empty item. This parameter can be null.</param>
/// <param name="predicate">A <see cref="Func{TEnum, bool}"/> to filter the items in the enums.</param>
/// <param name="htmlAttributes">An object that contains the HTML attributes to set for the element.</param>
/// <returns>An HTML select element for each value in the enumeration that is represented by the expression and the predicate.</returns>
/// <exception cref="ArgumentNullException">If expression is null.</exception>
/// <exception cref="ArgumentException">If TEnum is not Enum Type.</exception>
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, Func<TEnum, bool> predicate, string optionLabel, object htmlAttributes) where TEnum : struct, IConvertible
{
    if (expression == null)
    {
        throw new ArgumentNullException("expression");
    }

    if (!typeof(TEnum).IsEnum)
    {
        throw new ArgumentException("TEnum");
    }
    
    IList<SelectListItem> selectList = Enum.GetValues(typeof(TEnum))
            .Cast<TEnum>()
            .Where(e => predicate(e))
            .Select(e => new SelectListItem
                {
                    Value = Convert.ToUInt64(e).ToString(),
                    Text = ((Enum)(object)e).GetDisplayName(),
                }).ToList();
    if (!string.IsNullOrEmpty(optionLabel)) {
        selectList.Insert(0, new SelectListItem {
            Text = optionLabel,
        });
    }

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

/// <summary>
/// Gets the name in <see cref="DisplayAttribute"/> of the Enum.
/// </summary>
/// <param name="enumeration">A <see cref="Enum"/> that the method is extended to.</param>
/// <returns>A name string in the <see cref="DisplayAttribute"/> of the Enum.</returns>
public static string GetDisplayName(this Enum enumeration)
{
    Type enumType = enumeration.GetType();
    string enumName = Enum.GetName(enumType, enumeration);
    string displayName = enumName;
    try
    {
        MemberInfo member = enumType.GetMember(enumName)[0];

        object[] attributes = member.GetCustomAttributes(typeof(DisplayAttribute), false);
        DisplayAttribute attribute = (DisplayAttribute)attributes[0];
        displayName = attribute.Name;

        if (attribute.ResourceType != null)
        {
            displayName = attribute.GetName();
        }
    }
    catch { }
    return displayName;
}

For example:

@Html.EnumDropDownListFor(
    model => model.UserStatus,
    (userStatus) => { return userStatus != UserStatus.Active; },
    null,
    htmlAttributes: new { @class = "form-control" })

This will create an Enum dropdown list without the the option of Active.

Scrupulous answered 24/8, 2015 at 23:17 Comment(4)
Brilliant - This is going in my snippet library :) btw you dont need to return the condition you could just put (userStatus) => userStatus != UserStatus.ActiveHillery
Only thing needed to use in a view is using statement for the class where this is defined. Perfect. :-)Hypercriticism
The metadata variable is never used... I don't think it's necessary.Precedent
you probably do not want the trailing semi-colon in the usage example, as it will be rendered as mark-up.Vitriolic
M
10

You can create the dropdown yourself by looping through the values in the enum and only include the <option> if it is not Pending.

Here is how it should work, but as you can see, I'm not sure what you would use for the value or text of the option tag.

<select>
foreach (var status in Enum.GetValues(typeof(UserStatus)))
{
    if(status != UserStatus.Pending)
    {
        <option value="status.???">@status.???</option>
    }
}
</select>
Myogenic answered 25/11, 2014 at 17:36 Comment(1)
yep, sometimes the simplest solution is bestDrayton
S
7

I was looking for the answer to this question as it relates to .NET Core MVC. Given the following code, you can limit the enums you do not want to display within the UI:

<select asp-for="UserStatus" asp-items="@(Html.GetEnumSelectList<UserStatus>().Where(x => x.Value != 0))" class="form-control">
    <option selected="selected" value="">Please select</option>
</select>

Hope this helps anyone else looking for this answer.

Sememe answered 29/6, 2019 at 18:57 Comment(1)
Bit old but for me Value was of type string so changing x.Value != 0 to x.Value != "0" worked a treatAerotherapeutics
H
1

Below is an extension to the HtmlHelper. It is very similar to the EnumDropDownListFor extension from ASP.NET, but it sorts the SelectListItem by the item display name. It has a suggestive name: SortedEnumDropDownListFor for not conflicts with the original extension.

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TModel">The type of the model.</typeparam>
    /// <typeparam name="TEnum">The type of the value.</typeparam>
    /// <param name="htmlHelper">The HTML helper instance that this method extends.</param>
    /// <param name="expression">An expression that identifies the object that contains the properties to display</param>
    /// <param name="initalValue">The unselected item initial value</param>
    /// <param name="htmlAttributes"></param>
    /// <returns></returns>
    public static MvcHtmlString SortedEnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, string initalValue, object htmlAttributes = null)
    {

        ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

        Type enumType = GetNonNullableModelType(metadata);
        Type baseEnumType = Enum.GetUnderlyingType(enumType);
        List<SelectListItem> items = new List<SelectListItem>();

        foreach (FieldInfo field in enumType.GetFields(BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public))
        {
            string text = field.Name;
            string value = Convert.ChangeType(field.GetValue(null), baseEnumType).ToString();
            bool selected = field.GetValue(null).Equals(metadata.Model);

            foreach (DisplayAttribute displayAttribute in field.GetCustomAttributes(true).OfType<DisplayAttribute>())
            {
                text = displayAttribute.GetName();
            }

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

        items = new List<SelectListItem>(items.OrderBy(s => s.Text));
        items.Insert(0, new SelectListItem { Text = initalValue, Value = "" });

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

    }        

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

        if (underlyingType != null)
        {
            realModelType = underlyingType;
        }

        return realModelType;
    }

If you don't want to bother with the unselected item intitia, just build a overload like this:

public static MvcHtmlString SortedEnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, object htmlAttributes = null)
        {
            MvcHtmlString helper = SortedEnumDropDownListFor(htmlHelper, expression, string.Empty, htmlAttributes);
            return helper;
        }

And you are good to go. I hope it helps.

Hosmer answered 13/8, 2016 at 22:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.