Home » Items (Page 2)

Category Archives: Items

Restrict Object Instantiation via the Singleton Pattern in Sitecore

This post is a continuation of a series of posts I’m putting together around using design patterns in Sitecore, and will show a “proof of concept” around using the Singleton pattern — a creational pattern which restricts the creation of a class to only one instance, and also provides a global reference to it.

In this “proof of concept”, I am using a Singleton which contains a method that will “un-parent” an Item — all of the Item’s children will become its siblings in the Sitecore content tree — and will utilize this in a command which will be wired-up to the Sitecore Ribbon. I honestly don’t see much utility in having such functionality in Sitecore — you just might πŸ™‚ — but the functionality itself is not the purpose of this post.

I first started out by creating the following class which serves as the Singleton:

using System.Linq;

using Sitecore.Data.Items;
using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Items
{
    public class ItemOperations
    {
        private static volatile ItemOperations current;
        
        private static object locker = new object();

        private ItemOperations() 
        { 
        }

        public static ItemOperations Current
        {
            get 
            {
                if (current == null) 
                {
                    lock (locker) 
                    {
                        if (current == null)
                        {
                            current = new ItemOperations();
                        }
                    }
                }

                return current;
            }
        }

        public void Unparent(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            if(!item.Children.Any())
            {
                return;
            }

            foreach(Item child in item.Children)
            {
                child.MoveTo(item.Parent);
            }
        }
    }
}

Before going into the details of why the class above is a Singleton, I want to point out that it contains one method — the Unparent() method — which iterates over all child Items of the Item passed to it, and moves them under the passed Item’s parent — this will move these child Items to the same level as their former parent.

You might be asking “what makes this a Singleton”? The first clue is the private constructor — this class cannot be instantiated by other classes. It can only be instantiated from within itself.

This instantiation is being done in the logic of its Current property. If the static private variable “current” is null, we “lock” an arbitrary object — this is done to make this multithreading “friendly” so that we can avoid collisions in the case when two different threads invoke the Current property at about the exact same time — and then create an instance of the ItemOperations class. This instance is then saved in the static variable “current”.

When the next call to the Current property is made on this class’ type, the “current” variable is already set, so the instance stored in it is returned to the caller.

The reason why the variable “current” and its associated property “Current” are static is to ensure these aren’t bound to an instance of the class but instead are bound to the class’ type, and the “Current” property can be accessed directly on the class’ name.

I then created the following subclass of Sitecore.Shell.Framework.Commands.Command which is needed for integration into the Sitecore Ribbon:

using System.Collections.Generic;
using System.Linq;

using Sitecore.Data.Items;
using Sitecore.Shell.Framework.Commands;

using Sitecore.Sandbox.Items;

namespace Sitecore.Sandbox.Shell.Framework.Commands
{
    public class Unparent : Command
    {
        public Unparent()
        {
        }

        public override void Execute(CommandContext context)
        {
            Item item = GetItem(context);
            ItemOperations.Current.Unparent(item);
        }

        public override CommandState QueryState(CommandContext context)
        {
            Item item = GetItem(context);
            if (item == null || !item.Children.Any())
            {
                return CommandState.Hidden;
            }

            return CommandState.Enabled;
        }

        protected virtual Item GetItem(CommandContext context)
        {
            if(!context.Items.Any())
            {
                return null;
            }

            return context.Items.First();
        }
    }
}

The QueryState() method checks to see whether the selected Item in the Sitecore content tree has any children and returns the Hidden value on the Sitecore.Shell.Framework.Commands.CommandState enum — this lets the Sitecore Client code know that the button associated with this command should be hidden. If the Item does have children, the Enabled value on the Sitecore.Shell.Framework.Commands.CommandState enum is returned.

The Execute() method just passes the selected Item in the Sitecore content tree to the Unparent() method on the ItemOperations Singleton above — the Unparent() method is where the child Items are moved up a level.

I then had to register the command above with Sitecore via the following patch configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <commands>
      <command name="item:Unparent" type="Sitecore.Sandbox.Shell.Framework.Commands.Unparent, Sitecore.Sandbox" />
    </commands>
  </sitecore>
</configuration>

Now that the above command is registered in Sitecore, we must bind it to Sitecore ribbon. I did that in the core database:

unparent-ribbon

I won’t go into details of the above as I’ve already covered how to do so in many of my previous posts — do have a look!

Let’s take this for a spin!

Let’s choose an Item with some child Items:

unparent-1

After clicking the “Unparent” button in the Ribbon, I saw the following:

unparent-2

As you can see, this Item was “un-parented”.

Ok, now that we had some fun with this, let’s have a serious discussion about the Singleton pattern — yeah, I know serious discussions aren’t always pleasant but what I have to say is pretty important regarding this pattern.

Although the Singleton pattern does make it easy to implement things quite fast, and does allow for less memory usage due to having less object instances floating around in memory, it unfortunately promotes tight coupling in your classes.

If you have to change something on the Singleton class itself — perhaps a method signature on it — you will have to also update every single class that references it. This can be quite costly from a development effort and painful — especially if the Singleton is being referenced in a gazillion places ( is gazillion a word? πŸ˜‰ ).

So, please do think twice before using this pattern — sure, it might be alright to use a Singleton in a pinch but do be sure it won’t lead to a maintenance nightmare for future development in your Sitecore solutions.

Use the Factory Method Pattern for Object Creation in Sitecore

This post is a continuation of a series of posts I’m putting together around using design patterns in Sitecore, and will show a “proof of concept” around using the Factory Method pattern — a creational pattern whereby client code obtain instances of objects without knowing the concrete class types of these objects. This pattern promotes loose coupling between objects being created and the client code that use them.

In this “proof of concept”, I am using an Item Validator to call a factory method to obtain a “fields comparer” object to ascertain whether one field contains a value greater than a value in another field, and will show this for two different field types in Sitecore.

I first defined an interface for objects that will compare values in two Sitecore fields:

using Sitecore.Data.Fields;

namespace Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers
{
    public interface IFieldsComparer
    {
        bool IsFieldOneLessThanOrEqualToFieldTwo(Field fieldOne, Field fieldTwo);
    }
}

Instances of classes that implement the IFieldsComparer interface above will ascertain whether the value in fieldOne is less than or equal to the value in fieldTwo.

I then defined a class that implements the IFieldsComparer interface to compare integer values in two fields:

using System;

using Sitecore.Data.Fields;
using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers
{
    public class IntegerFieldsComparer : IFieldsComparer
    {
        public bool IsFieldOneLessThanOrEqualToFieldTwo(Field fieldOne, Field fieldTwo)
        {
            Assert.ArgumentNotNull(fieldOne, "fieldOne");
            Assert.ArgumentNotNull(fieldTwo, "fieldTwo");
            return ParseInteger(fieldOne) <= ParseInteger(fieldTwo);
        }

        protected virtual int ParseInteger(Field field)
        {
            int fieldValue;
            int.TryParse(field.Value, out fieldValue);
            return fieldValue;
        }
    }
}

There isn’t much to see in the class above. The class parses the integer values in each field, and checks to see if the value in fieldOne is less than or equal to the value in fieldTwo.

Now, let’s create a another class — one that compares DateTime values in two different fields:

using System;

using Sitecore.Data.Fields;
using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers
{
    public class DateFieldsComparer : IFieldsComparer
    {
        public bool IsFieldOneLessThanOrEqualToFieldTwo(Field fieldOne, Field fieldTwo)
        {
            Assert.ArgumentNotNull(fieldOne, "fieldOne");
            Assert.ArgumentNotNull(fieldTwo, "fieldTwo");
            return ParseDateTime(fieldOne) <= ParseDateTime(fieldTwo);
        }

        protected virtual DateTime ParseDateTime(Field field)
        {
            return DateUtil.IsoDateToDateTime(field.Value);
        }
    }
}

Similarly to the IFieldsComparer class for integers, the class above parses the field values into DateTime instances, and ascertains whether the DateTime value in fieldOne occurs before or at the same time as the DateTime value in fieldTwo.

You might now be asking “Mike, what about other field types?” Well, I could have defined more IFieldsComparer classes for other fields but this post would go on and on, and we both don’t want that πŸ˜‰ So, to account for other field types, I’ve defined the following Null Object for fields that are not accounted for:

using Sitecore.Data.Fields;

namespace Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers
{
    public class NullFieldsComparer : IFieldsComparer
    {
        public bool IsFieldOneLessThanOrEqualToFieldTwo(Field fieldOne, Field fieldTwo)
        {
            return true;
        }
    }
}

The Null Object class above just returns true without performing any comparison.

Now that we have “fields comparers”, we need a Factory method. I’ve defined the following interface for objects that will create instances of our IFieldsComparer:

using Sitecore.Data.Fields;

namespace Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers
{
    public interface IFieldsComparerFactory
    {
        IFieldsComparer GetFieldsComparer(Field fieldOne, Field fieldTwo);
    }
}

Instances of classes that implement the interface above will return the appropriate IFieldsComparer for comparing the two passed fields.

The following class implements the IFieldsComparerFactory interface above:

using System;
using System.Collections.Generic;
using System.Xml;

