Home » Search results for 'Sitecore Configuration Factory' (Page 4)

Search Results for: Sitecore Configuration Factory

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 Configuration-defined Sitecore Functionality using the Composite Design Pattern

In a couple of my past posts — Synchronize IDTable Entries Across Multiple Sitecore Databases Using a Composite IDTableProvider and
Chain Together Sitecore Client Commands using a Composite Command — I used the Composite design pattern to chain together functionality in two or more classes with the same interface — by interface here, I don’t strictly mean a C# interface but any class that servers as a base class for others where all instances share the same methods — and had a thought: how could I go about making a generic solution to chain together any class type defined in Sitecore configuration?

As a “proof of concept” I came up with the following solution while taking a break from my judgely duties reviewing 2015 Sitecore Hackathon modules.

I first defined an interface for classes that will construct objects using the Sitecore Configuration Factory:

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

namespace Sitecore.Sandbox.Shared
{
    public interface IConfigurationFactoryInstances<TInstance>
    {
        void AddInstance(XmlNode source);

        IEnumerable<TInstance> GetInstances();
    }
}

The following class implements the methods defined in the interface above:

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

using Sitecore.Configuration;

namespace Sitecore.Sandbox.Shared
{
    public class ConfigurationFactoryInstances<TInstance> : IConfigurationFactoryInstances<TInstance> where TInstance : class
    {
        private IList<TInstance> Instances { get; set; }

        public ConfigurationFactoryInstances()
        {
            Instances = new List<TInstance>();
        }

        public virtual IEnumerable<TInstance> GetInstances()
        {
            return Instances;
        }

        public void AddInstance(XmlNode configNode)
        {
            TInstance instance = CreateInstance(configNode);
            if (instance == null)
            {
                return;
            }

            Instances.Add(instance);
        }

        protected virtual TInstance CreateInstance(XmlNode configNode)
        {
            if (configNode == null)
            {
                return null;
            }

            TInstance instance = Factory.CreateObject(configNode, true) as TInstance;
            if (instance == null)
            {
                return null;
            }

            return instance;
        }
    }
}

The AddInstance() method in the class above — along with the help of the CreateInstance() method — takes in a System.Xml.XmlNode instance and attempts to create an instance of the type denoted by TInstance using the Sitecore Configuration Factory. If the instance was successfully created (i.e. it’s not null), it is added to a list.

The GetInstances() method in the class above just returns the list of the instances that were added by the AddInstance() method.

Since I’ve been posting a lot of meme images on Twitter lately — you can see the evidence here — I’ve decided to have a little fun tonight with this “proof of concept”, and created the following composite MediaProvider:

using System.Xml;

using Sitecore.Data.Items;
using Sitecore.Resources.Media;

using Sitecore.Sandbox.Shared;

namespace Sitecore.Sandbox.Resources.Media
{
    public class CompositeMediaProvider : MediaProvider
    {
        private static IConfigurationFactoryInstances<MediaProvider> Instances { get; set; }

        static CompositeMediaProvider()
        {
            Instances = new ConfigurationFactoryInstances<MediaProvider>();
        }

        public override string GetMediaUrl(MediaItem item, MediaUrlOptions options)
        {
            foreach (MediaProvider mediaProvider in Instances.GetInstances())
            {
                string url = mediaProvider.GetMediaUrl(item, options);
                if(!string.IsNullOrWhiteSpace(url))
                {
                    return url;
                }
            }

            return base.GetMediaUrl(item, options);
        }

        protected virtual void AddMediaProvider(XmlNode configNode)
        {
            Instances.AddInstance(configNode);
        }
    }
}

The AddMediaProvider() method in the class above adds new instances of Sitecore.Resources.Media.MediaProvider through delegation to an instance of the ConfigurationFactoryInstances class. The Sitecore Configuration Factory will call the AddMediaProvider() method since it’s defined in the patch include configuration file shown later in this post

The GetMediaUrl() method iterates over all instances of Sitecore.Resources.Media.MediaProvider that were created and stored by the ConfigurationFactoryInstances instance, and calls each of their GetMediaUrl() methods. The first non-null or empty URL from one of these “inner” instances is returned to the caller. If none of the instances return a URL, then the class above returns the value given by its base class’ GetMediaUrl() method.

I then spun up three MediaProvider classes to serve up specific image URLs of John West — I found these somewhere on the internet 😉 — when they encounter media Items with specific names (I am not advocating that anyone hard-codes anything like this — these classes are only here to serve as examples):

using System;

using Sitecore.Data.Items;
using Sitecore.Resources.Media;

namespace Sitecore.Sandbox.Resources.Media
{
    public class JohnWestOneMediaProvider : MediaProvider
    {
        public override string GetMediaUrl(MediaItem item, MediaUrlOptions options)
        {
            if(item.Name.Equals("john-west-1", StringComparison.CurrentCultureIgnoreCase))
            {
                return "http://cdn.meme.am/instances/500x/43030540.jpg";
            }

            return string.Empty;
        }
    }
}

using System;

using Sitecore.Data.Items;
using Sitecore.Resources.Media;

namespace Sitecore.Sandbox.Resources.Media
{
    public class JohnWestTwoMediaProvider : MediaProvider
    {
        public override string GetMediaUrl(MediaItem item, MediaUrlOptions options)
        {
            if (item.Name.Equals("john-west-2", StringComparison.CurrentCultureIgnoreCase))
            {
                return "http://cdn.meme.am/instances/500x/43044627.jpg";
            }

            return string.Empty;
        }
    }
}
using System;

using Sitecore.Data.Items;
using Sitecore.Resources.Media;

