C# and .NET,  Geeks,  Programming

Getting friendly names from an Enum when decorated with multiple attribute

It came about in MVC project I was working on that we wanted to output friendly names for a large number of enumerations we were working on directly into a selectable dropdown list. We would have an enum on a ViewModel object and that would be automatically put into a combo box with the selected item shown. Because alot of our model was pre-generated and we were getting these models from XML serialisation we ended up with a bunch of Enums defined like so.

Enum TestEnum
{
   [System.Xml.Serialization.XmlEnumAttribute("Tested")]   
   Test,
   AnotherOne
}

Ideal. We already had the enums created. So we went about creating a helper method that would work with these enumerations and we can use the XmlEnumAttribute as it’s name. So we came up with something like.

        public static string GetDescriptionAttributeValue(object record)
        {
            string value = record.ToString();

            var typeOf = record.GetType();
            var info = typeOf.GetMember(value);
            var attributes = info[0].GetCustomAttributes(false);

            string description = null;

            // if the enum has an attribute id then make the description that value
            if (attributes.Count() > 0)
            {
                foreach (object attr in attributes)
                {
                    if (attr is DescriptionAttribute)
                    {
                        // description attribute overwrites any existing value
                        description = ((DescriptionAttribute)attr).Description;
                    }
                    else if (attr is XmlEnumAttribute && description == null)
                    {
                        // enum attribute only used if not set already
                        description = ((XmlEnumAttribute)attr).Name;
                    }
                }

            }

            return description == null ? value : description;
        }

This worked perfectly until we realised that some Enums didn’t exactly have friendly UI names or our UI wanted to use different names than those defined. After scratching our heads for a bit we decided we could decorate each Enum without breaking the XML serialisation with another Attribute. We had a few arguments over what other attribute to use here. What that ended up doing was forcing us to create a bunch of classes where by we could choose which Attributes we wanted to use in this project and then in another project we could use the same code on a different set of attributes.

Here is what we ended up with. The core classes being used are the IObjectDescriptor interface coupled with the ObjectDescription and AttributeDescriptor (AttributeEnumDescriptor) classes.

    public interface IObjectDescriptor 
    {
       string GetDescription(object record);
    }

    public class ObjectDescription
    {
        private readonly IObjectDescriptor _objectDescriptor;

        public ObjectDescription(IObjectDescriptor attributePriorities)
        {
            _objectDescriptor = attributePriorities;
        }

        public string GetDisplayName(object record)
        {
            return GetDescription(record);
        }

        protected virtual string GetDescription(object record)
        {
            object value = record;

            IObjectDescriptor descriptor = GetDescriptor();

            if (descriptor != null)
                value = descriptor.GetDescription(record);
            
            return GetValueOrDefault(value);
        }

        protected virtual string GetValueOrDefault(object description)
        {
            return description == null ? null : description.ToString();
        }

        protected IObjectDescriptor GetDescriptor()
        {
            return _objectDescriptor;
        }
    }
   
    public abstract class AttributeDescriptor : IObjectDescriptor
    {
        private readonly List _attributes;

        public AttributeDescriptor()
        {
            _attributes = new List();
        }

        public virtual string GetDescription(object record)
        {
            var priorities = GetPriorities();

            if (priorities == null || record == null)
                return null;

            var attributes = GetAttributes(record);

            if (attributes != null)
                return GetAttributeDescription(attributes);
            else
                return null;
        }

        public virtual void AddAttributePriority(AttributePriority attr)
        {
            if(!_attributes.Exists(p => p.GetContainingClassType().Equals(attr.GetContainingClassType())))
                _attributes.Add(attr);
        }

        public virtual void AddAttributePriority(string propertyName, Type typeOfProperty)
        {
            AttributePriority lookup = new AttributePriority(typeof(T), propertyName,typeOfProperty);
            AddAttributePriority(lookup);
        }

        public virtual IEnumerable GetPriorities()
        {
            return _attributes;
        }

        protected abstract object[] GetAttributes(object record);

        protected virtual string GetAttributeDescription(object[] attributes)
        {
            ObjectHelper inspector = new ObjectHelper();
            object attribute = null;

            // looping from highest precedence
            foreach (var attr in GetPriorities())
            {
                // find a matching attribute on our object
                attribute = attributes.FirstOrDefault(p => inspector.IsOfType(p, attr.GetContainingClassType()));

                if (attribute != null)
                {
                    return attr.GetStringValue(attribute);
                }
            }

            return null;
        }
    }

    public class AttributeEnumDescriptor : AttributeDescriptor
    {
        public bool UseToStringOnNull { get; set; }

        public override string GetDescription(object record)
        {
            string value = base.GetDescription(record);

            if (value == null && UseToStringOnNull)
                return record.ToString();
            else
                return value;
        }

        protected override object[] GetAttributes(object record)
        {
            var typeOf = record.GetType();
            var info = typeOf.GetMember(record.ToString());
            return info[0].GetCustomAttributes(false);
        }
    }

    public class PropertyLookup
    {
        private readonly string _propertyName;
        private readonly Type _propertyType;

        public PropertyLookup(string propertyName)
            : this(propertyName, typeof(object))
        {
        }

        public PropertyLookup(string propertyName, Type typeOfProperty)
        {
            _propertyName = propertyName;
            _propertyType = typeOfProperty;
        }

        public object GetValue(object record)
        {
            if (record == null)
                return null;

            Type typeOf = record.GetType();

            ObjectHelper inspector = new ObjectHelper();

            PropertyInfo pInfo = typeOf.GetProperty(_propertyName);

            if(pInfo == null)
                return null;
            else
            {
                object value = pInfo.GetValue(record, null);

                if (inspector.IsOfType(value, _propertyType))
                    return value;
                else
                    return Activator.CreateInstance(_propertyType);
            }
        }

        public string GetStringValue(object record)
        {
            object value = GetValue(record);

            if (value == null)
                return string.Empty;
            else
                return value.ToString();            
        }

        public Type GetPropertyType()
        {
            return _propertyType;
        }
    }

    public class AttributePriority : PropertyLookup
    {
        private readonly Type _containingClassType;

        public AttributePriority(Type attributeClass, string propertyName, Type propertyType)
            : base(propertyName, propertyType)
        {
            _containingClassType = attributeClass;
        }

        public Type GetContainingClassType()
        {
            return _containingClassType;
        }
    }