using Sitecore.Configuration;
using Sitecore.Data.Fields;
using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers
{
    public class FieldsComparerFactory : IFieldsComparerFactory
    {
        private static volatile IFieldsComparerFactory current;
        private static object locker = new Object();

        public static IFieldsComparerFactory Current
        {
            get
            {
                if (current == null)
                {
                    lock (locker)
                    {
                        if (current == null)
                        {
                            current = CreateNewFieldsComparerFactory();
                        }
                    }
                }

                return current;
            }
        }

        private static IDictionary<string, XmlNode> FieldsComparersTypes { get; set; }

        private IFieldsComparer NullFieldsComparer { get; set; }

        static FieldsComparerFactory()
        {
            FieldsComparersTypes = new Dictionary<string, XmlNode>();
        }

        public IFieldsComparer GetFieldsComparer(Field fieldOne, Field fieldTwo)
        {
            Assert.IsNotNull(NullFieldsComparer, "NullFieldsComparer must be set in configuration!");
            if (!AreEqualIgnoreCase(fieldOne.Type, fieldTwo.Type) || !FieldsComparersTypes.ContainsKey(fieldOne.Type))
            {
                return NullFieldsComparer;
            }

            XmlNode configNode = FieldsComparersTypes[fieldOne.Type];
            if(configNode == null)
            {
                return NullFieldsComparer;
            }

            IFieldsComparer comparer = Factory.CreateObject(configNode, false) as IFieldsComparer;
            if (comparer == null)
            {
                return NullFieldsComparer;
            }

            return comparer;
        }

        private static bool AreEqualIgnoreCase(string stringOne, string stringTwo)
        {
            return string.Equals(stringOne, stringTwo, StringComparison.CurrentCultureIgnoreCase);
        }

        protected virtual void AddFieldsComparerConfigNode(XmlNode configNode)
        {
            if(configNode.Attributes["fieldType"] == null || string.IsNullOrWhiteSpace(configNode.Attributes["fieldType"].Value))
            {
                return;
            }

            if (configNode.Attributes["type"] == null || string.IsNullOrWhiteSpace(configNode.Attributes["type"].Value))
            {
                return;
            }

            FieldsComparersTypes[configNode.Attributes["fieldType"].Value] = configNode;
        }

        private static IFieldsComparerFactory CreateNewFieldsComparerFactory()
        {
            return Factory.CreateObject("factories/fieldsComparerFactory", true) as IFieldsComparerFactory;
        }
    }
}

The AddFieldsComparerConfigNode() method above is used by the Sitecore Configuration Factory to add configuration-defined Xml nodes that define field types and their IFieldsComparer — these are placed into the FieldsComparersTypes dictionary for later look-up and instantiation.

The GetFieldsComparer() factory method tries to figure out which IFieldsComparer to return from the FieldsComparersTypes dictionary. If an appropriate IFieldsComparer is found for the two fields, the method uses Sitecore.Configuration.Factory.CreateObject() — this is defined in Sitecore.Kernel.dll — to create the instance that is defined in the type attribute of the XmlNode that is stored in the FieldsComparersTypes dictionary.

If an appropriate IFieldsComparer cannot be determined for the passed fields, then the Null Object IFieldsComparer — this is injected into the NullFieldsComparer property via the Sitecore Configuration Factory — is returned.

As a quick and dirty solution for retrieving an instance of the class above, I’ve used the Singleton pattern. An instance of the class above is created by the Sitecore Configuration Factory via the CreateNewFieldsComparerFactory() method, and is placed into the Current property.

I then defined all of the above in the following Sitecore patch configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <factories>
      <fieldsComparerFactory type="Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers.FieldsComparerFactory, Sitecore.Sandbox">
        <NullFieldsComparer type="Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers.NullFieldsComparer, Sitecore.Sandbox" />
        <fieldComparers hint="raw:AddFieldsComparerConfigNode">
          <fieldComparer fieldType="Datetime" type="Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers.DateFieldsComparer, Sitecore.Sandbox" />
          <fieldComparer fieldType="Integer" type="Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers.IntegerFieldsComparer, Sitecore.Sandbox" />
        </fieldComparers>
      </fieldsComparerFactory>
    </factories>
  </sitecore>
</configuration>

Now that we have our factory in place, we need an Item Validator to use it:

using System.Runtime.Serialization;

using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Data.Validators;

using Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers;

namespace Sitecore.Sandbox.Data.Validators.ItemValidators
{
    public class FieldOneValueLessThanOrEqualToFieldTwoValueValidator : StandardValidator
    {
        public override string Name
        {
            get
            {
                return Parameters["Name"];
            }
        }

        private string fieldOneName;
        private string FieldOneName
        {
            get
            {
                if (string.IsNullOrWhiteSpace(fieldOneName))
                {
                    fieldOneName = Parameters["FieldOneName"];
                }

                return fieldOneName;
            }
        }

        private string fieldTwoName;
        private string FieldTwoName
        {
            get
            {
                if (string.IsNullOrWhiteSpace(fieldTwoName))
                {
                    fieldTwoName = Parameters["FieldTwoName"];
                }

                return fieldTwoName;
            }
        }

        public FieldOneValueLessThanOrEqualToFieldTwoValueValidator()
        {
        }

        public FieldOneValueLessThanOrEqualToFieldTwoValueValidator(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }

        protected override ValidatorResult Evaluate()
        {
            Item item = GetItem();
            if (IsValid(item))
            {
                return ValidatorResult.Valid;
            }

            Text = GetErrorMessage(item);
            return GetFailedResult(ValidatorResult.Warning);
        }

        private bool IsValid(Item item)
        {
            if (item == null || string.IsNullOrWhiteSpace(FieldOneName) || string.IsNullOrWhiteSpace(FieldTwoName))
            {
                return true;
            }

            Field fieldOne = item.Fields[FieldOneName];
            Field fieldTwo = item.Fields[FieldTwoName];
            if(fieldOne == null || fieldTwo == null)
            {
                return true;
            }

            return IsFieldOneLessThanOrEqualToFieldTwo(fieldOne, fieldTwo);
        }

        private bool IsFieldOneLessThanOrEqualToFieldTwo(Field fieldOne, Field fieldTwo)
        {
            IFieldsComparer fieldComparer = GetFieldsComparer(fieldOne, fieldTwo);
            return fieldComparer.IsFieldOneLessThanOrEqualToFieldTwo(fieldOne, fieldTwo);
        }

        protected virtual IFieldsComparer GetFieldsComparer(Field fieldOne, Field fieldTwo)
        {
            return FieldsComparerFactory.Current.GetFieldsComparer(fieldOne, fieldTwo);
        }

        protected virtual string GetErrorMessage(Item item)
        {
            string message = Parameters["ErrorMessage"];
            if (string.IsNullOrWhiteSpace(message))
            {
                return string.Empty;
            }

            message = message.Replace("$fieldOneName", FieldOneName);
            message = message.Replace("$fieldTwoName", FieldTwoName);

            return GetText(message, new[] { item.DisplayName });
        }

        protected override ValidatorResult GetMaxValidatorResult()
        {
            return base.GetFailedResult(ValidatorResult.Suggestion);
        }
    }
}

The real magic of the class above occurs in the IsValid(), IsFieldOneLessThanOrEqualToFieldTwo() and GetFieldsComparer() methods.

The IsValid() method gets the two fields being compared, and passes these along to the IsFieldOneLessThanOrEqualToFieldTwo() method.

The IsFieldOneLessThanOrEqualToFieldTwo() method passes the two fields to the GetFieldsComparer() — this returns the appropriate IFieldsComparer from the GetFieldsComparer() factory method on the FieldsComparerFactory Singleton — and uses the IFieldsComparer to ascertain whether the value in fieldOne is less than or equal to the value in fieldTwo.

If the value in fieldOne is less than or equal to the value in fieldTwo then the Item has passed validation. Otherwise, it has not, and an error message is passed back to the Sitecore client — we are replacing some tokens for fieldOne and fieldTwo in a format string to give the end user some information on the fields that are in question.

I then set up the Item Validator for Integer fields:

integer-comparer-item-validator

I also set up another Item Validator for Datetime fields:

datetime-item-validator

Let’s take this for a spin!

I entered some integer values in the two integer fields being compared:

integer-fields-item

As you can see, we get a warning.

I then set some Datetime field values on the two Datetime fields being compared:

datetime-fields-item

Since ‘Datetime One’ occurs in time after ‘Datetime Two’, we get a warning as expected.

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

Utilize the Strategy Design Pattern for Content Editor Warnings in Sitecore

This post is a continuation of a series of posts I’m putting together around using design patterns in Sitecore implementations, and will show a “proof of concept” around using the Strategy pattern — a pattern where a family of “algorithms” (for simplicity you can think of these as classes that implement the same interface) which should be interchangeable when used by client code, and such holds true even when each do something completely different than others within the same family.

The Strategy pattern can serve as an alternative to the Template method pattern — a pattern where classes have an abstract base class that defines most of an β€œalgorithm” for how classes that inherit from it work but provides method stubs (abstract methods) and method hooks (virtual methods) for subclasses to implement or override — and will prove this in this post by providing an alternative solution to the one I had shown in my previous post on the Template method pattern.

In this “proof of concept”, I will be adding a processor to the <getContentEditorWarnings> pipeline in order to add custom content editor warnings for Items — if you are unfamiliar with content editor warnings in Sitecore, the following screenshot illustrates an “out of the box” content editor warning around publishing and workflow state:

content-editor-warning-example

To start, I am reusing the following interface and classes from my previous post on the Template method pattern:

using System.Collections.Generic;

namespace Sitecore.Sandbox.Pipelines.GetContentEditorWarnings
{
    public interface IWarning
    {
        string Title { get; set; }

        string Message { get; set; }

        List<CommandLink> Links { get; set; }

        bool HasContent();

        IWarning Clone();
    }
}

Warnings will have a title, an error message for display, and a list of Sheer UI command links — the CommandLink class is defined further down in this post — to be displayed and invoked when clicked.

You might be asking why I am defining this when I can just use what’s available in the Sitecore API? Well, I want to inject these values via the Sitecore Configuration Factory, and hopefully this will become clear once you have a look at the Sitecore configuration file further down in this post.