namespace Sitecore.Sandbox.Resources.Media
{
    public class JohnWestThreeMediaProvider : MediaProvider
    {
        public override string GetMediaUrl(MediaItem item, MediaUrlOptions options)
        {
            if (item.Name.Equals("john-west-3", StringComparison.CurrentCultureIgnoreCase))
            {
                return "http://cdn.meme.am/instances/500x/43030625.jpg";
            }

            return string.Empty;
        }
    }
}

I then registered all of the above in Sitecore using a patch configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <mediaLibrary>
      <mediaProvider>
        <patch:attribute name="type">Sitecore.Sandbox.Resources.Media.CompositeMediaProvider, Sitecore.Sandbox</patch:attribute>
        <mediaProviders hint="raw:AddMediaProvider">
          <mediaProvider type="Sitecore.Sandbox.Resources.Media.JohnWestOneMediaProvider, Sitecore.Sandbox" />
          <mediaProvider type="Sitecore.Sandbox.Resources.Media.JohnWestTwoMediaProvider, Sitecore.Sandbox" />
          <mediaProvider type="Sitecore.Sandbox.Resources.Media.JohnWestThreeMediaProvider, Sitecore.Sandbox" />
          <mediaProvider type="Sitecore.Resources.Media.MediaProvider, Sitecore.Kernel" />
        </mediaProviders>
      </mediaProvider>
    </mediaLibrary>
  </sitecore>
</configuration>

Let’s see this in action!

To test, I uploaded four identical photos of John West to the Media Library:

four-jw-media-library

I then inserted these into a Rich Text field on my home Item:

rte-jw-times-four

I saved my home Item and published everything. Once the publish was finished, I navigated to my home page and saw the following:

four-jw-home-page

As you can see, the three custom John West MediaProvider class instances served up their URLs, and the “out of the box” Sitecore.Resources.Media.MediaProvider instance served up its URL on the last image.

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

Until next time, have a Sitecoretastic day!

Bundle CSS and JavaScript Files in Sitecore MVC

The other day I was poking around Sitecore.Forms.Mvc.dll — this assembly ships with Web Forms for Marketers (WFFM), and is used when WFFM is running on Sitecore MVC — and noticed WFFM does some bundling of JavaScript and CSS files:

wffm-bundling

WFFM uses the above class as an <initialize> pipeline processor. You can see this defined in Sitecore.Forms.Mvc.config:

Sitecore-Forms-Mvc-config

This got me thinking: why not build my own class to serve as an <initialize> pipeline processor to bundle my CSS and JavaScript files?

As an experiment I whipped up the following class to do just that:

using System.Collections.Generic;
using System.Linq;
using System.Web.Optimization;

using Sitecore.Pipelines;

namespace Sitecore.Sandbox.Forms.Mvc.Pipelines
{         
    public class RegisterAdditionalFormBundles
    {
        public RegisterAdditionalFormBundles()
        {
            CssFiles = new List<string>();
            JavaScriptFiles = new List<string>();
        }

        public void Process(PipelineArgs args)
        {
            BundleCollection bundles = GetBundleCollection();
            if (bundles == null)
            {
                return;
            }

            AddBundle(bundles, CreateCssBundle());
            AddBundle(bundles, CreateJavaScriptBundle());
        }

        protected virtual BundleCollection GetBundleCollection()
        {
            return BundleTable.Bundles;
        }

        protected virtual Bundle CreateCssBundle()
        {
            if (!CanBundleAssets(CssVirtualPath, CssFiles))
            {
                return null;
            }

            return new StyleBundle(CssVirtualPath).Include(CssFiles.ToArray());
        }

        protected virtual Bundle CreateJavaScriptBundle()
        {
            if (!CanBundleAssets(JavaScriptVirtualPath, JavaScriptFiles))
            {
                return null;
            }

            return new ScriptBundle(JavaScriptVirtualPath).Include(JavaScriptFiles.ToArray());
        }

        protected virtual bool CanBundleAssets(string virtualPath, IEnumerable<string> filePaths)
        {
            return !string.IsNullOrWhiteSpace(virtualPath)
                    && filePaths != null
                    && filePaths.Any();
        }

        private static void AddBundle(BundleCollection bundles, Bundle bundle)
        {
            if(bundle == null)
            {
                return;
            }

            bundles.Add(bundle);
        }

        private string CssVirtualPath { get; set; }

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

        private string JavaScriptVirtualPath { get; set; }

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

The class above basically takes in a collection of CSS and JavaScript file paths as well as their virtual bundled paths — these are magically populated by Sitecore’s Configuration Factory using values provided by the configuration file shown below — iterates over both collections, and adds them to the BundleTable — the BundleTable is defined in System.Web.Optimization.dll.

I then glued everything together using a patch configuration file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <initialize>
        <processor patch:after="processor[@type='Sitecore.Forms.Mvc.Pipelines.RegisterFormBundles, Sitecore.Forms.Mvc']"
          type="Sitecore.Sandbox.Forms.Mvc.Pipelines.RegisterAdditionalFormBundles, Sitecore.Sandbox">
          <CssVirtualPath>~/wffm-bundles/styles.css</CssVirtualPath>
          <CssFiles hint="list">
            <CssFile>~/css/uniform.aristo.css</CssFile>
          </CssFiles>
          <JavaScriptVirtualPath>~/wffm-bundles/scripts.js</JavaScriptVirtualPath>
          <JavaScriptFiles hint="list">
            <JavaScriptFile>~/js/jquery.min.js</JavaScriptFile>
            <JavaScriptFile>~/js/jquery.uniform.min.js</JavaScriptFile>
            <JavaScriptFile>~/js/bind.uniform.js</JavaScriptFile>
          </JavaScriptFiles>
        </processor>
      </initialize>
    </pipelines>
  </sitecore>
</configuration>

I’m adding the <initialize> pipeline processor shown above after WFFM’s though theoretically you could add it anywhere within the <initialize> pipeline.

The CSS and JavaScript files defined in the configuration file above are from the Uniform project — this project includes CSS, JavaScript and images to make forms look nice, though I am in no way endorsing this project. I only needed some CSS and JavaScript files to spin up something quickly for testing.

For testing, I built the following View — it uses some helpers to render the <link> and <script> tags for the bundles — and tied it to my Layout in Sitecore:

@using System.Web.Optimization
@using Sitecore.Mvc
@using Sitecore.Mvc.Presentation

<!DOCTYPE html>
<html>

<head>
    <title></title>

