How to make a default editor template for enums?
Asked Answered
S

7

20

How can I make a default editor template for enums? By which I mean: can I do something like this:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Enum>" %> 
<% -- any code to read the enum and write a dropdown -->

And put this in the EditorTemplates folder under the name Enum.ascx?

Here's a workaround for my problem that I tried, but it's not what I need.

Here is my Enum:

public enum GenderEnum
{
    /// <summary>
    /// Male
    /// </summary>
    [Description("Male Person")]
    Male,

    /// <summary>
    /// Female
    /// </summary>
    [Description("Female Person")]
    Female
}

I made a template called GenderEnum.acsx and put it in the Shared/EditorTemplates folder. Here is the Template:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<AlefTech.HumanResource.Core.GenderEnum>" %>
<%@ Import Namespace="AlefTech.HumanResource.WebModule.Classes" %>
<%=Html.DropDownListFor(m => m.GetType().Name, Model.GetType()) %>

Of course the method is my own:

public static class HtmlHelperExtension
    {
        public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, Type enumType)
        {
            List<SelectListItem> list = new List<SelectListItem>();
            Dictionary<string, string> enumItems = enumType.GetDescription();
            foreach (KeyValuePair<string, string> pair in enumItems)
                list.Add(new SelectListItem() { Value = pair.Key, Text = pair.Value });
            return htmlHelper.DropDownListFor(expression, list);
        }

        /// <summary>
        /// return the items of enum paired with its descrtioption.
        /// </summary>
        /// <param name="enumeration">enumeration type to be processed.</param>
        /// <returns></returns>
        public static Dictionary<string, string> GetDescription(this Type enumeration)
        {
            if (!enumeration.IsEnum)
            {
                throw new ArgumentException("passed type must be of Enum type", "enumerationValue");
            }

            Dictionary<string, string> descriptions = new Dictionary<string, string>();
            var members = enumeration.GetMembers().Where(m => m.MemberType == MemberTypes.Field);

            foreach (MemberInfo member in members)
            {
                var attrs = member.GetCustomAttributes(typeof(DescriptionAttribute), false);
                if (attrs.Count() != 0)
                    descriptions.Add(member.Name, ((DescriptionAttribute)attrs[0]).Description);
            }
            return descriptions;
        }

    }

However, even though this worked for me, it is not what I'm asking. Instead, I need the following to work:

Code for Shared\EditorTemplates\Enum.acsx:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Enum>" %>
<%@ Import Namespace="System.Web.Mvc.Html" %>
<%@ Import Namespace="WhereMyExtentionMethod" %>
<%=Html.DropDownListFor(m => m.GetType().Name, Model.GetType()) %>

With this I wouldn't have to make a template for every enum any more.

Skylab answered 7/8, 2010 at 18:18 Comment(2)
Did you still not get this to work? Would you mind posting the code for the helper used here: return htmlHelper.DropDownListFor(expression, list);?Ethnomusicology
By the way, your code should work if you put [UIHint("Enum")] on your enum-field in your model, change the type System.Web.Mvc.ViewUserControl<Enum> to dynamic, and cast them as the right types in the helper-call :)Ethnomusicology
S
7

Thank you all for your contributions
Yngvebn, i tried your solution (in your last comment) before, but the only thing i didn't do is the <dynamic>, i used instead <Enum> in generic type.

at last the solution is :
create a template named Enum.acsx and put it under the Views\Shared\EditorTemplates

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>
<%@ Import Namespace="System.Web.Mvc.Html" %>
<%@ Import Namespace="the extension methods namespace" %>
<% Enum model = (Enum)Model; %>
<%=Html.DropDownList(model.GetType().Name,model.GetType())%>

and in your Entity:

public class Person
{
  [UIHint("Enum")]
  public GenderEnum Gender{get;set;}
}

public Enum GenderEnum
{
 [Description("Male Person")]
 Male,
 [Description("Female Person")]
 Female
}

and again there is Extention Methods:

