Bind drop down to enum in external assembly

Andy Cook asked on February 1, 2021 01:34

I have a page type with a drop down list that I'd like to bind to the values of an enum in an external assembly. For example, all the colors in System.Drawing.Color enum.

It looks like there should be a way to do this by setting the data source to Macro, but simply entering the name of the enum type doesn't seem to work (even with built in Kentico enums) so I guess my syntax is wrong. I tried using Enums.WorkflowTypeEnum to test it, which shows the dropdown of the page editor with "CMS.Base.EnumDataContainer". Do I need to make an extension method to convert the enum to an enumerable?

I suspect there also needs to be a way to tell Kentico to register a type from the external assembly so it is available for the macro to use. Because it is an external assembly I can't add any attributes to that code.

So 2 problems, how to render an enum as a dropdownlist, and how to register an enum from an external assembly.

Recent Answers


Dmitry Bastron answered on February 1, 2021 11:09

Hi Andy,

For the text field (I'm assuming your page type field is text, isn't it?) you can select Form control: Enum selector in the page type fields configuration. Then, further down this form you'll have Enum assembly name and Type fields to specify. Kentico withdraws all enums registered within the application. I think to include options from the external library it should be enough to reference it in your code so that Kentico can see it in application domain.

0 votesVote for this answer Mark as a Correct answer

Andy Cook answered on February 1, 2021 22:54 (last edited on February 2, 2021 04:03)

Hi Dmitry,

Thanks for your answer, it's almost what I need! I found that I can also add a default item to the drop down "Select one..." under "Special items" in the advanced section of the field configuration.

However the values in the list are displayed with the full enum name, eg: Color.BlanchedAlmond and I'd prefer friendly names eg: "Blanched Almond".

From decompiling the Kentico code it looks like there is a EnumStringRepresentationAttribute which could be added which should control the friendly name, however these enums are coming from an assembly which has no Kentico dependencies, so I can't add that custom attribute.

I've tried adding the standard System.ComponentModel attributes onto the enum eg: [Display(Name="Blanched Almond"), Description("Blanched Almond")] but neither seem to be taken into account.

Any ideas how to get the friendly names of the enum to appear?

thanks, Andy

0 votesVote for this answer Mark as a Correct answer

Andy Cook answered on February 3, 2021 04:55

In case anyone else is looking, here is what I ended up doing. I created a control extender, which alters the values of the drop down later in the lifecycle, if the enum values have a Display(Name = "xxx") attribute. Then use this control extender as the form control for the required field.

public class EnumSelectorExtender : ControlExtender<CMSFormControls_System_EnumSelector>
{
    private static readonly Dictionary<string, Dictionary<string, string>> DisplayNameLookup = new Dictionary<string, Dictionary<string, string>>();

    private string _typeKey;

    public override void OnInit()
    {
        Control.Load += ControlOnLoad;
    }

    private void ControlOnLoad(object sender, EventArgs e)
    {
        // Create a cached lookup table of the enum friendly names.
        _typeKey = $"{Control.AssemblyName}.{Control.TypeName}";

        EnsureDisplayNameLookup();

        // Replace the values of the dropdown with friendly names if possible.
        foreach (ListItem li in Control.CurrentSelector.Items)
        {
            if (DisplayNameLookup[_typeKey].TryGetValue(li.Text, out var displayName)) li.Text = displayName;
        }
    }

    private void EnsureDisplayNameLookup()
    {
        if (!DisplayNameLookup.ContainsKey(_typeKey))
        {
            DisplayNameLookup.Add(_typeKey, new Dictionary<string, string>());
            var type = TryGetEnumType(Control.AssemblyName, Control.TypeName);
            if (type != null)
            {
                DisplayNameLookup[_typeKey] = type.GetFields(BindingFlags.Static | BindingFlags.Public)
                    .ToDictionary(f => $"{type.Name}.{f.Name}",
                        f => f.GetCustomAttribute<DisplayAttribute>()?.Name);
            }
        }
    }

    private Type TryGetEnumType(string assemblyname, string typeName)
    {
        if (!String.IsNullOrWhiteSpace(assemblyname) && !String.IsNullOrWhiteSpace(typeName))
        {
            var type = ClassHelper.GetClassType(assemblyname, typeName);

            if ((type != null) && type.IsEnum)
            {
                return type;
            }
        }

        return null;
    }
}

This was after digging into the code for CMSFormControls_System_EnumSelector to see how it works. It would be nice if that control took notice of the standard System.CompononentModel.Display attribute out of the box.

0 votesVote for this answer Mark as a Correct answer

Tandy Smith answered on July 28, 2021 09:01

Hey guys,

Was Andy's solution the only option here? How do we implement that Andy? Like, how does that extender actually get called/used?

0 votesVote for this answer Mark as a Correct answer

Andy Cook answered on July 30, 2021 02:45

Hi Tandy,

You add the control extender and register it like any other custom form control. Assign it to the field in a page type and configure it under the Advanced dropdown under "Editing control settings".

Try the Kentico docs for some more info on this:

https://docs.xperience.io/k11/custom-development/developing-form-controls/example-developing-custom-form-controls

Andy

Andy

0 votesVote for this answer Mark as a Correct answer

   Please, sign in to be able to submit a new answer.