Next, we have a class that implements the interface above:

using System.Collections.Generic;

namespace Sitecore.Sandbox.Pipelines.GetContentEditorWarnings
{
    public class Warning : IWarning
    {
        public string Title { get; set; }

        public string Message { get; set; }

        public List<CommandLink> Links { get; set; }

        public Warning()
        {
            Links = new List<CommandLink>();
        }

        public bool HasContent()
        {
            return !string.IsNullOrWhiteSpace(Title)
                    || !string.IsNullOrWhiteSpace(Title)
                    || !string.IsNullOrWhiteSpace(Message);
        }

        public IWarning Clone()
        {
            IWarning clone = new Warning { Title = Title, Message = Message };
            foreach (CommandLink link in Links)
            {
                clone.Links.Add(new CommandLink { Text = link.Text, Command = link.Command });
            }

            return clone;
        }
    }
}

The HasContent() method just returns “true” if the instance has any content to display though this does not include CommandLinks — what’s the point in displaying these if there is no warning content to be displayed with them?

The Clone() method makes a new instance of the Warning class, and copies values into it — this is useful when defining tokens in strings that must be expanded before being displayed. If we expand them on the instance that is injected via the Sitecore Configuration Factory, the changed strings will persistent in memory until the application pool is recycled for the Sitecore instance.

The following class represents a Sheer UI command link to be displayed in the content editor warning so content editors/authors can take action on the warning:


namespace Sitecore.Sandbox.Pipelines.GetContentEditorWarnings
{
    public class CommandLink
    {
        public string Text { get; set; }

        public string Command { get; set; }
    }
}

The Strategy pattern calls for a family of “algorithms” which can be interchangeably used. In order for us to achieve this, we need to define an interface for this family of “algorithms”:

using System.Collections.Generic;

using Sitecore.Data.Items;

namespace Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.Strategy_Pattern
{
    public interface IWarningsGenerator
    {
        Item Item { get; set; }

        IEnumerable<IWarning> Generate();
    }
}

Next, I created the following class that implements the interface above to ascertain whether a supplied Item has too many child Items:

using System.Collections.Generic;

using Sitecore.Data.Items;
using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.Strategy_Pattern
{
    public class TooManyChildItemsWarningsGenerator : IWarningsGenerator
    {
        private int MaxNumberOfChildItems { get; set; }

        private IWarning Warning { get; set; }

        public Item Item { get; set; }

        public IEnumerable<IWarning> Generate()
        {
            AssertProperties();
            if (Item.Children.Count <= MaxNumberOfChildItems)
            {
                return new List<IWarning>();
            }

            return new[] { Warning };
        }

        private void AssertProperties()
        {
            Assert.ArgumentCondition(MaxNumberOfChildItems > 0, "MaxNumberOfChildItems", "MaxNumberOfChildItems must be set correctly in configuration!");
            Assert.IsNotNull(Warning, "Warning", "Warning must be set in configuration!");
            Assert.ArgumentCondition(Warning.HasContent(), "Warning", "Warning should have some fields populated from configuration!");
            Assert.IsNotNull(Item, "Item", "Item must be set!");
        }
    }
}

The “maximum number of child items allowed” value — this is stored in the MaxNumberOfChildItems integer property of the class — is passed to the class instance via the Sitecore Configuration Factory (you’ll see this defined in the Sitecore configuration file further down in this post).

The IWarning instance that is injected into the instance of this class will give content authors/editors the ability to convert the Item into an Item Bucket when it has too many child Items.

I then defined another class that implements the interface above — a class whose instances determine whether Items have invalid characters in their names:

using System.Collections.Generic;
using System.Linq;

using Sitecore.Data.Items;
using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.Strategy_Pattern
{
    public class HasInvalidCharacetersInNameWarningsGenerator : IWarningsGenerator
    {
        private string CharacterSeparator { get; set; }

        private string Conjunction { get; set; }

        private List<string> InvalidCharacters { get; set; }

        private IWarning Warning { get; set; }

        public Item Item { get; set; }

        public HasInvalidCharacetersInNameWarningsGenerator()
        {
            InvalidCharacters = new List<string>();
        }

        public IEnumerable<IWarning> Generate()
        {
            AssertProperties();
            HashSet<string> charactersFound = new HashSet<string>();
            foreach (string character in InvalidCharacters)
            {
                if (Item.Name.Contains(character))
                {
                    charactersFound.Add(character.ToString());
                }
            }

            if(!charactersFound.Any())
            {
                return new List<IWarning>();
            }

            IWarning warning = Warning.Clone();
            string charactersFoundString = string.Join(CharacterSeparator, charactersFound);
            int lastSeparator = charactersFoundString.LastIndexOf(CharacterSeparator);
            if (lastSeparator < 0)
            {
                warning.Message = ReplaceInvalidCharactersToken(warning.Message, charactersFoundString);
                return new[] { warning };
            }

            warning.Message = ReplaceInvalidCharactersToken(warning.Message, Splice(charactersFoundString, lastSeparator, CharacterSeparator.Length, Conjunction));
            return new[] { warning };
        }

        private void AssertProperties()
        {
            Assert.IsNotNullOrEmpty(CharacterSeparator, "CharacterSeparator", "CharacterSeparator must be set in configuration!");
            Assert.ArgumentCondition(InvalidCharacters != null && InvalidCharacters.Any(), "InvalidCharacters", "InvalidCharacters must be set in configuration!");
            Assert.IsNotNull(Warning, "Warning", "Warning must be set in configuration!");
            Assert.ArgumentCondition(Warning.HasContent(), "Warning", "Warning should have some fields populated from configuration!");
            Assert.IsNotNull(Item, "Item", "Item must be set!");
        }

        private static string Splice(string value, int startIndex, int length, string replacement)
        {
            if(string.IsNullOrWhiteSpace(value))
            {
                return value;
            }

            return string.Concat(value.Substring(0, startIndex), replacement, value.Substring(startIndex + length));
        }

        private static string ReplaceInvalidCharactersToken(string value, string replacement)
        {
            return value.Replace("$invalidCharacters", replacement);
        }
    }
}

The above class will return an IWarning instance when an Item has invalid characters in its name — these invalid characters are defined in Sitecore configuration.

The Generate() method iterates over all invalid characters passed from Sitecore configuration and determines if they exist in the Item name. If they do, they are added to a HashSet<string> instance — I’m using a HashSet<string> to ensure the same character isn’t added more than once to the collection — which is used for constructing the warning message to be displayed to the content author/editor.

Once the Generate() method has iterated through all invalid characters, a string is built using the HashSet<string> instance, and is put in place wherever the $invalidCharacters token is defined in the Message property of the IWarning instance.

Now that we have our family of “algorithms” defined, we need a class to encapsulate and invoke these. I defined the following interface for classes that perform this role:

using System.Collections.Generic;

using Sitecore.Data.Items;

namespace Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.Strategy_Pattern
{
    public interface IWarningsGeneratorContext
    {
        IWarningsGenerator Generator { get; set; }

        IEnumerable<IWarning> GetWarnings(Item item);
    }
}

I then defined the following class which implements the interface above:

using System.Collections.Generic;

using Sitecore.Data.Items;
using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.Strategy_Pattern
{
    public class WarningsGeneratorContext : IWarningsGeneratorContext
    {
        public IWarningsGenerator Generator { get; set; }

        public IEnumerable<IWarning> GetWarnings(Item item)
        {
            Assert.IsNotNull(Generator, "Generator", "Generator must be set!");
            Assert.ArgumentNotNull(item, "item");
            Generator.Item = item;
            return Generator.Generate();
        }
    }
}

Instances of the class above take in an instance of IWarningsGenerator via its Generator property — in a sense, we are “lock and loading” WarningsGeneratorContext instances to get them ready. Instances then pass a supplied Item instance to the IWarningsGenerator instance, and invoke its GetWarnings() method. This method returns a collection of IWarning instances.

In a way, the IWarningsGeneratorContext instances are really adapters for IWarningsGenerator instances — IWarningsGeneratorContext instances provide a bridge for client code to use IWarningsGenerator instances via its own little API.

Now that we have all of the stuff above — yes, I know, there is a lot of code in this post, and we’ll reflect on this at the end of the post — we need a class whose instance will serve as a <getContentEditorWarnings> pipeline processor:

using System.Collections.Generic;
using System.Linq;

using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.Pipelines.GetContentEditorWarnings;

namespace Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.Strategy_Pattern
{
    public class ContentEditorWarnings
    {
        private List<IWarningsGenerator> WarningsGenerators { get; set; }

        private IWarningsGeneratorContext WarningsGeneratorContext { get; set; }

        public ContentEditorWarnings()
        {
            WarningsGenerators = new List<IWarningsGenerator>();
        }

        public void Process(GetContentEditorWarningsArgs args)
        {
            AssertProperties();
            Assert.ArgumentNotNull(args, "args");
            Assert.ArgumentNotNull(args.Item, "args.Item");
            
            IEnumerable<IWarning> warnings = GetWarnings(args.Item);
            if(warnings == null || !warnings.Any())
            {
                return;
            }

            foreach(IWarning warning in warnings)
            {
                AddWarning(args, warning);
            }
        }

        private IEnumerable<IWarning> GetWarnings(Item item)
        {
            List<IWarning> warnings = new List<IWarning>();
            foreach(IWarningsGenerator generator in WarningsGenerators)
            {
                IEnumerable<IWarning> generatorWarnings = GetWarnings(generator, item);
                if(generatorWarnings != null && generatorWarnings.Any())
                {
                    warnings.AddRange(generatorWarnings);
                }
            }

            return warnings;
        }