    @Styles.Render("~/wffm-bundles/styles.css")
    @Scripts.Render("~/wffm-bundles/scripts.js")
    
</head>
<body>
    

    @Html.Sitecore().Placeholder("page content")

</body>
</html>

I then built a “Feedback” form in WFFM; mapped it to the “page content” placeholder defined in the View above; published it; and pulled it up in my browser. As you can see the code from the Uniform project styled the form:

styled-form

For comparison, this is what the form looks like without the <initialize> pipeline processor above:

2014-10-30_2316

If you have any thoughts on this, or have alternative ways of bundling CSS and JavaScript files in your Sitecore MVC solutions, please share in a comment.

Show Submitted Web Forms for Marketers Form Field Values on a Confirmation Page in Sitecore

Recently on my About page, someone had asked me how to show submitted form field values in Web Forms for Marketers.

I had done such a thing in a past project, and thought I would share how I went about accomplishing this.

This solution reuses an instance of a storage class I had used in a previous post.

This is the interface for that storage class:

namespace Sitecore.Sandbox.Utilities.Storage
{
    public interface IRepository<TKey, TValue>
    {
        bool Contains(TKey key);

        TValue this[TKey key] { get; set; }

        void Put(TKey key, TValue value);

        void Remove(TKey key);

        void Clear();

        TValue Get(TKey key);
    }
}

This is the implementation of the storage class:

using System.Web;
using System.Web.SessionState;

using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Utilities.Storage
{
    public class SessionRepository : IRepository<string, object>
    {
        private HttpSessionStateBase Session { get; set; }

        public object this[string key]
        {
            get
            {
                return Get(key);
            }
            set
            {
                Put(key, value);
            }
        }

        public SessionRepository()
            : this(HttpContext.Current.Session)
        {
        }

        public SessionRepository(HttpSessionState session)
            : this(CreateNewHttpSessionStateWrapper(session))
        {
        }

        public SessionRepository(HttpSessionStateBase session)
        {
            SetSession(session);
        }

        private void SetSession(HttpSessionStateBase session)
        {
            Assert.ArgumentNotNull(session, "session");
            Session = session;
        }

        public bool Contains(string key)
        {
            return Session[key] != null;
        }

        public void Put(string key, object value)
        {
            Assert.ArgumentNotNullOrEmpty(key, "key");
            Assert.ArgumentCondition(IsSerializable(value), "value", "value must be serializable!");
            Session[key] = value;
        }

        private static bool IsSerializable(object instance)
        {
            Assert.ArgumentNotNull(instance, "instance");
            return instance.GetType().IsSerializable;
        }

        public void Remove(string key)
        {
            Session.Remove(key);
        }

        public void Clear()
        {
            Session.Clear();
        }

        public object Get(string key)
        {
            return Session[key];
        }

        private static HttpSessionStateWrapper CreateNewHttpSessionStateWrapper(HttpSessionState session)
        {
            Assert.ArgumentNotNull(session, "session");
            return new HttpSessionStateWrapper(session);
        }
    }
}

The class above basically serializes a supplied object, and puts it into session using a key given by the calling code.

Plus, you can remove objects saved in it using a key.

I modified this class from the original version: I declared the constructors public so that I can reference them in a Sitecore configuration file (you will see this configuration file further down in this post).

I then created a POCO to house form field values for serialization purposes:

using System;
using System.Collections.Generic;

namespace Sitecore.Sandbox.Form.Submit.DTO
{
    [Serializable]
    public class Field
    {
        public string Name { get; set; }

        public string Value { get; set; }
    }
}

Field values belong to a form, so I built another POCO class to store a collection of Sitecore.Sandbox.Form.Submit.DTO.Field class instances, and also hold on to the submitted form’s ID:

using System;
using System.Collections.Generic;

namespace Sitecore.Sandbox.Form.Submit.DTO
{
    [Serializable]
    public class FormSubmission
    {
        public Guid ID { get; set; }

        public List<Field> Fields { get; set; }
    }
}

Now we need a custom Web Forms for Marketers SaveAction — all custom SaveActions must implement Sitecore.Form.Core.Client.Data.Submit.ISaveAction which is defined in Sitecore.Forms.Core.dll — to create and store instances of the POCO classes defined above:

using System.Collections.Generic;

using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Diagnostics;
using Sitecore.Web;

using Sitecore.Form.Core.Client.Data.Submit;
using Sitecore.Form.Core.Controls.Data;
using Sitecore.Form.Submit;

using Sitecore.Sandbox.Form.Submit.DTO;
using Sitecore.Sandbox.Utilities.Storage;

namespace Sitecore.Sandbox.Form.Submit
{
    public class StoreForm : ISaveAction
    {
        static StoreForm()
        {
            RepositoryKey = Settings.GetSetting("RepositoryKey");
            Assert.IsNotNullOrEmpty(RepositoryKey, "RepositoryKey must be set in your configuration!");

            Repository = Factory.CreateObject("repository", true) as IRepository<string, object>;
            Assert.IsNotNull(Repository, "Repository must be set in your configuration!");
        }

