Sortable enum properties for Optimizely CMS

Enum properties in Episerver/Optimizely has been used for more than 10 years. The legendary Joel Abrahamsson blogged about it as early as in 2013.

Enum properties can still be used the same way in Optimizely CMS 12. However, over the years I have been a little hesitant about using enum properties for two reasons.

  • What happens when an item needs to be renamed?
  • What happens when items need to be rearranged?

Today I'm fixing both problems!

Given this joyful enum...

public enum Joy
{
    Pleasure,
    Cheerfulness,
    Delight,
    Exhilaration,
    Jubilation,
    Euphoria,
    Ecstasy,
    Bliss
}

I can add an enum property to my content type like this...

[Display(Name = "Type of Joy")]
[SelectOne(SelectionFactoryType = typeof(EnumSelectionFactory<Joy>))]
public virtual Joy Joy { get; set; }

And the editor will see a dropdown property like this...

I should be able to rename the individual items using the Display attribute, like this...

public enum Joy
{
    Pleasure,
    
    Cheerfulness,
    
    [Display(Name = "Delightfulness")]
    Delight,
    
    Exhilaration,
    
    Jubilation,
    
    Euphoria,
    
    Ecstasy,
    
    Bliss
}

The dropdown is now updated...

...and I should be able to update the sort order in edit mode like this, when I realize Bliss should be further up...

public enum Joy
{
    [Display(Order = 1)]
    Pleasure,
    
    [Display(Order = 2)]
    Cheerfulness,
    
    [Display(Name = "Delightfulness", Order = 3)]
    Delight,
    
    [Display(Order = 4)]
    Exhilaration,
    
    [Display(Order = 5)]
    Jubilation,
    
    [Display(Order = 7)]
    Euphoria,
    
    [Display(Order = 8)]
    Ecstasy,
    
    [Display(Order = 6)]
    Bliss
}

Notice that the sort order for Bliss is set to 6 (less than Euphoria and Ecstasy), and now the dropdown is updated like this...

But what should my EnumSelectionFactory look like, in order to respect the Display attribute? My current implementation looks like this...

public class EnumSelectionFactory<TEnum> : ISelectionFactory
{
    public IEnumerable<ISelectItem> GetSelections(ExtendedMetadata metadata)
    {
        return GetSelectionsList().OrderBy(x => GetOrder((TEnum)x.Value)).ThenBy(x => (int)x.Value);
    }

    private static IEnumerable<SelectItem> GetSelectionsList()
    {
        var values = Enum.GetValues(typeof(TEnum));
        foreach (var value in values)
        {
            yield return new SelectItem
            {
                Text = GetName((TEnum)value),
                Value = value
            };
        }
    }

    private static string GetName(TEnum value)
    {
        return GetDisplayAttribute(value)?.GetName() ?? Enum.GetName(typeof(TEnum), value);
    }

    private static int? GetOrder(TEnum value)
    {
        return GetDisplayAttribute(value)?.GetOrder();
    }

    private static DisplayAttribute GetDisplayAttribute(TEnum value)
    {
        Type enumType = typeof(TEnum);
        var fieldInfo = enumType.GetField(value.ToString());
        var attributes = fieldInfo.GetCustomAttributes(typeof(DisplayAttribute), false) as DisplayAttribute[];
        return attributes?.FirstOrDefault();
    }
}

That's it!