        private IEnumerable<IWarning> GetWarnings(IWarningsGenerator generator, Item item)
        {
            WarningsGeneratorContext.Generator = generator;
            return WarningsGeneratorContext.GetWarnings(item);
        }

        private void AddWarning(GetContentEditorWarningsArgs args, IWarning warning)
        {
            if(!warning.HasContent())
            {
                return;
            }

            GetContentEditorWarningsArgs.ContentEditorWarning editorWarning = args.Add();
            if(!string.IsNullOrWhiteSpace(warning.Title))
            {
                editorWarning.Title = TranslateText(warning.Title);
            }

            if(!string.IsNullOrWhiteSpace(warning.Message))
            {
                editorWarning.Text = TranslateText(warning.Message);
            }

            if (!warning.Links.Any())
            {
                return;
            }
            
            foreach(CommandLink link in warning.Links)
            {
                editorWarning.AddOption(TranslateText(link.Text), link.Command);
            }
        }

        private string TranslateText(string text)
        {
            if(string.IsNullOrWhiteSpace(text))
            {
                return text;
            }

            return Translate.Text(text);
        }

        private void AssertProperties()
        {
            Assert.IsNotNull(WarningsGeneratorContext, "WarningsGeneratorContext", "WarningsGeneratorContext must be set in configuration!");
            Assert.ArgumentCondition(WarningsGenerators != null && WarningsGenerators.Any(), "WarningsGenerators", "At least one WarningsGenerator must be set in configuration!");
        }
    }
}

The Process() method is the main entry into the pipeline processor. The method delegates to the GetWarnings() method to get a collection of IWarning instances from all IWarningGenerator instances that were injected into the class instance via the Sitecore Configuration Factory.

The GetWarnings() method iterates over all IWarningsGenerator instances, and passes each to the other GetWarnings() method overload which basically sets the IWarningGenerator on the IWarningsGeneratorContext instance, and invokes its GetWarnings() method with the supplied Item instance.

Once all IWarning instances have been collected, the Process() method iterates over the IWarning collection, and adds them to the GetContentEditorWarningsArgs instance via the AddWarning() method.

I then registered everything above in Sitecore using the following Sitecore patch configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <getContentEditorWarnings>
        <processor type="Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.Strategy_Pattern.ContentEditorWarnings, Sitecore.Sandbox">
          <WarningsGenerators hint="list">
            <WarningsGenerator type="Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.Strategy_Pattern.TooManyChildItemsWarningsGenerator, Sitecore.Sandbox">
              <MaxNumberOfChildItems>20</MaxNumberOfChildItems>
              <Warning type="Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.Warning, Sitecore.Sandbox">
                <Title>This Item has too many child items!</Title>
                <Message>Please consider converting this Item into an Item Bucket.</Message>
                <Links hint="list">
                  <Link type="Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.CommandLink">
                    <Text>Convert to Item Bucket</Text>
                    <Command>item:bucket</Command>
                  </Link>
                </Links>
              </Warning>
            </WarningsGenerator>
            <WarningsGenerator type="Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.Strategy_Pattern.HasInvalidCharacetersInNameWarningsGenerator, Sitecore.Sandbox">
              <CharacterSeparator>,&amp;nbsp;</CharacterSeparator>
              <Conjunction>&amp;nbsp;and&amp;nbsp;</Conjunction>
              <InvalidCharacters hint="list">
                <Character>-</Character>
                <Character>$</Character>
                <Character>1</Character>
              </InvalidCharacters>
              <Warning type="Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.Warning, Sitecore.Sandbox">
                <Title>The name of this Item has invalid characters!</Title>
                <Message>The name of this Item contains $invalidCharacters. Please consider renaming the Item.</Message>
                <Links hint="list">
                  <Link type="Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.CommandLink">
                    <Text>Rename Item</Text>
                    <Command>item:rename</Command>
                  </Link>
                </Links>
              </Warning>
            </WarningsGenerator>
          </WarningsGenerators>
          <WarningsGeneratorContext type="Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.Strategy_Pattern.WarningsGeneratorContext, Sitecore.Sandbox" />
        </processor>
      </getContentEditorWarnings>
    </pipelines>
  </sitecore>
</configuration>

Let’s test this out.

I set up an Item with more than 20 child Items, and gave it a name that includes -, $ and 1 — these are defined as invalid in the configuration file above:

strategy-1

As you can see, both warnings appear on the Item in the content editor.

Let’s convert the Item into an Item Bucket:

strategy-2

As you can see the Item is now an Item Bucket:

strategy-3

Let’s fix the Item’s name:

strategy-4

The Item’s name is now fixed, and there are no more content editor warnings:

strategy-5

You might be thinking “Mike, that is a lot of code — a significant amount over what you had shown in your previous post where you used the Template method pattern — so why bother with the Strategy pattern?”

Yes, there is more code here, and definitely more moving parts to the Strategy pattern over the Template method pattern.

So, what’s the benefit here?

Well, in the Template method pattern, subclasses are tightly coupled to their abstract base class. A change to the parent class could potentially break code in the subclasses, and this will require code in all subclasses to be changed. This could be quite a task if subclasses are defined in multiple projects that don’t reside in the same solution as the parent class.

The Strategy pattern forces loose coupling among all instances within the pattern thus reducing the likelihood that changes in one class will adversely affect others.

However, with that said, it does add complexity by introducing more code, so you should consider the pros and cons of using the Strategy pattern over the Template method pattern, or perhaps even decide if you should use a pattern to begin with.

Remember, the KISS principle should be followed wherever/whenever possible when designing and developing software.

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

Employ the Template Method Design Pattern for Content Editor Warnings in Sitecore

This post is a continuation of a series of posts I’m putting together around using design patterns in Sitecore solutions, and will show a “proof of concept” around using the Template method pattern — a pattern where classes have an abstract base class that defines most of an “algorithm” for how classes that inherit from it work but provides method stubs — these are abstract methods that must be implemented by subclasses to “fill in the blanks” of the “algorithm” — and method hooks — these are virtual methods that can be overridden if needed.

In this “proof of concept”, I am tapping into the <getContentEditorWarnings> pipeline in order to add custom content editor warnings for Items — if you are unfamiliar with content editor warnings in Sitecore, the following screenshot illustrates an “out of the box” content editor warning around publishing and workflow state:

content-editor-warning-example

To start, I defined the following interface for classes that will contain content for warnings that will be displayed in the content editor:

using System.Collections.Generic;

namespace Sitecore.Sandbox.Pipelines.GetContentEditorWarnings
{
    public interface IWarning
    {
        string Title { get; set; }

        string Message { get; set; }

        List<CommandLink> Links { get; set; }

        bool HasContent();

        IWarning Clone();
    }
}

Warnings will have a title, an error message for display, and a list of Sheer UI command links — the CommandLink class is defined further down this post — to be displayed and invoked when clicked.

You might be asking why I am defining this when I can just use what’s available in the Sitecore API? Well, I want to inject these values via the Sitecore Configuration Factory, and hopefully this will become clear once you have a look at the Sitecore configuration file further down in this post.

Next, I defined the following class that implements the interface above:

using System.Collections.Generic;

namespace Sitecore.Sandbox.Pipelines.GetContentEditorWarnings
{
    public class Warning : IWarning
    {
        public string Title { get; set; }

        public string Message { get; set; }

        public List<CommandLink> Links { get; set; }

        public Warning()
        {
            Links = new List<CommandLink>();
        }

        public bool HasContent()
        {
            return !string.IsNullOrWhiteSpace(Title)
                    || !string.IsNullOrWhiteSpace(Title)
                    || !string.IsNullOrWhiteSpace(Message);
        }

        public IWarning Clone()
        {
            IWarning clone = new Warning { Title = Title, Message = Message };
            foreach (CommandLink link in Links)
            {
                clone.Links.Add(new CommandLink { Text = link.Text, Command = link.Command });
            }

            return clone;
        }
    }
}

The HasContent() method just returns “true” if the instance has any content to display though this does not include CommandLinks — what’s the point in displaying these if there is no warning content to be displayed with them?

The Clone() method makes a new instance of the Warning class, and copies values into it — this is useful when defining tokens in strings that must be expanded before being displayed. If we expand them on the instance that is injected via the Sitecore Configuration Factory, the changed strings will persistent in memory until the application pool is recycled for the Sitecore instance.

The following class represents a Sheer UI command link to be displayed in the content editor warning so content editors/authors can take action on the warning:


namespace Sitecore.Sandbox.Pipelines.GetContentEditorWarnings
{
    public class CommandLink
    {
        public string Text { get; set; }

        public string Command { get; set; }
    }
}

I then built the following abstract class to serve as the base class for all classes whose instances will serve as a <getContentEditorWarnings> pipeline processor:

using System.Collections.Generic;
using System.Linq;

using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.Pipelines.GetContentEditorWarnings;

namespace Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.Template_Method_Pattern
{
    public abstract class ContentEditorWarnings
    {
        public void Process(GetContentEditorWarningsArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.ArgumentNotNull(args.Item, "args.Item");
            IEnumerable<IWarning> warnings = GetWarnings(args.Item);
            if(warnings == null || !warnings.Any())
            {
                return;
            }

            foreach(IWarning warning in warnings)
            {
                AddWarning(args, warning);
            }
        }

        protected abstract IEnumerable<IWarning> GetWarnings(Item item);

        private void AddWarning(GetContentEditorWarningsArgs args, IWarning warning)
        {
            if(!warning.HasContent())
            {
                return;
            }

            GetContentEditorWarningsArgs.ContentEditorWarning editorWarning = args.Add();
            if(!string.IsNullOrWhiteSpace(warning.Title))
            {
                editorWarning.Title = TranslateText(warning.Title);
            }

            if(!string.IsNullOrWhiteSpace(warning.Message))
            {
                editorWarning.Text = TranslateText(warning.Message);
            }

            if (!warning.Links.Any())
            {
                return;
            }
            
            foreach(CommandLink link in warning.Links)
            {
                editorWarning.AddOption(TranslateText(link.Text), link.Command);
            }
        }

