Improved CategoryList editor descriptor for EPiServer

Mattias Olsson 06.05.2015 01.08.31

Of course, you could easily accomplish a filtered category list with the [SelectOne] or [SelectMany] attribute on a string property together with a ISelectionFactory, but I really don't like having to parse that string into a list of integers. I want a CategoryList! Period. :) My idea was to override the editor descriptor for CategoryList that's built into EPiServer. To be able to configure root category id or name, I created a custom attribute.

L'attribut

I added three root category configuration properties to my attribute, RootCategoryId, RootCategoryName and RootCategoryAppSettingKey. One of them is selected in the following priority:

  1. RootCategoryId
  2. RootCategoryName
  3. RootCategoryAppSettingKey (Name of appSetting key with root category id value)
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class CategorySelectionAttribute : Attribute
{
    /// 
    ///  ID of the root category.
    /// 
    public int RootCategoryId { get; set; }

    /// 
    /// Name of the root category.
    /// 
    public string RootCategoryName { get; set; }

    /// 
    /// The appSetting key containing the root category id to use.
    /// 
    public string RootCategoryAppSettingKey { get; set; }

    public int GetRootCategoryId()
    {
        if (RootCategoryId > 0)
        {
            return RootCategoryId;
        }

        if (!string.IsNullOrWhiteSpace(RootCategoryName))
        {
            var category = Category.Find(RootCategoryName);

            if (category != null)
            {
                return category.ID;
            }
        }

        if (!string.IsNullOrWhiteSpace(RootCategoryAppSettingKey))
        {
            string appSettingValue = ConfigurationManager.AppSettings[RootCategoryAppSettingKey];
            int rootCategoryId;

            if (!string.IsNullOrWhiteSpace(appSettingValue) && int.TryParse(appSettingValue, out rootCategoryId))
            {
                return rootCategoryId;
            }
        }

        return Category.GetRoot().ID;
    }
}


Editor descriptor

To make things easy I inherited the CategoryListEditorDescriptor in EPiServer and set the EditorDescriptorBehavior to OverrideDefault. What I'm doing in the code is to check if one of the attributes is a CategorySelectionAttribute, and if true, I replace the settings that is passed to the dojo widget (epi-cms.widget.CategorySelector). Lucky for me, the category widget in EPiServer already has support for specifying a root category. :)

[EditorDescriptorRegistration(TargetType = typeof(CategoryList), EditorDescriptorBehavior = EditorDescriptorBehavior.OverrideDefault)]
public class CustomCategoryListEditorDescriptor : CategoryListEditorDescriptor
{
    public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable attributes)
    {
        base.ModifyMetadata(metadata, attributes);
        var categorySelectionAttribute = 
            attributes.OfType<CategorySelectionAttribute>().FirstOrDefault();

        if (categorySelectionAttribute != null)
        {
            metadata.EditorConfiguration["root"] =
                categorySelectionAttribute.GetRootCategoryId();
            return;
        }

        var contentTypeCategorySelectionAttribute =
            metadata.ContainerType.GetCustomAttribute<CategorySelectionAttribute>(true);

        if (contentTypeCategorySelectionAttribute != null)
        {
            metadata.EditorConfiguration["root"] =
                contentTypeCategorySelectionAttribute.GetRootCategoryId();
        }
    }
}


Usage examples

[CategorySelection(RootCategoryId = 123)]
public virtual CategoryList MyCategories { get; set; }

[CategorySelection(RootCategoryName = "MyRootCategory")]
public virtual CategoryList MyCategories { get; set; }

[CategorySelection(RootCategoryAppSettingKey = "NewsRootCategoryId")]
public virtual CategoryList MyCategories { get; set; }

The last example uses an appSetting to set root category id

<appSettings>
    <add key="NewsRootCategoryId" value="123" />
</appSettings>


Using the attribute on a content type class

You can also use the CategorySelectionAttribute on a content type class to set root category for all CategoryList properties, including the built-in PageCategory property. Property attribute targets have higher priority than class so you can still override class settings for specific properties.

[CategorySelection(RootCategoryId = 123)]
[ContentType]
public class StartPage : PageData
{
    // Overrides settings on the class for this property
    [CategorySelection(RootCategoryId = 321)] 
    public virtual CategoryList MyCategory { get; set; }
}