Improved CategoryList editor descriptor for EPiServer
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:
- RootCategoryId
- RootCategoryName
- 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; } }