        protected virtual string TranslateText(string text)
        {
            if(string.IsNullOrWhiteSpace(text))
            {
                return text;
            }

            return Translate.Text(text);
        }
    }
}

So what’s going on in this class? Well, the Process() method gets a collection of IWarnings from the GetWarnings() method — this method must be defined by subclasses of this class; iterates over them; and delegates to the AddWarning() method to add each to the GetContentEditorWarningsArgs instance.

The TranslateText() method calls the Text() method on the Sitecore.Globalization.Translate class — this lives in Sitecore.Kernel.dll — and is used when adding values on IWarning instances to the GetContentEditorWarningsArgs instance. This method is a hook, and can be overridden by subclasses if needed. I am not overriding this method on the subclasses further down in this post.

I then defined the following subclass of the class above to serve as a <getContentEditorWarnings> pipeline processor to warn content authors/editors if an Item has too many child Items:

using System.Collections.Generic;
using System.Linq;

using Sitecore.Data.Items;
using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.Template_Method_Pattern
{
    public class TooManyChildItemsWarnings : ContentEditorWarnings
    {
        private int MaxNumberOfChildItems { get; set; }

        private IWarning Warning { get; set; }

        protected override IEnumerable<IWarning> GetWarnings(Item item)
        {
            AssertProperties();
            if(item.Children.Count <= MaxNumberOfChildItems)
            {
                return new List<IWarning>();
            }

            return new[] { Warning };
        }

        private void AssertProperties()
        {
            Assert.ArgumentCondition(MaxNumberOfChildItems > 0, "MaxNumberOfChildItems", "MaxNumberOfChildItems must be set correctly in configuration!");
            Assert.IsNotNull(Warning, "Warning", "Warning must be set in configuration!");
            Assert.ArgumentCondition(Warning.HasContent(), "Warning", "Warning should have some fields populated from configuration!");
        }
    }
}

The class above is getting its IWarning instance and maximum number of child Items value from Sitecore configuration.

The GetWarnings() method ascertains whether the Item has too many child Items and returns the IWarning instance when it does in a collection — I defined this to be a collection to allow <getContentEditorWarnings> pipeline processors subclassing the abstract base class above to return more than one warning if needed.

I then defined another subclass of the abstract class above to serve as another <getContentEditorWarnings> pipeline processor:

using System.Collections.Generic;
using System.Linq;

using Sitecore.Data.Items;
using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.Template_Method_Pattern
{
    public class HasInvalidCharacetersInNameWarnings : ContentEditorWarnings
    {
        private string CharacterSeparator { get; set; }

        private string Conjunction { get; set; }

        private List<string> InvalidCharacters { get; set; }

        private IWarning Warning { get; set; }

        public HasInvalidCharacetersInNameWarnings()
        {
            InvalidCharacters = new List<string>();
        }

        protected override IEnumerable<IWarning> GetWarnings(Item item)
        {
            AssertProperties();
            HashSet<string> charactersFound = new HashSet<string>();
            foreach (string character in InvalidCharacters)
            {
                if(item.Name.Contains(character))
                {
                    charactersFound.Add(character.ToString());
                }
            }

            if(!charactersFound.Any())
            {
                return new List<IWarning>();
            }

            IWarning warning = Warning.Clone();
            string charactersFoundString = string.Join(CharacterSeparator, charactersFound);
            int lastSeparator = charactersFoundString.LastIndexOf(CharacterSeparator);
            if (lastSeparator < 0)
            {
                warning.Message = ReplaceInvalidCharactersToken(warning.Message, charactersFoundString);
                return new[] { warning };
            }

            warning.Message = ReplaceInvalidCharactersToken(warning.Message, Splice(charactersFoundString, lastSeparator, CharacterSeparator.Length, Conjunction));
            return new[] { warning };
        }

        private void AssertProperties()
        {
            Assert.IsNotNullOrEmpty(CharacterSeparator, "CharacterSeparator", "CharacterSeparator must be set in configuration!");
            Assert.ArgumentCondition(InvalidCharacters != null && InvalidCharacters.Any(), "InvalidCharacters", "InvalidCharacters must be set in configuration!");
            Assert.IsNotNull(Warning, "Warning", "Warning must be set in configuration!");
            Assert.ArgumentCondition(Warning.HasContent(), "Warning", "Warning should have some fields populated from configuration!");
        }

        private static string Splice(string value, int startIndex, int length, string replacement)
        {
            if(string.IsNullOrWhiteSpace(value))
            {
                return value;
            }

            return string.Concat(value.Substring(0, startIndex), replacement, value.Substring(startIndex + length));
        }

        private static string ReplaceInvalidCharactersToken(string value, string replacement)
        {
            return value.Replace("$invalidCharacters", replacement);
        }
    }
}

The above class will return an IWarning instance when an Item has invalid characters in its name — these invalid characters are defined in Sitecore configuration.

The GetWarnings() method iterates over all invalid characters passed from Sitecore configuration and determines if they exist in the Item name. If they do, they are added to a HashSet<string> instance — I’m using a HashSet<string> to ensure the same character isn’t added more than once to the collection — which is be used for constructing the warning message to be displayed to the content author/editor.

Once the GetWarnings() method has iterated through all invalid characters, a string is built using the HashSet<string> instance, and is put in place wherever the $invalidCharacters token is defined in the Message property of the IWarning instance.

I then registered everything above in Sitecore via the following patch configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <getContentEditorWarnings>
        <processor type="Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.Template_Method_Pattern.TooManyChildItemsWarnings, Sitecore.Sandbox">
          <MaxNumberOfChildItems>20</MaxNumberOfChildItems>
          <Warning type="Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.Warning, Sitecore.Sandbox">
            <Title>This Item has too many child items!</Title>
            <Message>Please consider converting this Item into an Item Bucket.</Message>
            <Links hint="list">
              <Link type="Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.CommandLink">
                <Text>Convert to Item Bucket</Text>
                <Command>item:bucket</Command>
              </Link>
            </Links>
          </Warning>
        </processor>
        <processor type="Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.Template_Method_Pattern.HasInvalidCharacetersInNameWarnings, Sitecore.Sandbox">
          <CharacterSeparator>,&amp;nbsp;</CharacterSeparator>
          <Conjunction>&amp;nbsp;and&amp;nbsp;</Conjunction>
          <InvalidCharacters hint="list">
            <Character>-</Character>
            <Character>$</Character>
            <Character>1</Character>
          </InvalidCharacters>
          <Warning type="Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.Warning, Sitecore.Sandbox">
            <Title>The name of this Item has invalid characters!</Title>
            <Message>The name of this Item contains $invalidCharacters. Please consider renaming the Item.</Message>
            <Links hint="list">
              <Link type="Sitecore.Sandbox.Pipelines.GetContentEditorWarnings.CommandLink">
                <Text>Rename Item</Text>
                <Command>item:rename</Command>
              </Link>
            </Links>
          </Warning>
        </processor>
      </getContentEditorWarnings>
    </pipelines>
  </sitecore>
</configuration>

As you can see, I am injecting warning data into my <getContentEditorWarnings> pipeline processors as well as other things which are used in code for each.

For the TooManyChildItemsWarnings <getContentEditorWarnings> pipeline processor, we are giving content authors/editors the ability to convert the Item into an Item bucket — we are injecting the item:bucket command via the configuration file above.

For the HasInvalidCharacetersInNameWarnings <getContentEditorWarnings> pipeline processor, we are passing in the Sheer UI command that will launch the Item Rename dialog to give content authors/editors the ability to rename the Item if it has invalid characters in its name.

Let’s see if this works.

I navigated to an Item in my content tree that has less than 20 child Items and has no invalid characters in its name:

no-content-editor-warnings

As you can see, there are no warnings.

Let’s go to another Item, one that not only has more than 20 child Items but also has invalid characters in its name:

both-warnings-appear

As you can see, both warnings are appearing for this Item.

Let’s now rename the Item:

rename-dialog

Great! Now the ‘invalid characters in name” warning is gone. Let’s convert this Item into an Item Bucket:

item-bucket-click-1

After clicking the ‘Convert to Item Bucket’ link, I saw the following dialog:

item-bucket-click-2

After clicking the ‘OK’ button, I saw the following progress dialog:

item-bucket-click-3

As you can see, the Item is now an Item Bucket, and both content editor warnings are gone:

item-bucket-click-4

If you have any thoughts on this, or have ideas on other places where you might want to employ the Template method pattern, please share in a comment.

Also, if you would like to see another example around adding a custom content editor warning in Sitecore, check out an older post of mine where I added one for expanding tokens in fields on an Item.

Until next time, keep on learning and keep on Sitecoring — Sitecoring is a legit verb, isn’t it? πŸ˜‰

Chain Together Sitecore Functionality Using the Chain-of-responsibility Design Pattern

This post is a continuation of a series of posts I’m putting together around using design patterns in Sitecore solutions, and will show a “proof of concept” around using the chain-of-responsibility pattern — a pattern where objects are linked together and are invoked in a cascading manner.

I decided to revisit a post I wrote over two years ago on chaining together client commands — these are invoked via the Sheer UI framework which drives how the ribbon, item context menu and other things work in Sitecore.

Earlier today — or yesterday depending on where you are — I began my code journey by building the following interface:

using Sitecore.Shell.Framework.Commands;

namespace Sitecore.Sandbox.Invokers.Commands
{
    public interface ICommandInvoker
    {
        bool HasCommand();