        public void Execute(ID formid, AdaptedResultList fields, params object[] data)
        {
            StoreFormSubmission(formid, fields);
        }

        protected virtual void StoreFormSubmission(ID formid, AdaptedResultList fields)
        {
            FormSubmission form = CreateNewFormSubmission(formid, fields);
            Repository[GetRepositoryKey()] = form;
        }

        protected virtual FormSubmission CreateNewFormSubmission(ID formid, AdaptedResultList fields)
        {
            return new FormSubmission
            {
                ID = formid.Guid,
                Fields = CreateNewFields(fields)
            };
        }

        protected virtual List<Field> CreateNewFields(AdaptedResultList results)
        {
            Assert.ArgumentNotNull(results, "results");
            List<Field> fields = new List<Field>();
            foreach (AdaptedControlResult result in results)
            {
                fields.Add(new Field{ Name = result.FieldName, Value = result.Value });
            }

            return fields;
        }

        protected virtual string GetRepositoryKey()
        {
            return string.Concat(RepositoryKey, "_", WebUtil.GetSessionID());
        }

        private static string RepositoryKey { get; set; }
        
        private static IRepository<string, object> Repository { get; set; }
    }
}

The SaveAction above creates instances of the POCO classes above using the submitted form field values, and passes these to an instance of a IRepository: this is defined in the configuration file below jointly with a substring of the unique storage key (this is a concatenation of the key defined in the following configuration file and the user’s session ID to guarantee a unique key):

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <repository type="Sitecore.Sandbox.Utilities.Storage.SessionRepository" />
    <settings>
      <setting name="RepositoryKey" value="MyRepository"/>
    </settings>
  </sitecore>
</configuration>

I then registered the ISaveAction class above in Web Forms for Marketers:

store-form-save-action

I then wired it up to my test form:

add-store-form-to-form

For testing, I created the following sublayout — no, it’s not the prettiest code I have ever written but I needed something quick for testing — which I mapped to a confirmation page:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Form Submission Confirmation.ascx.cs" Inherits="Sandbox.layouts.sublayouts.FormSubmissionConfirmation" %>
<asp:Repeater ID="rptConfirmation" runat="server">
    <HeaderTemplate>
        <h2>What you gave us:</h2>
    </HeaderTemplate>
    <ItemTemplate>
        <%# Eval("Name") %>: <%# Eval("Value") %>
    </ItemTemplate>
    <SeparatorTemplate>
        <br />
    </SeparatorTemplate>
</asp:Repeater>

The following class serves as the code-behind for the sublayout:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

using Sitecore.Configuration;
using Sitecore.Diagnostics;
using Sitecore.Sandbox.Form.Submit.DTO;
using Sitecore.Sandbox.Utilities.Storage;
using Sitecore.Web;

namespace Sandbox.layouts.sublayouts
{
    public partial class FormSubmissionConfirmation : System.Web.UI.UserControl
    {
        private static string RepositoryKey { get; set; }
        
        private static IRepository<string, object> Repository { get; set; }

        static FormSubmissionConfirmation()
        {
            RepositoryKey = Settings.GetSetting("RepositoryKey");
            Assert.IsNotNullOrEmpty(RepositoryKey, "RepositoryKey must be set in your configuration!");

            Repository = Factory.CreateObject("repository", true) as IRepository<string, object>;
            Assert.IsNotNull(Repository, "Repository must be set in your configuration!");
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            string key = GetRepositoryKey();
            FormSubmission submission = Repository.Get(key) as FormSubmission;
            Repository.Remove(key);
            if (submission == null || !submission.Fields.Any())
            {
               Visible = false;
                return;
            }
            
            rptConfirmation.DataSource = submission.Fields;
            rptConfirmation.DataBind();
        }