public static class HtmlHelperExtension
    {
        public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, Type enumType)
        {
            List<SelectListItem> list = new List<SelectListItem>();
            Dictionary<string, string> enumItems = enumType.GetDescription();
            foreach (KeyValuePair<string, string> pair in enumItems)
                list.Add(new SelectListItem() { Value = pair.Key, Text = pair.Value });
            return htmlHelper.DropDownListFor(expression, list);
        }

        /// <summary>
        /// return the items of enum paired with its descrtioption.
        /// </summary>
        /// <param name="enumeration">enumeration type to be processed.</param>
        /// <returns></returns>
        public static Dictionary<string, string> GetDescription(this Type enumeration)
        {
            if (!enumeration.IsEnum)
            {
                throw new ArgumentException("passed type must be of Enum type", "enumerationValue");
            }

            Dictionary<string, string> descriptions = new Dictionary<string, string>();
            var members = enumeration.GetMembers().Where(m => m.MemberType == MemberTypes.Field);

            foreach (MemberInfo member in members)
            {
                var attrs = member.GetCustomAttributes(typeof(DescriptionAttribute), false);
                if (attrs.Count() != 0)
                    descriptions.Add(member.Name, ((DescriptionAttribute)attrs[0]).Description);
            }
            return descriptions;
        }

    }
Skylab answered 10/8, 2010 at 12:1 Comment(5)
Have you actually tried this solution? There are compile time errorsFrame
of course, Vince i tried it, what kind of compile errors do you mean?? ... post it here if you want, may be i can help you.Skylab
I was looking at this and you seemed to added code for the DropDownFor and gave your example using DropDown, that's the problem. I was a b it thrown off as well.Mundell
You need to add "using System.Web.Mvc.Html;" to import the original helper. If you don't, the compiler thinks it's a recursive call to the helper you just defined.Halidom
i tried this, but it has compile time error: Enum model = (Enum) Model; Compiler can not convert Model to Enum type.Durden
W
18

Late to answer but I hope this helps others. Ideally you want all enums to use your Enum template by convention, not by specifying a UIHint each time, and you can accomplish that by creating a custom model metadata provider like this:

using System;
using System.Collections.Generic;
using System.Web.Mvc;

public class CustomMetadataProvider : DataAnnotationsModelMetadataProvider
{
    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) {
        var mm = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
        if (modelType.IsEnum && mm.TemplateHint == null) {
            mm.TemplateHint = "Enum";
        }
        return mm;
    }
}

Then simply register it in the Application_Start method of Global.asax.cs:

ModelMetadataProviders.Current = new CustomMetadataProvider();

Now all your enum properties will use your Enum template by default.

Wheelhouse answered 6/2, 2012 at 20:18 Comment(1)
I used your idea with some help from odetocode.com/blogs/scott/archive/2012/09/04/… That worked completely fine. Thanks ;)Durden
S
7

Thank you all for your contributions
Yngvebn, i tried your solution (in your last comment) before, but the only thing i didn't do is the <dynamic>, i used instead <Enum> in generic type.

at last the solution is :
create a template named Enum.acsx and put it under the Views\Shared\EditorTemplates

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>
<%@ Import Namespace="System.Web.Mvc.Html" %>
<%@ Import Namespace="the extension methods namespace" %>
<% Enum model = (Enum)Model; %>
<%=Html.DropDownList(model.GetType().Name,model.GetType())%>

and in your Entity:

public class Person
{
  [UIHint("Enum")]
  public GenderEnum Gender{get;set;}
}

public Enum GenderEnum
{
 [Description("Male Person")]
 Male,
 [Description("Female Person")]
 Female
}

and again there is Extention Methods:

public static class HtmlHelperExtension
    {
        public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, Type enumType)
        {
            List<SelectListItem> list = new List<SelectListItem>();
            Dictionary<string, string> enumItems = enumType.GetDescription();
            foreach (KeyValuePair<string, string> pair in enumItems)
                list.Add(new SelectListItem() { Value = pair.Key, Text = pair.Value });
            return htmlHelper.DropDownListFor(expression, list);
        }

        /// <summary>
        /// return the items of enum paired with its descrtioption.
        /// </summary>
        /// <param name="enumeration">enumeration type to be processed.</param>
        /// <returns></returns>
        public static Dictionary<string, string> GetDescription(this Type enumeration)
        {
            if (!enumeration.IsEnum)
            {
                throw new ArgumentException("passed type must be of Enum type", "enumerationValue");
            }

            Dictionary<string, string> descriptions = new Dictionary<string, string>();
            var members = enumeration.GetMembers().Where(m => m.MemberType == MemberTypes.Field);

            foreach (MemberInfo member in members)
            {
                var attrs = member.GetCustomAttributes(typeof(DescriptionAttribute), false);
                if (attrs.Count() != 0)
                    descriptions.Add(member.Name, ((DescriptionAttribute)attrs[0]).Description);
            }
            return descriptions;
        }

    }
Skylab answered 10/8, 2010 at 12:1 Comment(5)
Have you actually tried this solution? There are compile time errorsFrame
of course, Vince i tried it, what kind of compile errors do you mean?? ... post it here if you want, may be i can help you.Skylab
I was looking at this and you seemed to added code for the DropDownFor and gave your example using DropDown, that's the problem. I was a b it thrown off as well.Mundell
You need to add "using System.Web.Mvc.Html;" to import the original helper. If you don't, the compiler thinks it's a recursive call to the helper you just defined.Halidom
i tried this, but it has compile time error: Enum model = (Enum) Model; Compiler can not convert Model to Enum type.Durden
E
5

Here's a helper I made for this.. In your View you can simply do:

<%= Html.DropDownForEnum<MyEnum>("some-name-for-dropdown", MyEnum.TheFirstValue) %>

for the text in the actual dropdown it will look for a Resource in the resource-file that matches the name of the enum, otherwise just write the actual Enumtext itself.

public static MvcHtmlString DropDownForEnum<T>(this HtmlHelper h, string name, T selectedValue)
{
    Type enumType = typeof(T);
    Tag t = new Tag("select").With("name", name).And("id", name);

    foreach (T val in Enum.GetValues(enumType))
    {
        string enumText = Resources.ResourceManager.GetString(val.ToString());
        if (String.IsNullOrEmpty(enumText)) enumText = val.ToString();
        Tag option = new Tag("option").With("value", (val).ToString()).AndIf(val.Equals(selectedValue), "selected", "selected").WithText(enumText);
        t.Append(option);
    }
    return MvcHtmlString.Create(t.ToString());
}

You will also need my overloaded Tag-class if you want it to work with no rewriting..

public class Tag : TagBuilder
{
public Tag (string TagName): base(TagName)
{

}

public Tag Append(Tag innerTag)
{
    base.InnerHtml += innerTag.ToString();
    return this;
}

public Tag WithText(string text)
{

    base.InnerHtml += text;
    return this;
}

public Tag With(Tag innerTag)
{
    base.InnerHtml = innerTag.ToString();
    return this;
}

public Tag With(string attributeName, string attributeValue)
{
    base.Attributes.Add(attributeName, attributeValue);
    return this;
}

public Tag And(string attributeName, string attributeValue)
{
    base.Attributes.Add(attributeName, attributeValue);
    return this;
}


public Tag AndIf(bool condition, string attributeName, string attributeValue)
{
    if(condition)
        base.Attributes.Add(attributeName, attributeValue);
    return this;
}
}
Ethnomusicology answered 9/8, 2010 at 11:30 Comment(1)
that is great Yngvebn, but it still not answer my question. actually, to work around my problem, i made something similar to your code, but I'm not using the resources,instead I'm using the Description Attribute on every element of my Enum. i will add an answer which contains my code.Skylab
R
5