        void SetCommand(string commandName);

        void SetNextInvoker(ICommandInvoker nextInvoker);

        bool CanInvoke(CommandContext commandContext);

        void Invoke(CommandContext commandContext);
    }
}

The idea here is instances of classes that implement the interface above — let’s call them processing objects — will encapsulate instances of subclasses of Sitecore.Shell.Framework.Commands.Command — this is defined in Sitecore.Kernel.dll.

Each processing object will be linked to another processing object — I’m calling this other processing object the NextInvoker in code — which is invoked after the previous one.

Since the NextInvoker implements the interface above, it can also have its own NextInvoker thus chaining together a series of classes that implement the ICommandInvoker interface above.

I decided employ another design pattern in this solution — the Null Object pattern — and defined the following class whose instances will serve as a Null Object — I did this to reduce the amount of null checks in code (I should probably devote an entire post on this pattern):

using Sitecore.Diagnostics;
using Sitecore.Shell.Framework.Commands;

namespace Sitecore.Sandbox.Invokers.Commands
{
    public class NullCommandInvoker : ICommandInvoker
    {
        public NullCommandInvoker()
        {
        }

        public bool HasCommand()
        {
            return false;
        }

        public void SetCommand(string commandName)
        {
        }

        public void SetNextInvoker(ICommandInvoker nextInvoker)
        {
        }

        public bool CanInvoke(CommandContext commandContext)
        {
            return true;
        }

        public void Invoke(CommandContext commandContext)
        {
        }
    }
}

Basically, instances of the class above do nothing and can be invoked — why not? They don’t do actually do anything so no harm done, right? πŸ˜‰ — as can be seen in the CanInvoke() method.

I then created the following class whose instances will serve as the default processing objects:

using Sitecore.Configuration;
using Sitecore.Diagnostics;
using Sitecore.Shell.Framework.Commands;

namespace Sitecore.Sandbox.Invokers.Commands
{
    public class CommandInvoker : ICommandInvoker
    {
        private static ICommandInvoker NullCommandInvoker { get; set; }

        private Command Command { get; set; }

        private ICommandInvoker nextInvoker;
        private ICommandInvoker NextInvoker 
        { 
            get
            {
                return nextInvoker ?? NullCommandInvoker;
            }
            set
            {
                nextInvoker = value;
            }
        }

        static CommandInvoker()
        {
            NullCommandInvoker = CreateNullCommandInvoker();
        }

        public CommandInvoker()
        {
        }

        public bool HasCommand()
        {
            return Command != null;
        }

        public void SetCommand(string commandName)
        {
            Assert.ArgumentNotNullOrEmpty(commandName, "commandName");
            Command = GetCommand(commandName);
            Assert.IsNotNull(Command, "commandName", "commandName is not a valid command!");
        }

        public void SetNextInvoker(ICommandInvoker nextInvoker)
        {
            Assert.ArgumentNotNull(nextInvoker, "nextInvoker");
            NextInvoker = nextInvoker;
        }

        public bool CanInvoke(CommandContext commandContext)
        {
            Assert.ArgumentNotNull(commandContext, "commandContext");
            if(!HasCommand())
            {
                return false;
            }
            
            return Command.QueryState(commandContext) == CommandState.Enabled && NextInvoker.CanInvoke(commandContext);
        }

        public void Invoke(CommandContext commandContext)
        {
            Assert.ArgumentNotNull(commandContext, "commandContext");
            if(!HasCommand())
            {
                return;
            }

            Command.Execute(commandContext);
            NextInvoker.Invoke(commandContext);
        }

        protected virtual Command GetCommand(string commandName)
        {
            return CommandManager.GetCommand(commandName);
        }

        private static ICommandInvoker CreateNullCommandInvoker()
        {
            ICommandInvoker nullCommandInvoker = Factory.CreateObject("commandInvokers/nullCommandInvoker", true) as ICommandInvoker;
            Assert.IsNotNull(nullCommandInvoker, "nullCommandInvoker", "nullCommandInvoker must be set correctly in configuration!");
            return nullCommandInvoker;
        }
    }
}

There is a lot going on in the class above, so let me try to briefly capture the main things.

The SetCommand() method in the class above takes in the name of the command and delegates to the GetCommand() method which looks it up using the GetCommand() method on Sitecore.Shell.Framework.Commands.CommandManager in Sitecore.Kernel.dll.

The SetNextInvoker() method will chain the current CommandInvoker instance with another class instance that implements the ICommandInvoker interface — this is the NextInvoker.

The CanInvoke() method basically checks to see if the current CommandInvoker instance has a non-null Sitecore.Shell.Framework.Commands.Command instance set within it; ascertains whether the Sitecore.Shell.Framework.Commands.Command instance is enabled; and determines if the NextInvoker can be invoked.

The Invoke() method calls the Execute() method on the Sitecore.Shell.Framework.Commands.Command instance, and then calls the Invoke() method on the class instance’s NextInvoker.

One thing I’d like to point out is an instance of the Null Object class that was defined above is used when the NextInvoker is not set — this is why a null check is not done in the CanInvoke() and Invoke() methods on the NextInvoker instance.

I then built the following Sitecore.Shell.Framework.Commands.Command subclass which is to be wired-up to a menu option of some type in the Core database:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;

using Sitecore.Collections;
using Sitecore.Configuration;
using Sitecore.Diagnostics;
using Sitecore.Shell.Framework.Commands;
using Sitecore.Web;

using Sitecore.Sandbox.Invokers.Commands;

namespace Sitecore.Sandbox.Shell.Framework.Commands
{
    public class ChainOfResponsibilityCommand : Command
    {
        private ICommandInvoker commandInvoker;
        private ICommandInvoker CommandInvoker 
        { 
            get
            {
                if(commandInvoker == null)
                {
                    commandInvoker = GetCommandInvoker();
                }

                return commandInvoker;
            }
        }

        public override void Execute(CommandContext context)
        {
            if (!CommandInvoker.CanInvoke(context))
            {
                return;
            }

            CommandInvoker.Invoke(context);
        }

        public override CommandState QueryState(CommandContext context)
        {
            if(!CommandInvoker.CanInvoke(context))
            {
                return CommandState.Hidden;
            }

            return CommandState.Enabled;
        }

        private ICommandInvoker GetCommandInvoker()
        {
            ICommandInvoker firstInvoker = CreateNewCommandInvoker();
            IEnumerable<string> commandNames = GetCommandNames(GetParameters());
            if(commandNames == null || !commandNames.Any())
            {
                return firstInvoker;
            }
            
            ICommandInvoker invoker = firstInvoker;
            invoker.SetCommand(commandNames.First());
            commandNames = commandNames.Skip(1).ToList();
            if (!commandNames.Any())
            {
                return firstInvoker;
            }

            foreach(string commandName in commandNames)
            {
                ICommandInvoker nextInvoker = CreateNewCommandInvoker();
                nextInvoker.SetCommand(commandName);
                invoker.SetNextInvoker(nextInvoker);
                invoker = nextInvoker;
            }

            return firstInvoker;
        }

        private IEnumerable<string> GetCommandNames(SafeDictionary<string> parameters)
        {
            string commands = GetCommandsString(parameters);
            char[] delimiters = GetCommandsDelimiters(parameters);

            if (string.IsNullOrWhiteSpace(commands) || delimiters == null || !delimiters.Any())
            {
                return new List<string>();
            }

            return commands.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
        }

        protected virtual string GetCommandsString(SafeDictionary<string> parameters)
        {
            return parameters["commands"];
        }

        private char[] GetCommandsDelimiters(SafeDictionary<string> parameters)
        {
            string delimiters = GetDelimitersString(parameters);
            if (string.IsNullOrWhiteSpace(delimiters))
            {
                return new char[] { };
            }

            return GetDelimitersString(parameters).ToCharArray();
        }

        protected virtual string GetDelimitersString(SafeDictionary<string> parameters)
        {
            return parameters["delimiters"];
        }

        protected virtual SafeDictionary<string> GetParameters()
        {
            XmlNode xmlNode = Factory.GetConfigNode(string.Format("commands/command[@name='{0}']", Name));
            string parametersValue = GetAttributeValue(xmlNode, "parameters");
            if (string.IsNullOrWhiteSpace(parametersValue))
            {
                return new SafeDictionary<string>();
            }
           
            return WebUtil.ParseQueryString(xmlNode.Attributes["parameters"].Value);
        }

        private string GetAttributeValue(XmlNode xmlNode, string attributeName)
        {
            if (xmlNode == null || xmlNode.Attributes[attributeName] == null || string.IsNullOrWhiteSpace(xmlNode.Attributes[attributeName].Value))
            {
                return string.Empty;
            }

            return xmlNode.Attributes[attributeName].Value;
        }

        protected static ICommandInvoker CreateNewCommandInvoker()
        {
            ICommandInvoker commandInvoker = Factory.CreateObject("commandInvokers/defaultCommandInvoker", true) as ICommandInvoker;
            Assert.IsNotNull(commandInvoker, "commandInvoker", "commandInvoker must be set correctly in configuration!");
            return commandInvoker;
        }
    }
}

The Command above reads the list of commands to chain together from Sitecore configuration — this is coming from an attribute on the Command’s configuration element which you will see in the configuration file below — and builds up a linked list of ICommandInvoker instances via the GetCommandInvoker() method.

The QueryState() method simply checks to see if the linked list of ICommandInvoker instances can be invoked, and the Execute() — which also performs the same check as is done in the QueryState() method — ultimately calls the Invoke() method on the first ICommandInvoker instance — this will cascade throughout the entire linked list.