        protected virtual string GetRepositoryKey()
        {
            return string.Concat(RepositoryKey, "_", WebUtil.GetSessionID());
        }
    }
}

The code-behind above gets the FormSubmission instance from the IRepository instance defined in the configuration file shown above, and passes the Field POCO instances within it to a repeater.

Let’s see this in action!

I navigated to my test form, and filled it in:
filled-in-form

After submitting the form, I was redirected to my confirmation page. As you can see the form values I had entered are displayed:

form-confirmation

One thing to note: the solution above only works when your Web Forms for Marketers confirmation page is its own page, and you set your form to redirect to it after submitting the form.

If you have any thoughts on this, or know of other ways to show submitted Web Forms for Marketers form field values on a confirmation page, please share in a comment.

Execute PowerShell Scripts in Scheduled Tasks using Sitecore PowerShell Extensions

At the Sitecore User Group Conference 2014, I demonstrated how to invoke PowerShell scripts in a Sitecore Scheduled Task using Sitecore PowerShell Extensions, and felt I should pen what I had shown in a blog post — yes, you guessed it: this is that blog post. 😉

In my presentation, I shared the following PowerShell script with the audience:

ForEach($site in [Sitecore.Configuration.Factory]::GetSiteNames()) {
    $siteInfo = [Sitecore.Configuration.Factory]::GetSiteInfo($site)
    if($siteInfo -ne $null) {
         $siteInfo.HtmlCache.Clear()   
         $logEntry = [string]::Format("HtmlCache.Clear() invoked for {0}", $siteInfo.Name)
         Write-Log $logEntry
    }
}

The script above iterates over all sites in your Sitecore instance, clears the Html Cache for each, and creates log entries expressing the Html Cache was cleared for all sites processed.

You would probably never have to use a script like the one above. I only wrote it for demonstration purposes since I couldn’t think of a more practical example to show. If you can think of any practical examples, or feel the script above has some practicality, please share in a comment.

I wrote, tested, and saved the above script in the PowerShell ISE:

powershell-ise-task

The PowerShell script was saved to a new Item created by the dialog above:

task-location-script-library

I then created a Schedule Item to invoke the script housed in the Item above (to learn more about Sitecore Scheduled Tasks, please see John West‘s post discussing them):

create-schedule-item-spe

I saved my Item, waited a bit, and opened up my latest Sitecore log file:

html-cache-clear-spe

As you can see, the Html Cache was cleared for each site in my Sitecore instance.

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

Launch PowerShell Scripts in the Item Context Menu using Sitecore PowerShell Extensions

Last week during my Sitecore PowerShell Extensions presentation at the Sitecore User Group Conference 2014 — a conference held in Utrecht, Netherlands — I demonstrated how to invoke PowerShell scripts from the Item context menu in Sitecore, and felt I should capture what I had shown in a blog post — yes, this is indeed that blog post. 😉

During that piece of my presentation, I shared the following PowerShell script to expands tokens in fields of a Sitecore item (if you want to learn more about tokens in Sitecore, please take a look at John West’s post about them, and also be aware that one can also invoke the Expand-Token PowerShell command that comes with Sitecore PowerShell Extensions to expand tokens on Sitecore items — this makes things a whole lot easier 😉 ):

$item = Get-Item .
$tokenReplacer = [Sitecore.Configuration.Factory]::GetMasterVariablesReplacer()
$item.Editing.BeginEdit()
$tokenReplacer.ReplaceItem($item)
$item.Editing.EndEdit()
Close-Window

The script above calls Sitecore.Configuration.Factory.GetMasterVariablesReplacer() for an instance of the MasterVariablesReplacer class — which is defined and can be overridden in the “MasterVariablesReplacer” setting in your Sitecore instance’s Web.config — and passes the context item — this is denote by a period — to the MasterVariablesReplacer instance’s ReplaceItem() method after the item has been put into editing mode.

Once the Item has been processed, it is taken out of editing mode.

So how do we save this script so that we can use it in the Item context menu? The following screenshot walks you through the steps to do just that:

item-context-menu-powershell-ise

The script is saved to an Item created by the dialog above:

expand-tokens-item

Let’s test this out!

I selected an Item with unexpanded tokens:

home-tokens-to-expand

I then launched its Item context menu, and clicked the option we created to ‘Expand Tokens’:

home-item-context-menu-expand-tokens

As you can see the tokens were expanded:

home-tokens-expanded

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

Until next time, have a scriptolicious day 😉

Add Sitecore Rocks Commands to Protect and Unprotect Items

The other day I read this post where the author showcased a new Clipboard command he had added into Sitecore Rocks, and immediately wanted to experiment with adding my own custom command into Sitecore Rocks.

After some research, I stumbled upon this post which gave a walk-through on augmenting Sitecore Rocks by adding a Server Component — this is an assembled library of code for your Sitecore instance to handle requests from Sitecore Rocks — and a Plugin — this is an assembled library of code that can contain custom commands — and decided to follow its lead.

I first created a Sitecore Rocks Server Component project in Visual Studio:

sitecore-rocks-server-component

After some pondering, I decided to ‘cut my teeth’ on creating custom commands to protect and unprotect Items in Sitecore (for more information on protecting/unprotecting Items in Sitecore, check out ‘How to Protect or Unprotect an Item’ in Sitecore’s Client Configuration
Cookbook
).

I decided to use the template method pattern for the classes that will handle requests from Sitecore Rocks — I envisioned some shared logic across the two — and put this shared logic into the following base class:

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

namespace Sitecore.Rocks.Server.Requests
{
    public abstract class EditItem
    {
        public string Execute(string id, string databaseName)
        {
            Assert.ArgumentNotNullOrEmpty(id, "id");
            return ExecuteEditItem(GetItem(id, databaseName));
        }

        private string ExecuteEditItem(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            item.Editing.BeginEdit();
            string response = UpdateItem(item);
            item.Editing.EndEdit();
            return response;
        }

        protected abstract string UpdateItem(Item item);
        
        private static Item GetItem(string id, string databaseName)
        {
            Assert.ArgumentNotNullOrEmpty(id, "id");
            Database database = GetDatabase(databaseName);
            Assert.IsNotNull(database, string.Format("database: {0} does not exist!", databaseName));
            return database.GetItem(id);
        }

        private static Database GetDatabase(string databaseName)
        {
            Assert.ArgumentNotNullOrEmpty(databaseName, "databaseName");
            return Factory.GetDatabase(databaseName);
        }
    }
}

The EditItem base class above gets the Item in the requested database, and puts the Item into edit mode. It then passes the Item to the UpdateItem method — subclasses must implement this method — and then turns off edit mode for the Item.

As a side note, all Server Component request handlers must have a method named Execute.

For protecting a Sitecore item, I built the following subclass of the EditItem class above:

using Sitecore.Data.Items;

namespace Sitecore.Rocks.Server.Requests.Attributes
{
    public class ProtectItem : EditItem
    {
        protected override string UpdateItem(Item item)
        {
            item.Appearance.ReadOnly = true;
            return string.Empty;
        }
    }
}

The ProtectItem class above just protects the Item passed to it.

I then built the following subclass of EditItem to unprotect an item passed to its UpdateItem method:

using Sitecore.Data.Items;

namespace Sitecore.Rocks.Server.Requests.Attributes
{
    public class UnprotectItem : EditItem
    {
        protected override string UpdateItem(Item item)
        {
            item.Appearance.ReadOnly = false;
            return string.Empty;
        }
    }
}

I built the above Server Component solution, and put its resulting assembly into the /bin folder of my Sitecore instance.

I then created a Plugin solution to handle the protect/unprotect commands in Sitecore Rocks:

sitecore-rocks-plugin

I created the following command to protect a Sitecore Item:

using System;
using System.Linq;

using Sitecore.VisualStudio.Annotations;
using Sitecore.VisualStudio.Commands;
using Sitecore.VisualStudio.Data;
using Sitecore.VisualStudio.Data.DataServices;

using SmartAssembly.SmartExceptionsCore;

namespace Sitecore.Rocks.Sandbox.Commands
{
    [Command]
    public class ProtectItemCommand : CommandBase
    {
        public ProtectItemCommand()
        {
            Text = "Protect Item";
            Group = "Edit";
            SortingValue = 4010;
        }