Thankfully we didn’t need to change all the places where this method was used. All we did was change our helper method to now be:

    public static class EnumHelper
    {
        private static ObjectDescription _enumDescription;

        public static ObjectDescription EnumAttributeDescriptor
        {
            get 
            {
                if(_enumDescription == null)
                {
                    var descriptor = new AttributeEnumDescriptor() { UseToStringOnNull = true };                    
                    descriptor.AddAttributePriority("Description", typeof(string));
                    descriptor.AddAttributePriority("Name", typeof(string));

                    _enumDescription = new ObjectDescription(descriptor);
                }

                return _enumDescription;
            }
        }
         
        public static string GetXmlEnumAttributeValue(object record)
        {
            return EnumAttributeDescriptor.GetDisplayName(record);
        }
    }

And finally our helper method to actually provide the select list for the UI:

        public static List ConvertEnumToSelectListItems(string selectValue, string[] excludeList, Type typeOfEnum)             
        {
            if (!typeOfEnum.IsEnum)
            {
                throw new InvalidCastException(typeOfEnum.FullName + " is not an enum type.");
            }

            var enumValues = Enum.GetValues(typeOfEnum);

            List items = new List();

            foreach (var record in enumValues)
            {
                string value = record.ToString();
                string description = EnumHelper.GetXmlEnumAttributeValue(record);

                if ((excludeList == null || (excludeList != null && excludeList.Contains(value) == false)))
                {
                    items.Add(new SelectListItem()
                    {
                        Selected = value.Equals(selectValue),
                        Text = description,
                        Value = value
                    });
                }
            }

            return items;
        }

I think it’s probably worth mentioning that I did trawl around but most of the solutions just focused on something very similar to our original design. Because we wanted more than one attribute on our Enum’s we thought it was better to create this framework for managing that rather than adjusting the code in the original helper extension.

Hope this gives some ideas for anyone looking at decorating Enum’s with a number of different attributes. I’m sure there are better ways of doing this but this method has worked for us in two projects so seems like an ok solution for now.

Leave a Reply