I then wired everything up in the following Sitecore configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <commands>
      <command name="item:MoveRenamePublish" parameters="commands=item:moveto|item:rename|item:publish&amp;delimiters=|"  type="Sitecore.Sandbox.Shell.Framework.Commands.ChainOfResponsibilityCommand, Sitecore.Sandbox"/>
      <command name="item:MoveLastPublish" parameters="commands=item:movelast|item:publish&amp;delimiters=|"  type="Sitecore.Sandbox.Shell.Framework.Commands.ChainOfResponsibilityCommand, Sitecore.Sandbox"/>
    </commands>
    <commandInvokers>
      <defaultCommandInvoker type="Sitecore.Sandbox.Invokers.Commands.CommandInvoker, Sitecore.Sandbox" singleInstance="false" />
      <nullCommandInvoker type="Sitecore.Sandbox.Invokers.Commands.NullCommandInvoker, Sitecore.Sandbox" singleInstance="false" />
    </commandInvokers>
  </sitecore>
</configuration>

I defined two different commands in the above configuration file to test whether the ChainOfResponsibilityCommand class can be reused for multiple Sheer UI commands.

I then set up two different buttons in the ribbon for the two command elements in the configuration file above:

item:MoveRenamePublish:

move-rename-publish-ribbon-core

item:MoveLastPublish:

move-last-publish-ribbon-core

Let’s take this for a spin.

Let’s move, rename and publish the following Sitecore Item:

cat-page-one-move-rename-publish-1

I clicked the button, and was presented with this dialog:

cat-page-one-move-rename-publish-2

I was then prompted with this dialog after the Item was moved to the selected destination:

cat-page-one-move-rename-publish-3

I renamed the Item, and was prompted with the publishing dialog:

cat-page-one-move-rename-publish-4

Ok, that appears to be working. Let’s try out the other Ribbon button. Let’s try it on this Item:

sub-page-one-move-last-publish-1

After the Item was moved, I was prompted with the publishing dialog:

sub-page-one-move-last-publish-2

As you can see, this worked as well.

I will say that although I had fun implementing this solution, it is way more complex than the solution I had built over two years ago in my older post.

This brings up an important point I want to make regarding design patterns: don’t use a design pattern because it may seem like a cool thing to do, or because your crazy developer cousin who always carries around a copy of the Gang Of Four book on design patterns says all solutions should implement them always.

Use them wisely.

if you see an opportunity to use one where it will save time when introducing new features moving forward, or it makes it easy to swap-in/out features, then by all means go for it. Otherwise, the KISS principle is a better “rule of thumb” to follow.

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

Augment Functionality in Sitecore Using the Decorator Design Pattern

Over the past few days, I’ve been trying to come up with a good idea for a blog post showing the usage of the Decorator design pattern in Sitecore.

During this time of cogitation, I was having difficulties coming up with a good example despite having had used this pattern in Sitecore on many past projects — I can’t really share those solutions since they are owned by either previous employers or clients.

However, I finally had an “EUREKA!” moment after John West — CTO of Sitecore USA — wrote a blog post earlier today where he shared an <httpRequestBegin> pipeline processor which redirects to a canonical URL for an Item.

So, what exactly did I come up with?

I built the following example which simply “decorates” the “out of the box” ItemResolver — Sitecore.Pipelines.HttpRequest.ItemResolver in Sitecore.Kernel.dll — which is used as an <httpRequestBegin> pipeline processor to figure out what the context Item should be from the URL being requested by looking for an entry in the IDTable in Sitecore (note: this idea is adapted from a blog post that Alex Shyba — Director of Platform Innovation and Engineering at Sitecore — wrote a few years ago):

using Sitecore;
using Sitecore.Data;
using Sitecore.Data.IDTables;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Pipelines.HttpRequest;

namespace Sitecore.Sandbox.Pipelines.HttpRequest
{
    public class IDTableItemResolver : HttpRequestProcessor
    {
        private string Prefix { get; set; }

        private HttpRequestProcessor InnerProcessor { get; set; }

        public override void Process(HttpRequestArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            AssertProperties();
            
            Item item = GetItem(args.Url.FilePath);
            if (item == null)
            {
                InnerProcessor.Process(args);       
                return;
            }

            Context.Item = item; 
        }

        protected virtual void AssertProperties()
        {
            Assert.IsNotNullOrEmpty(Prefix, "Prefix", "Prefix must be set in configuration!");
            Assert.IsNotNull(InnerProcessor, "InnerProcessor", "InnerProcessor must be set in configuration!");
        }

        protected virtual Item GetItem(string url)
        {
            IDTableEntry entry = IDTable.GetID(Prefix, url);
            if (entry == null || entry.ID.IsNull)
            {
                return null;
            }

            return GetItem(entry.ID);
        }

        protected Item GetItem(ID id)
        {
            Database database = GetDatabase();
            if (database == null)
            {
                return null;
            }

            return database.GetItem(id);
        }

        protected virtual Database GetDatabase()
        {
            return Context.Database;
        }
    }
}

What is the above class doing? It’s basically seeing if it can find an Item for the passed relative URL — this is passed via the FilePath property of the Url property of the HttpRequestArgs instance taken in by the Process() method — by delegating to a method that looks up an entry in the IDTable for the URL — the URL would be the key into the IDTable — and return the Item from the context database if an entry is found. If no entry is found, it just returns null.

If null is returned, that pretty much means there is no entry in the IDTable for the given relative URL so a delegation to the Process() method of the InnerProcessor is needed in order to preserve “out of the box” Sitecore functionality for Item URL resolution.

I then replaced the “out of the box” ItemResolver with the above in the following patch include configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <httpRequestBegin>
        <processor patch:instead="*[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']" 
                   type="Sitecore.Sandbox.Pipelines.HttpRequest.IDTableItemResolver, Sitecore.Sandbox">
          <Prefix>UrlRewrite</Prefix>
          <InnerProcessor type="Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel" />
        </processor>  
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

In the above configuration file, we are setting the “out of the box” ItemResolver to be injected into the class above so that its Process() method can be “decorated”.

Let’s see this in action!

Let’s try this out with the following page Item:

mario-content-editor

In order to see the above <httpRequestBegin> pipeline processor in action, I had to add an entry into my IDTable — let’s make pretend an UrlRewrite module on the Sitecore Marketplace added this entry for us:

url-rewrite-id-table

I loaded up another browser window; navigated to the relative URL specified in the IDTable entry; and then saw the following:

resolved-url

As you can see, it worked.

We can also navigate to the same page using its true URL — the one resolved by Sitecore “out of the box”:

mario-resolved

The above worked because the inner processor resolved it.

Let’s now go to a completely different page Item altogether. Let’s use this one:

cat-page-five-sitecore

As you can see, that also worked:

cat-page-five

If you have any thoughts on this, or have other ideas around using the Decorator pattern in Sitecore, please share in a comment.

Expand Tokens on Items Using a Sitecore PowerShell Extensions Toolbox Script

Last Wednesday I had the opportunity of presenting Sitecore PowerShell Extensions (SPE) at the Milwaukee Sitecore Meetup. During this presentation, I demonstrated how quickly and easily one can add, execute and reuse PowerShell scripts in SPE, and I did this using version 3.0 of SPE on Sitecore XP 8.

During one segment of the presentation, I shared how one can seamlessly add scripts to the SPE Toolbox — a repository of utility scripts if you will — and used the following script when showing this:

<#
    .NAME
        Expand tokens in all content items
 
    .SYNOPSIS
        Expand tokens in all fields in all content items
    .NOTES
        Mike Reynolds
#>
 
$items = Get-ChildItem -Path "master:\sitecore\content" -Recurse
$items | ForEach-Object { $_.Fields.ReadAll() }
$items |  Expand-Token
Close-Window

The script above grabs all descendant Items under /sitecore/content/; iterates over them to ensure all field values are available — the ReadAll() method on the FieldCollection instance will ensure values from fields on the Item’s template’s Standard Values Item are pulled in for processing; and sends in these Items into the Expand-Token commandlet which comes “out of the box” with SPE.

The script also closes the processing dialog.

I then saved the above script into my Toolbox library in my SPE module:

toolbox-script-ise-save

Let’s try this out. Let’s find some Items with tokens in some fields. It looks like the Home Item has some:

home-tokens

Here’s another Item that also has tokens:

descendant-tokens

Let’s go to the SPE Toolbox, and click on our Toolbox utility:

toolbox-expand-tokens-click

As you can see the tokens were expanded on the Home Item:

home-tokens-expanded

Tokens were also expanded on the descendant Item:

descendant-tokens-expanded

If you have any thoughts and/or suggestions on this, or have ideas for other SPE Toolbox scripts, please drop a comment.

If you would like to watch the Milwaukee Sitecore Meetup presentation where I showed the above — you’ll also get to see some epic Sitecore PowerShell Extensions stuff from Adam Brauer, Senior Product Engineer at Active Commerce, in this presentation as well — have a look below:

If you would like to see another example of adding a script to the SPE Toolbox, please see my previous post on this subject.

Until next time, have a scriptaculous day!

Add to the Sitecore Gutter Using Sitecore PowerShell Extensions

Last Wednesday I had the pleasure of presenting Sitecore PowerShell Extensions (SPE) at the Milwaukee Sitecore Meetup. The goal of my presentation was to share how easy it is to add, execute and reuse PowerShell scripts in SPE, and I did this on version 3.0 of SPE in a Sitecore XP 8 instance.

In my presentation, I showed how quickly one can add a custom Sitecore gutter icon with SPE, and used the following script to demonstrate that:

<#
    .NAME 
        Item Has 20 Or More Sub-items Gutter

    .SYNOPSIS
        Renders gutters indicated whether the item has more than 20 sub-items.
     
    .NOTES
        Mike Reynolds