Nour Sabony, I modified your version to also support localization with resources. Therefore I changed the DescriptionAttribute to the DisplayAttribute of the DataAnnotations namespace

    public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, Type enumType)
    {
        List<SelectListItem> list = new List<SelectListItem>();
        Dictionary<string, string> enumItems = enumType.GetDisplayNames(htmlHelper.ViewContext.HttpContext);
        foreach (KeyValuePair<string, string> pair in enumItems)
            list.Add(new SelectListItem() { Value = pair.Key, Text = pair.Value });
        return htmlHelper.DropDownListFor(expression, list);
    }

    /// <summary>
    /// return the items of enum paired with its DisplayName.
    /// </summary>
    /// <param name="enumeration">enumeration type to be processed.</param>
    /// <returns></returns>
    public static Dictionary<string, string> GetDisplayNames(this Type enumeration, HttpContextBase httpContext)
    {
        if (!enumeration.IsEnum)
        {
            throw new ArgumentException("passed type must be of Enum type", "enumerationValue");
        }

        Dictionary<string, string> displayNames = new Dictionary<string, string>();
        var members = enumeration.GetMembers().Where(m => m.MemberType == MemberTypes.Field);

        foreach (MemberInfo member in members)
        {
            var attrs = member.GetCustomAttributes(typeof(DisplayAttribute), false);
            if (attrs.Count() != 0)
                if (((DisplayAttribute)attrs[0]).ResourceType != null)
                {
                    displayNames.Add(member.Name, ((DisplayAttribute)attrs[0]).GetName(););
                }
                else
                {
                    displayNames.Add(member.Name, ((DisplayAttribute)attrs[0]).Name);
                }
        }
        return displayNames;
    }

The definition of an enum has to look like this now:

public enum Gender
{
    [Display(Name = "Male", ResourceType = typeof(mynamespace.App_LocalResources.Shared))]
    Male = 1,

    [Display(Name = "Female", ResourceType = typeof(mynamespace.App_LocalResources.Shared))]
    Female = 2,

}

it can be used in a View in the same way, e.g. (Razor):

@Html.DropDownListFor(model => model.Gender, typeof(Gender))

Hope this helps someone!

Retral answered 24/7, 2011 at 22:0 Comment(0)
G
2

I made the dropdownlistfor method a bit easier and now you can give a selectedValue with it:

public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, Type enumType)
{
    return DropDownListFor(htmlHelper, expression, enumType, null);
}

public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, Type enumType, object selectedValue)
{
    Dictionary<string, string> enumItems = enumType.GetDisplayNames(htmlHelper.ViewContext.HttpContext);
    return htmlHelper.DropDownListFor(expression, new SelectList(enumItems, "Key", "Value", selectedValue));
}

Use it like this in your View:

@Html.DropDownListFor(m => m.Gender, typeof(Gender), Model.Gender)

Model is my MVC Model and its property Gender contains the selectedValue for the DropDownListFor.

Germaine answered 4/8, 2011 at 8:7 Comment(0)
E
1

I don't think there is a default way to define an editor for all enum types because you could want different behavior depending on the situation. For example, maybe you have a [Flags] enum and want multi select, or you want a dropdownlist, or you want radio buttons.

Plus, generally you are going to want some sort of meaningful display string beyond what you can accomplish in the variable naming limitations.

Certainly assigning to a property of type enum works out of the box but how you get that value is going to be up to you.

Endometrium answered 7/8, 2010 at 19:18 Comment(0)
F
0

Yes

Almost sure this works out of the box.


Try naming your template the same name as your enum.

Frater answered 7/8, 2010 at 18:20 Comment(2)
actually i did that, but it did'nt work. i made this file , but the file is never called. i think the proplem is (Enum) is not a type ?Skylab
well, what i am trying to do, is to create one file for all declared Enum in my solution. just like if i created a default editor template for (int) so whenever there is a property of (int), the mvc will use that template. the solution you suggested make me create a template for each kind of enum in my project.Skylab

© 2022 - 2024 — McMap. All rights reserved.