        public override bool CanExecute([CanBeNull] object parameter)
        {
            IItemSelectionContext context = null;
            bool canExecute = false;
            try
            {
                context = parameter as IItemSelectionContext;
                canExecute = context != null && context.Items.Count() == 1 && !IsProtected(context.Items.FirstOrDefault());
            }
            catch (Exception ex)
            {
                StackFrameHelper.CreateException3(ex, context, this, parameter);
                throw;
            }

            return canExecute;
        }

        private static bool IsProtected(IItem item)
        {
            ItemVersionUri itemVersionUri = new ItemVersionUri(item.ItemUri, LanguageManager.CurrentLanguage, Sitecore.VisualStudio.Data.Version.Latest);
            Item item2 = item.ItemUri.Site.DataService.GetItemFields(itemVersionUri);
            foreach (Field field in item2.Fields)
            {
                if (string.Equals("__Read Only", field.Name, StringComparison.CurrentCultureIgnoreCase) && field.Value == "1")
                {
                    return true;
                }
            }

            return false;
        }

        public override void Execute([CanBeNull] object parameter)
        {
            IItemSelectionContext context = null;
            try
            {
                context = parameter as IItemSelectionContext;
                IItem item = context.Items.FirstOrDefault();
                item.ItemUri.Site.DataService.ExecuteAsync
                (
                    "Attributes.ProtectItem",
                    CreateEmptyCallback(),
                    new object[] 
                    { 
	                    item.ItemUri.ItemId.ToString(), 
	                    item.ItemUri.DatabaseName.ToString()
                    }
                );
            }
            catch (Exception ex)
            {
                StackFrameHelper.CreateException3(ex, context, this, parameter);
                throw;
            }
        }

        private ExecuteCompleted CreateEmptyCallback()
        {
            return (response, executeResult) => { return; };
        }
    }
}

The ProtectItemCommand command above is only displayed when the selected Item is not protected — this is ascertained by logic in the CanExecute method — and fires off a request to the Sitecore.Rocks.Server.Requests.Attributes.ProtectItem request handler in the Server Component above to protect the selected Item.

I then built the following command to do the exact opposite of the command above: only appear when the selected Item is protected, and make a request to Sitecore.Rocks.Server.Requests.Attributes.UnprotectItem — shown above in the Server Component — to unprotect the selected Item:

using System;
using System.Linq;

using Sitecore.VisualStudio.Annotations;
using Sitecore.VisualStudio.Commands;
using Sitecore.VisualStudio.Data;
using Sitecore.VisualStudio.Data.DataServices;

using SmartAssembly.SmartExceptionsCore;

namespace Sitecore.Rocks.Sandbox.Commands
{
    [Command]
    public class UnprotectItemCommand : CommandBase
    {
        public UnprotectItemCommand()
        {
            Text = "Unprotect Item";
            Group = "Edit";
            SortingValue = 4020;
        }

        public override bool CanExecute([CanBeNull] object parameter)
        {
            IItemSelectionContext context = null;
            bool canExecute = false;
            try
            {
                context = parameter as IItemSelectionContext;
                canExecute = context != null && context.Items.Count() == 1 && IsProtected(context.Items.FirstOrDefault());
            }
            catch (Exception ex)
            {
                StackFrameHelper.CreateException3(ex, context, this, parameter);
                throw;
            }

            return canExecute;
        }

        private static bool IsProtected(IItem item)
        {
            ItemVersionUri itemVersionUri = new ItemVersionUri(item.ItemUri, LanguageManager.CurrentLanguage, Sitecore.VisualStudio.Data.Version.Latest);
            Item item2 = item.ItemUri.Site.DataService.GetItemFields(itemVersionUri);
            foreach (Field field in item2.Fields)
            {
                if (string.Equals("__Read Only", field.Name, StringComparison.CurrentCultureIgnoreCase) && field.Value == "1")
                {
                    return true;
                }
            }

            return false;
        }

        public override void Execute([CanBeNull] object parameter)
        {
            IItemSelectionContext context = null;
            try
            {
                context = parameter as IItemSelectionContext;
                IItem item = context.Items.FirstOrDefault();
                item.ItemUri.Site.DataService.ExecuteAsync
                (
                    "Attributes.UnprotectItem",
                    CreateEmptyCallback(),
                    new object[] 
                    { 
	                    item.ItemUri.ItemId.ToString(), 
	                    item.ItemUri.DatabaseName.ToString()
                    }
                );
            }
            catch (Exception ex)
            {
                StackFrameHelper.CreateException3(ex, context, this, parameter);
                throw;
            }
        }

