Home » Rules Engine » Actions » Customize How Item Bucket Folder Paths are Created Using the Sitecore Rules Engine

Customize How Item Bucket Folder Paths are Created Using the Sitecore Rules Engine

Sitecore Technology MVP 2016
Sitecore MVP 2015
Sitecore MVP 2014

Enter your email address to follow this blog and receive notifications of new posts by email.

I bet you all love Sitecore Item Buckets, right? Everyone loves Item Buckets, even this cat:

Cat-Traps-Itself-Inside-Bucket

If you don’t know what Item Buckets are — and if you don’t I recommend having a read of this pdf — they are Items that contain a nested structure of Bucket Folder Items, and the leaf Bucket Folder Items contain bucketable Items.

What’s the purpose of this? Well, Item Buckets offer a way to store a massive amount of Items under the nested structure of Bucket Folders Items. This offers better performance over having a large amount of Items under one parent Item:

item-bucket

By default, the Bucket Folder Items are named based on a DateTime format which is housed in a configuration setting in Sitecore:

bucket-folder-date-time-format

One thing I’ve always been curious about was what code used the above setting to create path for these Bucket Folders — I wanted to see if I could override it, and add my own logic to name the Bucket Folder Items using some different algorithm.

I found that this code lives in Sitecore.Buckets.Util.BucketFolderPathResolver in Sitecore.Buckets.dll.

However, that wasn’t the only thing I saw in this class — I also saw code in there leveraging the Sitecore Rules Engine as well. Basically the Rules Engine code would execute first, and if it returned no path structure — as a string — the default DateTime format code would execute.

After talking with Sitecore MVP Robbert Hock about it on the Sitecore Community Slack, he shared the following post by Alex van Wolferen which highlights that you can create custom conditions and actions for the Rules Engine to generate a custom folder path for the Bucket Folders, and even showed how you can set this stuff up in the Content Editor. Really? How did I not know about this? I must have been asleep when the Item Buckets feature was released. 😉

The only unfortunate thing about that blog post is there is no code to look at. 😦

As an alternative method of discovery, I surfed through Sitecore.Buckets.dll to see how the existing Bucket conditions and actions were built, and then decided have a bit of fun: I created two custom conditions and one custom action.

The following class serves as my first custom condition which determines if the Item Bucket has any presentation:

using Sitecore.Buckets.Rules.Bucketing;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Pipelines.HasPresentation;
using Sitecore.Rules.Conditions;

namespace Sitecore.Sandbox.Buckets.Rules.Conditions
{
    public class WhenBucketHasNoPresentation<TRuleContext> : WhenCondition<TRuleContext> where TRuleContext : BucketingRuleContext
    {
        protected override bool Execute(TRuleContext ruleContext)
        {
            Assert.ArgumentNotNull(ruleContext, "ruleContext");
            Assert.ArgumentNotNull(ruleContext.BucketItem, "ruleContext.BucketItem");
            return !HasPresentation(ruleContext.BucketItem);
        }

        protected virtual bool HasPresentation(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            return HasPresentationPipeline.Run(item);
        }
    }
}

The Execute() method in the above class gets an instance of the Item Bucket from the BucketingRuleContext instance, and passes it off to the HasPresentation() method which determines whether the Item Bucket has any presentation components defined on it.

In order for Sitecore to know about the class above, we need to create a new Condition Item. I did just that here:

bucket-folder-path-condition-no-presentation

The following class serves as another condition though this condition determines if the Item Bucket has N or more child Items underneath it (N is an integer set on the rule which I show further down in this post):

using Sitecore.Buckets.Rules.Bucketing;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Rules.Conditions;

namespace Sitecore.Sandbox.Buckets.Rules.Conditions
{
    public class WhenBucketHasNChildrenOrMore<TRuleContext> : WhenCondition<TRuleContext> where TRuleContext : BucketingRuleContext
    {
        public string Value { get; set; }

        protected override bool Execute(TRuleContext ruleContext)
        {
            Assert.ArgumentNotNull(ruleContext, "ruleContext");
            Assert.ArgumentNotNull(ruleContext.BucketItem, "ruleContext.BucketItem");
            return ShouldExecute(ruleContext.BucketItem);
        }

        protected virtual bool ShouldExecute(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            int excessiveNumber = GetExcessiveNumberOfChildren();
            return excessiveNumber > 0 && item.Children.Count >= excessiveNumber;
        }