#>

$item = Get-Item .
$gutter = New-Object Sitecore.Shell.Applications.ContentEditor.Gutters.GutterIconDescriptor
if($item.Children.Count -gt 20) {
    $gutter.Icon = "Applications/16x16/delete.png"
    $gutter.Tooltip = "This Item has more than 20 sub-items!"
    
} else {
    $gutter.Icon = "Applications/16x16/check2.png"
    $gutter.Tooltip = "This Item has 20 or less sub-items."
}

$gutter

The script above creates a new Sitecore.Shell.Applications.ContentEditor.Gutters.GutterIconDescriptor instance — this class is defined in Sitecore.Kernel.dll — and sets a certain icon and tooltip on it if the context Item has more than 20 sub-items. If the Item has 20 or less sub-items, a different icon and tooltip are used.

The script then outputs the GutterIconDescriptor instance.

I then saved the above script to the Gutter integration point in my SPE module:

gutter-script-ise

Now that it’s saved, we have to sync it to the Sitecore Gutter:

sync-gutter

We should be good to go! Let’s test this out!

I went to my content tree; right-clicked in the gutter area; and was presented with the gutter menu:

gutter-menu

After clicking my new gutter menu option, I was presented with gutter icons next to Items in my tree:

cat-page-one-more-20-sub-items

As you can see, the Item with the X icon has more than 20 sub-items:

more-than-20-sub-items-expanded

For comparison, the following Item has a green check-mark icon next to it which indicates it has 20 or less sub-items (this Item actually has 10 sub-items):

less-than-20-sub-items

If you have any thoughts and/or suggestions on this, or have ideas for other gutter icon scripts that can be incorporated into SPE, please share in a comment.

If you would like to watch the Milwaukee Sitecore Meetup presentation where I showed the above — you’ll also get to see some cool Sitecore PowerShell Extensions stuff from Adam Brauer, Senior Product Engineer at Active Commerce, in this presentation as well — have a look below:

Until next time, keep on learning and sharing!

Bucket Items in Sitecore using a Custom Commandlet in Sitecore PowerShell Extensions

Last Wednesday I had the privilege to present Sitecore PowerShell Extensions (SPE) at the Milwaukee Sitecore Meetup. During my presentation, I demonstrated how easy it is to add, execute and reuse PowerShell scripts in SPE, and I showcased version 3.0 of SPE on Sitecore XP 8.

Unfortunately, I ran out of time before showing how one can go about creating a custom commandlet in SPE, and hope to make it up to everyone by sharing the commandlet I wrote for the presentation in this post.

I wrote the following commandlet to convert an Item into an Item Bucket in Sitecore:

using System;
using System.Management.Automation;

using Sitecore.Data.Items;
using Sitecore.Shell.Framework.Commands;

using Cognifide.PowerShell.Commandlets;
using Cognifide.PowerShell.Commandlets.Interactive.Messages;

namespace Sitecore.Sandbox.SPE.Commandlets.Buckets
{
    [Cmdlet(VerbsData.ConvertTo, "Bucket"), OutputType(new Type[] { typeof(Item) })]
    public class ConvertToBucketCommand : BaseItemCommand
    {
        protected override void ProcessItem(Item item)
        {
            try
            {
                PutMessage(new ShellCommandInItemContextMessage(item, "item:bucket"));   
            }
            catch (Exception exception)
            {
                WriteError(new ErrorRecord(exception, "sitecore_new_bucket_error", ErrorCategory.NotSpecified, Item));
            }

            WriteItem(Item);
        }
    }
}

The above commandlet implements the ProcessItem() method — this method is declared abstract in one of the ancestor classes of the class above — and leverages the framework of SPE to invoke a Sheer UI command to bucket the Item passed to the method — one of the ancestor classes of this class passes the Item to be processed.

The above highlights how in SPE we are employing the Template method pattern for many “out of the box” commandlets. This involves inheriting from an abstract base class — Cognifide.PowerShell.Commandlets.BaseItemCommand in Cognifide.PowerShell.dll (this assembly comes with the SPE module) is an example of one of these base classes — and implementing methods that are defined as abstract. The parent or an ancestor class will do the brunt of the work behind the scenes, and use your method implementation for specifics.

As a side note, we also provide method hooks as well — these are virtual methods defined on a base or ancestor class — which you can override to change how they work to meet your particular needs.

I then wired the above up using a Sitecore include configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <powershell>
      <commandlets>
        <add Name="Custom Bucket Commandlets" type="*, Sitecore.Sandbox.SPE" />
      </commandlets>
    </powershell>
  </sitecore>
</configuration>

I deployed the above to my Sitecore instance; loaded up the Integrated Scripting Environment (ISE) in SPE; and saw that my commandlet was registered using the Control-Space shortcut key:

convert-to-bucket-ise-control-space

Let’s take this for a spin. Let’s convert the Home Item into an Item Bucket:

home-before-bucket

Here’s my script to do that:

ise-convert-home-bucket

I clicked the execute button, and then got this confirmation dialog:

ise-convert-home-bucket-confirm

I then clicked the “Ok” button and was immediately presented with this dialog:

ise-convert-home-bucket-processing

As you can see it worked! The Home Item in my content tree is now an Item Bucket:

home-after-bucket

If you have any thoughts on this or ideas for other custom commandlets for SPE, please share in a comment.

If you would like to watch the Milwaukee Sitecore Meetup presentation where I showcased Sitecore PowerShell Extensions — and as a bonus you’ll also get to see some real-life application of SPE from Adam Brauer, Senior Product Engineer at Active Commerce, in this presentation as well — it has been recorded for posterity, and you can watch it here:

Until next time, stay curious, keep experimenting, and let’s keep on sharing all the Sitecore things!

Bucket and Unbucket Items via Custom Item Context Menu Options Using Sitecore PowerShell Extensions

Last Wednesday I was honored to present Sitecore PowerShell Extensions (SPE) at the Milwaukee Sitecore Meetup. My presentation was all about how easy it is to add, execute and reuse PowerShell scripts in SPE, and I showcased this using version 3.0 of SPE on Sitecore XP 8.

During the presentation, I demonstrated how one can go about adding custom Item Context Menu options using SPE, and did so with the following scripts which bucket and unbucket Sitecore Items:

 <#
    .NAME 
        Convert To Item Bucket

    .SYNOPSIS
        Converts the context item to an Item Bucket
     
    .NOTES
        Mike Reynolds
#>
 
$item = Get-Item .

if($item."__Is Bucket" -eq "1") {
   return 
}

Get-ChildItem . -Recurse | %{ $_.__Bucketable = "1" }
Invoke-ShellCommand -Name "item:bucket" -Item $item
Close-Window

The above PowerShell script basically checks to see if an Item is an Item Bucket and does nothing if it is. If the Item is not an Item Bucket, we make sure all sub-items are bucketable — we just tick the “__Bucketable” Checkbox field on them — and invoke the Sheer UI command for converting an Item into an Item Bucket — this is done via the Invoke-ShellCommand commandlet that ships with SPE — and then close the script execution dialog users are presented with when SPE executes a script in the Item Context Menu.

The following script does the exact opposite of the script above — it converts an Item Bucket back to a regular Sitecore Item using the Sheer UI command for unbucketing:

<#
    .NAME 
        Convert Item Bucket To A Regular Item

    .SYNOPSIS
        Converts an Item Bucket to a regular Item
     
    .NOTES
        Mike Reynolds
#>

$item = Get-Item .

if($item."__Is Bucket" -ne "1") {
   return 
}

Invoke-ShellCommand -Name "item:unbucket" -Item $item
Close-Window
 

I then saved the above scripts to a Context Menu integration point in a SPE module I created during the presentation using the SPE Integrated Scripting Environment (ISE) (to get to the ISE in SPE, go to Sitecore ==> Development Tools ==> PowerShell ISE in the Sitecore Start menu of the Sitecore Desktop):

Saved-context-menu-script

I’ve omitted screenshots on saving the “Convert Item Bucket To A Regular Item” script for brevity.

I then set rules in the “Show if rules are met or not defined” field on both Context Menu script Items — we only want the “Convert To Item Bucket” Context Menu option to show when the Item isn’t an Item Bucket, and the “Convert Item Bucket To A Regular Item” Context Menu option to show when the Item it is an Item Bucket:

The “Convert To Item Bucket” Context Menu item (this Item was saved to /sitecore/system/Modules/PowerShell/Script Library/SitecoreUG Module/Content Editor/Context Menu/Convert To Item Bucket in my Sitecore instance):

set-rules-convert-to-bucket

The “Convert Item Bucket To A Regular Item” Context Menu item:

set-rules-convert-to-unbucket

After saving the above, I navigated to an Item in my content tree that has sub-items, right-clicked on it, and clicked on the “Convert To Item Bucket” Context Menu option:

convert-to-bucket-right-click

I was then presented with a confirmation dialog:

convert-to-bucket-confirm

As you can see the Item is now an Item Bucket:

item-is-a-bucket

I right-clicked on the Item again, and clicked on the “Convert Item Bucket To A Regular Item” Context Menu option:

convert-to-unbucket-right-click

I was presented with another confirmation dialog:

convert-to-ubucket-confirm

As you can see the Item is no longer an Item Bucket:

no-longer-a-bucket

If you have any thoughts on this or ideas for other Context Menu PowerShell scripts for SPE, please drop a comment.

If you would like to watch the Milwaukee Sitecore Meetup presentation where I showed the above — you’ll also get to see some cool Sitecore PowerShell Extensions stuff from Adam Brauer, Senior Product Engineer at Active Commerce, in this presentation as well — have a look below:

Until next time, have a Sitecoretastic day!