        private ExecuteCompleted CreateEmptyCallback()
        {
            return (response, executeResult) => { return; };
        }
    }
}

I had to do a lot of discovery in Sitecore.Rocks.dll via .NET Reflector in order to build the above commands, and had a lot of fun while searching and learning.

Unfortunately, I could not get the commands above to show up in the Sitecore Explorer context menu in my instance of Sitecore Rocks even though my plugin did make its way out to my C:\Users\[my username]\AppData\Local\Sitecore\Sitecore.Rocks\Plugins\ folder.

I troubleshooted for some time but could not determine why these commands were not appearing — if you have any ideas, please leave a comment — and decided to register my commands using Extensions in Sitecore Rocks as a fallback plan:

sitecore-rocks-extensions-menu-option

After clicking ‘Extensions’ in the Sitecore dropdown menu in Visual Studio, I was presented with the following dialog, and added my classes via the ‘Add’ button on the right:

sitecore-rocks-extension-dialog

Let’s see this in action.

I first created a Sitecore Item for testing:

item-unprotected

I navigated to that Item in the Sitecore Explorer in Sitecore Rocks, and right-clicked on it:

item-unprotected-1

After clicking ‘Protect Item’, I verified the Item was protected in Sitecore:

item-protected

I then went back to our test Item in the Sitecore Explorer of Sitecore Rocks, and right-clicked again:

sitecore-rocks-unprotect-item

After clicking ‘Unprotect Item’, I took a look at the Item in Sitecore, and saw that it was no longer protected:

item-unprotected-again

If you have any thoughts on this, or ideas for other commands that you would like to see in Sitecore Rocks, please drop a comment.

Until next time, have a Sitecoretastic day, and don’t forget: Sitecore Rocks!

Synchronize IDTable Entries Across Multiple Sitecore Databases Using a Custom publishItem Pipeline Processor

In a previous post I showed a solution that uses the Composite design pattern in an attempt to answer the following question by Sitecore MVP Kyle Heon:

Although I enjoyed building that solution, it isn’t ideal for synchronizing IDTable entries across multiple Sitecore databases — entries are added to all configured IDTables even when Items might not exist in all databases of those IDTables (e.g. the Sitecore Items have not been published to those databases).

I came up with another solution to avoid the aforementioned problem — one that synchronizes IDTable entries using a custom <publishItem> pipeline processor, and the following class contains code for that processor:

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

using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.IDTables;
using Sitecore.Diagnostics;
using Sitecore.Publishing.Pipelines.PublishItem;

namespace Sitecore.Sandbox.Pipelines.Publishing
{
    public class SynchronizeIDTables : PublishItemProcessor
    {
        private IEnumerable<string> _IDTablePrefixes;
        private IEnumerable<string> IDTablePrefixes
        {
            get
            {
                if (_IDTablePrefixes == null)
                {
                    _IDTablePrefixes = GetIDTablePrefixes();
                }

                return _IDTablePrefixes;
            }
        }

        private string IDTablePrefixesConfigPath { get; set; }

        public override void Process(PublishItemContext context)
        {
            Assert.ArgumentNotNull(context, "context");
            Assert.ArgumentNotNull(context.PublishOptions, "context.PublishOptions");
            Assert.ArgumentNotNull(context.PublishOptions.SourceDatabase, "context.PublishOptions.SourceDatabase");
            Assert.ArgumentNotNull(context.PublishOptions.TargetDatabase, "context.PublishOptions.TargetDatabase");
            IDTableProvider sourceProvider = CreateNewIDTableProvider(context.PublishOptions.SourceDatabase);
            IDTableProvider targetProvider = CreateNewIDTableProvider(context.PublishOptions.TargetDatabase);
            RemoveEntries(targetProvider, GetAllEntries(targetProvider, context.ItemId));
            AddEntries(targetProvider, GetAllEntries(sourceProvider, context.ItemId));
        }

        protected virtual IDTableProvider CreateNewIDTableProvider(Database database)
        {
            Assert.ArgumentNotNull(database, "database");
            return Factory.CreateObject(string.Format("IDTable[@id='{0}']", database.Name), true) as IDTableProvider;
        }

        protected virtual IEnumerable<IDTableEntry> GetAllEntries(IDTableProvider provider, ID itemId)
        {
            Assert.ArgumentNotNull(provider, "provider");
            Assert.ArgumentCondition(!ID.IsNullOrEmpty(itemId), "itemId", "itemId cannot be null or empty!");
            List<IDTableEntry> entries = new List<IDTableEntry>();
            foreach(string prefix in IDTablePrefixes)
            {
                IEnumerable<IDTableEntry> entriesForPrefix = provider.GetKeys(prefix, itemId);
                if (entriesForPrefix.Any())
                {
                    entries.AddRange(entriesForPrefix);
                }
            }

            return entries;
        }

        private static void RemoveEntries(IDTableProvider provider, IEnumerable<IDTableEntry> entries)
        {
            Assert.ArgumentNotNull(provider, "provider");
            Assert.ArgumentNotNull(entries, "entries");
            foreach (IDTableEntry entry in entries)
            {
                provider.Remove(entry.Prefix, entry.Key);
            }
        }

        private static void AddEntries(IDTableProvider provider, IEnumerable<IDTableEntry> entries)
        {
            Assert.ArgumentNotNull(provider, "provider");
            Assert.ArgumentNotNull(entries, "entries");
            foreach (IDTableEntry entry in entries)
            {
                provider.Add(entry);
            }
        }