        protected virtual int GetExcessiveNumberOfChildren()
        {
            int excessiveNumber;
            if(!int.TryParse(Value, out excessiveNumber))
            {
                return 0;
            }

            return excessiveNumber;
        }
    }
}

The GetExcessiveNumberOfChildren() method tries to parse an integer from the value set on the Value property on the class instance. If the value is not set, it just returns zero.

The above method is called by the ShouldExecute() method which ascertains whether the value is greater zero, and if it is, whether the Item Bucket has that amount of child Items or more. If both of these are true, the method returns true; false otherwise.

The boolean value of the ShouldExecute() method is then returned to the caller via the Execute() method.

Just like the last condition class, we have to let Sitecore know about it. I did that by creating another Condition Item:

bucket-folder-path-condition-number-of-children

Now we need a class that builds up the Bucket Folder path. The following class serves as an action to do just that:

using System;
using System.Collections.Generic;

using Sitecore.Buckets.Rules.Bucketing;
using Sitecore.Diagnostics;
using Sitecore.Rules.Actions;

namespace Sitecore.Sandbox.Buckets.Rules.Actions
{
    public class CreateRandomIntegerBasedPath<TContext> : RuleAction<TContext> where TContext : BucketingRuleContext
    {
        private const int LengthOfInteger = 3;

        protected static readonly Random Random = new Random();

        public string Levels { get; set; }

        public override void Apply(TContext ruleContext)
        {
            Assert.ArgumentNotNull(ruleContext, "ruleContext");
            int levels = GetLevels();
            if(levels < 1)
            {
                Log.Error(string.Format("Cannot apply CreateRandomIntegerBasedPath action. The value of levels: {0}", Levels), this);
                return;
            }

            IEnumerable<string> integers = GetRandomIntegers(LengthOfInteger, levels);
            ruleContext.ResolvedPath = string.Join(Sitecore.Buckets.Util.Constants.ContentPathSeperator, integers);
        }

        protected virtual int GetLevels()
        {
            int levels;
            if(!int.TryParse(Levels, out levels) || levels < 1)
            {
                return 0;
            }

            return levels;
        }
        
        protected virtual IEnumerable<string> GetRandomIntegers(int lengthOfInteger, int count)
        {
            IList<string> strings = new List<string>();
            for (int i = 0; i < count; i++)
            {
                int number = GetRandomPowerOfTenNumber(lengthOfInteger);
                strings.Add(number.ToString());
            }

            return strings;
        }

        private int GetRandomPowerOfTenNumber(int exponent)
        {
            return Random.Next((int)Math.Pow(10, exponent - 1), (int)Math.Pow(10, exponent) - 1);
        }
    }
}

The class above, in a nutshell, reads in the value that governs how deep the folder structure should be (i.e. this is stored in the Levels string property which is set on the rule further down in this post); tries to parse this as an integer (we can’t do much if it’s not an integer); creates N strings composed of random numbers between 100 and 999 with endpoints inclusive (N is the integer value of Levels); builds up a string delimited by “/” to create a folder path; and returns it to the caller of the Apply() method.

Just like the condition classes, we need to register the above action class in Sitecore. I did this by creating a custom Action Item:

bucket-folder-path-action-create-random-folder-path

Now that we have our custom conditions and action ready to go, we need to piece them together into a rule. I did that in the ‘Rules for Resolving the Bucket Folder Path’ field on /sitecore/system/Settings/Buckets/Item Buckets Settings:

bucket-folder-path-item-buckets-settings

Let’s take this for a spin!

I created some test Items with some child items. The following Item has no presentation and 5 child items:

bucket-folder-path-5-items-no-presentation

Let’s turn it into an Item Bucket:

bucket-folder-path-5-items-no-presentation-click-bucket

As you can see, it has Bucket Folders with random integers as their Item names:

bucket-folder-path-5-items-no-presentation-bucket

Let’s now try this on an Item that has no presentation but only 4 child items:

bucket-folder-path-4-items-no-presentation

Let’s convert this Item into an Item Bucket:

bucket-folder-path-4-items-no-presentation-click-bucket

As you can see, the default Item Bucket folder structure was created:

bucket-folder-path-4-items-no-presentation-bucket

If you have any thoughts on this, please share in a comment.


2 Comments

  1. […] Customize How Item Bucket Folder Paths are Created Using the Sitecore Rules Engine […]

  2. […] Customize How Item Bucket Folder Paths are Created Using the Sitecore Rules Engine […]

Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.