        protected virtual IEnumerable<string> GetIDTablePrefixes()
        {
            Assert.ArgumentNotNullOrEmpty(IDTablePrefixesConfigPath, "IDTablePrefixConfigPath");
            return Factory.GetStringSet(IDTablePrefixesConfigPath);
        }
    }
}

The Process method above grabs all IDTable entries for all defined IDTable prefixes — these are pulled from the configuration file that is shown later on in this post — from the source database for the Item being published, and pushes them all to the target database after deleting all preexisting entries from the target database for the Item (the code is doing a complete overwrite for the Item’s IDTable entries in the target database).

I also added the following code to serve as an item:deleted event handler (if you would like to learn more about events and their handlers, check out John West‘s post about them, and also take a look at this page on the
Sitecore Developer Network (SDN)) to remove entries for the Item when it’s being deleted:

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

using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Events;
using Sitecore.Data.IDTables;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Events;

namespace Sitecore.Sandbox.Data.IDTables
{
    public class ItemEventHandler
    {
        private IEnumerable<string> _IDTablePrefixes;
        private IEnumerable<string> IDTablePrefixes
        {
            get
            {
                if (_IDTablePrefixes == null)
                {
                    _IDTablePrefixes = GetIDTablePrefixes();
                }

                return _IDTablePrefixes;
            }
        }

        private string IDTablePrefixesConfigPath { get; set; }

        protected void OnItemDeleted(object sender, EventArgs args)
        {
            if (args == null)
            {
                return;
            }

            Item item = Event.ExtractParameter(args, 0) as Item;
            if (item == null)
            {
                return;
            }

            DeleteItemEntries(item);
        }

        private void DeleteItemEntries(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            IDTableProvider provider = CreateNewIDTableProvider(item.Database.Name);
            foreach (IDTableEntry entry in GetAllEntries(provider, item.ID))
            {
                provider.Remove(entry.Prefix, entry.Key);
            }
        }

        protected virtual IEnumerable<IDTableEntry> GetAllEntries(IDTableProvider provider, ID itemId)
        {
            Assert.ArgumentNotNull(provider, "provider");
            Assert.ArgumentCondition(!ID.IsNullOrEmpty(itemId), "itemId", "itemId cannot be null or empty!");
            List<IDTableEntry> entries = new List<IDTableEntry>();
            foreach (string prefix in IDTablePrefixes)
            {
                IEnumerable<IDTableEntry> entriesForPrefix = provider.GetKeys(prefix, itemId);
                if (entriesForPrefix.Any())
                {
                    entries.AddRange(entriesForPrefix);
                }
            }

            return entries;
        }

        private static void RemoveEntries(IDTableProvider provider, IEnumerable<IDTableEntry> entries)
        {
            Assert.ArgumentNotNull(provider, "provider");
            Assert.ArgumentNotNull(entries, "entries");
            foreach (IDTableEntry entry in entries)
            {
                provider.Remove(entry.Prefix, entry.Key);
            }
        }

        protected virtual IDTableProvider CreateNewIDTableProvider(string databaseName)
        {
            return Factory.CreateObject(string.Format("IDTable[@id='{0}']", databaseName), true) as IDTableProvider;
        }

        protected virtual IEnumerable<string> GetIDTablePrefixes()
        {
            Assert.ArgumentNotNullOrEmpty(IDTablePrefixesConfigPath, "IDTablePrefixConfigPath");
            return Factory.GetStringSet(IDTablePrefixesConfigPath);
        }
    }
}

The above code retrieves all IDTable entries for the Item being deleted — filtered by the configuration defined IDTable prefixes — from its database’s IDTable, and calls the Remove method on the IDTableProvider instance that is created for the Item’s database for each entry.

I then registered all of the above in Sitecore using the following configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <events>
      <event name="item:deleted">
        <handler type="Sitecore.Sandbox.Data.IDTables.ItemEventHandler, Sitecore.Sandbox" method="OnItemDeleted">
          <IDTablePrefixesConfigPath>IDTablePrefixes/IDTablePrefix</IDTablePrefixesConfigPath>
        </handler>
      </event>
    </events>
    <IDTable type="Sitecore.Data.$(database).$(database)IDTable, Sitecore.Kernel" singleInstance="true">
      <patch:attribute name="id">master</patch:attribute>
      <param connectionStringName="master"/>
      <param desc="cacheSize">500KB</param>
    </IDTable>
    <IDTable id="web" type="Sitecore.Data.$(database).$(database)IDTable, Sitecore.Kernel" singleInstance="true">
      <param connectionStringName="web"/>
      <param desc="cacheSize">500KB</param>
    </IDTable>
    <IDTablePrefixes>
      <IDTablePrefix>IDTableTest</IDTablePrefix>
    </IDTablePrefixes>
    <pipelines>
      <publishItem>
        <processor type="Sitecore.Sandbox.Pipelines.Publishing.SynchronizeIDTables, Sitecore.Sandbox">
          <IDTablePrefixesConfigPath>IDTablePrefixes/IDTablePrefix</IDTablePrefixesConfigPath>
        </processor>
      </publishItem>
    </pipelines>
  </sitecore>
</configuration>

For testing, I quickly whipped up a web form to add a couple of IDTable entries using an IDTableProvider for the master database — I am omitting that code for brevity — and ran a query to verify the entries were added into the IDTable in my master database (I also ran another query for the IDTable in my web database to show that it contains no entries):

idtables-before-publish

I published both items, and queried the IDTable in the master and web databases:

idtables-after-publish-both-items

As you can see, both entries were inserted into the web database’s IDTable.

I then deleted one of the items from the master database via the Sitecore Content Editor:

idtables-deleted-from-master

It was removed from the IDTable in the master database.

I then published the deleted item’s parent with subitems:

idtables-published-deletion

As you can see, it was removed from the IDTable in the web database.

If you have any suggestions for making this code better, or have another solution for synchronizing IDTable entries across multiple Sitecore databases, please share in a comment.