Home » Processors (Page 2)

Category Archives: Processors

Expand New Tokens Added to Standard Values on All Items Using Its Template in Sitecore

If you have read some of my older posts, you probably know by now how much I love writing code that expands tokens on Items in Sitecore, and decided to build another solution that expands new tokens added to Standard Values Items of Templates — out of the box, these aren’t expanded on preexisting Items that use the Template of the Standard Values Item, and end up making their way in fields on those preexisting Items (for an alternative solution, check out this older post I wrote some time ago).

In the following solution — this solution is primarily composed of a custom pipeline — tokens that are added to fields on the Standard Values Item will be expanded on all Items that use the Template of the Standard Values Item after the Standard Values Item is saved in the Sitecore client (I hook into the <saveUI> pipeline for this action on save).

We first need a class whose instance serves as the custom pipeline’s argument object:

using Sitecore.Data.Items;
using Sitecore.Pipelines;
using System.Collections.Generic;

namespace Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems
{
    public class ExpandNewTokensOnAllItemsArgs : PipelineArgs
    {
        public Item StandardValuesItem { get; set; }

        private List<Item> items;
        public List<Item> Items 
        {
            get
            {
                if(items == null)
                {
                    items = new List<Item>();
                }

                return items;
            }
            set
            {
                items = value;
            }
        }
    }
}

The caller of the custom pipeline is required to pass the Standard Values Item that contains the new tokens. One of the processors of the custom pipeline will collect all Items that use its Template — these are stored in the Items collection property.

The instance of the following class serves as the first processor of the custom pipeline:

using System;

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

namespace Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems
{
    public class EnsureStandardValues
    {
        public void Process(ExpandNewTokensOnAllItemsArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.ArgumentNotNull(args.StandardValuesItem, "args.StandardValuesItem");
            if(IsStandardValues(args.StandardValuesItem))
            {
                return;
            }

            args.AbortPipeline();
        }

        protected virtual bool IsStandardValues(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            return StandardValuesManager.IsStandardValuesHolder(item);
        }
    }
}

This processor basically just ascertains whether the Item passed as the Standard Values Item is indeed a Standard Values Item — the code just delegates to the static IsStandardValuesHolder() method on Sitecore.Data.StandardValuesManager (this lives in Sitecore.Kernel.dll).

The instance of the next class serves as the second step of the custom pipeline:

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

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

namespace Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems
{
    public class EnsureUnexpandedTokens
    {
        private List<string> Tokens { get; set; }

        public EnsureUnexpandedTokens()
        {
            Tokens = new List<string>();
        }

        public void Process(ExpandNewTokensOnAllItemsArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.ArgumentNotNull(args.StandardValuesItem, "args.StandardValuesItem");
            if (!Tokens.Any())
            {
                args.AbortPipeline();
                return;
            }

            args.StandardValuesItem.Fields.ReadAll();
            foreach(Field field in args.StandardValuesItem.Fields)
            {
                if(HasUnexpandedTokens(field))
                {
                    return;
                }
            }
            
            args.AbortPipeline();
        }

        protected virtual bool HasUnexpandedTokens(Field field)
        {
            Assert.ArgumentNotNull(field, "field");
            foreach(string token in Tokens)
            {
                if(field.Value.Contains(token))
                {
                    return true;
                }
            }

            return false;
        }
    }
}

A collection of tokens are injected into the class’ instance via the Sitecore Configuration Factory — see the patch configuration file further down in this post — and determines if tokens exist in any of its fields. If no tokens are found, then the pipeline is aborted. Otherwise, we exit the Process() method immediately.

The instance of the following class serves as the third processor of the custom pipeline:

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

using Sitecore.ContentSearch;
using Sitecore.ContentSearch.SearchTypes;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Data.Managers;
using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems
{
    public class CollectAllItems
    {
        public void Process(ExpandNewTokensOnAllItemsArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.ArgumentNotNull(args.StandardValuesItem, "args.StandardValuesItem");
            args.Items = GetAllItemsByTemplateID(args.StandardValuesItem.TemplateID);
            if(args.Items.Any())
            {
                return;
            }

            args.AbortPipeline();
        }

        protected virtual List<Item> GetAllItemsByTemplateID(ID templateID)
        {
            Assert.ArgumentCondition(!ID.IsNullOrEmpty(templateID), "templateID", "templateID cannot be null or empty!");
            using (var context = ContentSearchManager.GetIndex("sitecore_master_index").CreateSearchContext())
            {
                var query = context.GetQueryable<SearchResultItem>().Where(i => i.TemplateId == templateID);
                return query.ToList().Select(result => result.GetItem()).ToList();
            }  
        }
    }
}

This class uses the Sitecore.ContentSearch API to find all Items that use the Template of the Standard Values Item. If at least one Item is found, we exit the Process() method immediately. Otherwise, we abort the pipeline.

The instance of the class below serves as the fourth processor of the custom pipeline:

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

using Sitecore.ContentSearch;
using Sitecore.ContentSearch.SearchTypes;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Data.Managers;
using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems
{
    public class FilterStandardValuesItem
    {
        public void Process(ExpandNewTokensOnAllItemsArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.ArgumentNotNull(args.Items, "args.Items");
            if(!args.Items.Any())
            {
                return;
            }

            args.Items = args.Items.Where(item => !IsStandardValues(item)).ToList();
        }

        protected virtual bool IsStandardValues(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            return StandardValuesManager.IsStandardValuesHolder(item);
        }
    }
}

The code in this class ensures the Stardard Values Item is not in the collection of Items. It’s probably not a good idea to expand tokens on the Standard Values Item. πŸ™‚

The instance of the next class serves as the final processor of the custom pipeline:

using System.Linq;

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

namespace Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems
{
    public class ExpandTokens
    {
        private MasterVariablesReplacer TokenReplacer { get; set; }

        public ExpandTokens()
        {
            TokenReplacer = GetTokenReplacer();
        }

        public void Process(ExpandNewTokensOnAllItemsArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.ArgumentNotNull(args.Items, "args.Items");
            if (!args.Items.Any())
            {
                args.AbortPipeline();
                return;
            }

            foreach(Item item in args.Items)
            {
                ExpandTokensOnItem(item);
            }
        }

        protected virtual void ExpandTokensOnItem(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            item.Fields.ReadAll();
            item.Editing.BeginEdit();
            TokenReplacer.ReplaceItem(item);
            item.Editing.EndEdit();
        }

        protected virtual MasterVariablesReplacer GetTokenReplacer()
        {
            return Factory.GetMasterVariablesReplacer();
        }
    }
}

The code above uses the instance of Sitecore.Data.MasterVariablesReplacer (subclass or otherwise) — this is defined in your Sitecore configuration at settings/setting[@name=”MasterVariablesReplacer”] — and passes all Items housed in the pipeline argument instance to its ReplaceItem() method — each Item is placed in an editing state before having their tokens expanded.

I then built the following class to serve as a <saveUI> pipeline processor (this pipeline is triggered when someone saves an Item in the Sitecore client):

using Sitecore;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Pipelines;
using Sitecore.Pipelines.Save;

using Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems;

namespace Sitecore.Sandbox.Pipelines.SaveUI
{
    public class ExpandNewStandardValuesTokens
    {
        private string ExpandNewTokensOnAllItemsPipeline { get; set; }

        public void Process(SaveArgs args)
        {
            Assert.IsNotNullOrEmpty(ExpandNewTokensOnAllItemsPipeline, "ExpandNewTokensOnAllItemsPipeline must be set in configuration!");
            foreach (SaveArgs.SaveItem saveItem in args.Items)
            {
                Item item = GetItem(saveItem);
                if(IsStandardValues(item))
                {
                    ExpandNewTokensOnAllItems(item);
                }
            }
        }

        protected virtual Item GetItem(SaveArgs.SaveItem saveItem)
        {
            Assert.ArgumentNotNull(saveItem, "saveItem");
            return Client.ContentDatabase.Items[saveItem.ID, saveItem.Language, saveItem.Version];
        }

        protected virtual bool IsStandardValues(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            return StandardValuesManager.IsStandardValuesHolder(item);
        }

        protected virtual void ExpandNewTokensOnAllItems(Item standardValues)
        {
            CorePipeline.Run(ExpandNewTokensOnAllItemsPipeline, new ExpandNewTokensOnAllItemsArgs { StandardValuesItem = standardValues });
        }
    }
}

The code above invokes the custom pipeline when the Item being saved is a Standard Values Item — the Standard Values Item is passed to the pipeline via a new ExpandNewTokensOnAllItemsArgs instance.

I then glued all of the pieces above together in the following patch configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <expandNewTokensOnAllItems>
        <processor type="Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems.EnsureStandardValues, Sitecore.Sandbox" />
        <processor type="Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems.EnsureUnexpandedTokens, Sitecore.Sandbox">
          <Tokens hint="list">
            <Token>$name</Token>
            <Token>$id</Token>
            <Token>$parentid</Token>
            <Token>$parentname</Token>
            <Token>$date</Token>
            <Token>$time</Token>
            <Token>$now</Token>
          </Tokens>
        </processor>
        <processor type="Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems.CollectAllItems, Sitecore.Sandbox" />
        <processor type="Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems.FilterStandardValuesItem, Sitecore.Sandbox" />
        <processor type="Sitecore.Sandbox.Pipelines.ExpandNewTokensOnAllItems.ExpandTokens, Sitecore.Sandbox" />
      </expandNewTokensOnAllItems>
    </pipelines>
    <processors>
      <saveUI>
        <processor patch:before="saveUI/processor[@type='Sitecore.Pipelines.Save.Save, Sitecore.Kernel']" 
                   mode="on" type="Sitecore.Sandbox.Pipelines.SaveUI.ExpandNewStandardValuesTokens">
          <ExpandNewTokensOnAllItemsPipeline>expandNewTokensOnAllItems</ExpandNewTokensOnAllItemsPipeline>
        </processor>  
      </saveUI>
    </processors>
  </sitecore>
</configuration>

Let’s see this in action!

I added three new fields to a template, and added some tokens in them:

added-new-tokens

After clicking save, I navigated to one of the content Items that use this Template:

tokens-expanded

As you can see, the tokens were expanded. πŸ™‚

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

Utilize the Strategy Design Pattern for Content Editor Warnings in Sitecore

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

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

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

content-editor-warning-example

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

using System.Collections.Generic;

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

        string Message { get; set; }

        List<CommandLink> Links { get; set; }

        bool HasContent();

        IWarning Clone();
    }
}

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

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

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

using System.Collections.Generic;

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

        public string Message { get; set; }

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

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

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

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

            return clone;
        }
    }
}

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

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

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


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

        public string Command { get; set; }
    }
}

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

using System.Collections.Generic;

using Sitecore.Data.Items;

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

        IEnumerable<IWarning> Generate();
    }
}

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

using System.Collections.Generic;

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

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

        private IWarning Warning { get; set; }

        public Item Item { get; set; }

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

            return new[] { Warning };
        }

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

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

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

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

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

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

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

        private string Conjunction { get; set; }

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

        private IWarning Warning { get; set; }

        public Item Item { get; set; }

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

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

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

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

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

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

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

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

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

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

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

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

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

using System.Collections.Generic;

using Sitecore.Data.Items;

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

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

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

using System.Collections.Generic;

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

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

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

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

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

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

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

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

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

        private IWarningsGeneratorContext WarningsGeneratorContext { get; set; }

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

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

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

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

            return warnings;
        }

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

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

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

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

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

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

            return Translate.Text(text);
        }

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

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

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

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

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

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

Let’s test this out.

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

strategy-1

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

Let’s convert the Item into an Item Bucket:

strategy-2

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

strategy-3

Let’s fix the Item’s name:

strategy-4

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

strategy-5

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

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

So, what’s the benefit here?

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

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

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

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

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

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

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

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

content-editor-warning-example

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

using System.Collections.Generic;

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

        string Message { get; set; }

        List<CommandLink> Links { get; set; }

        bool HasContent();

        IWarning Clone();
    }
}

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

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

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

using System.Collections.Generic;

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

        public string Message { get; set; }

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

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

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

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

            return clone;
        }
    }
}

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

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

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


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

        public string Command { get; set; }
    }
}

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

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

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

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

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

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

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

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

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

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

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

            return Translate.Text(text);
        }
    }
}

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

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

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

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

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

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

        private IWarning Warning { get; set; }

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

            return new[] { Warning };
        }

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

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

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

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

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

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

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

        private string Conjunction { get; set; }

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

        private IWarning Warning { get; set; }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Let’s see if this works.

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

no-content-editor-warnings

As you can see, there are no warnings.

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

both-warnings-appear

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

Let’s now rename the Item:

rename-dialog

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

item-bucket-click-1

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

item-bucket-click-2

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

item-bucket-click-3

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

item-bucket-click-4

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

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

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

Augment Functionality in Sitecore Using the Decorator Design Pattern

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

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

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

So, what exactly did I come up with?

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

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

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

        private HttpRequestProcessor InnerProcessor { get; set; }

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

            Context.Item = item; 
        }

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

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

            return GetItem(entry.ID);
        }

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

            return database.GetItem(id);
        }

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

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

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

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

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

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

Let’s see this in action!

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

mario-content-editor

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

url-rewrite-id-table

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

resolved-url

As you can see, it worked.

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

mario-resolved

The above worked because the inner processor resolved it.

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

cat-page-five-sitecore

As you can see, that also worked:

cat-page-five

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

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.

Clone Items using the Sitecore Item Web API

Yesterday, I had the privilege to present with Ben Lipson and Jamie Michalski, both of Velir, on the Sitecore Item Web API at the New England Sitecore User Group — if you want to see us in action, check out the recording of our presentation!

Plus, my slides are available here!

During my presentation, I demonstrated how easy it is to customize the Sitecore Item API by adding a custom <itemWebApiRequest> pipeline processor, and a custom pipeline to handle a cloning request — for another example on adding a custom <itemWebApiRequest> pipeline processor, and another pipeline to execute a different custom operation, have a look at this post where I show how to publish Items using the Sitecore Item Web API.

For any custom pipeline you build for the Sitecore Item Web API, you must define a Parameter Object that inherits from Sitecore.ItemWebApi.Pipelines.OperationArgs:

using System.Collections.Generic;

using Sitecore.Data.Items;

using Sitecore.ItemWebApi.Pipelines;

namespace Sitecore.Sandbox.ItemWebApi.Pipelines.Clone
{
    public class CloneArgs : OperationArgs
    {
        public CloneArgs(Item[] scope)
            : base(scope)
        {
        }

        public IEnumerable<Item> Destinations { get; set; }

        public bool IsRecursive { get; set; }

        public IEnumerable<Item> Clones { get; set; }
    }
}

I added three properties to the class above: a property to hold parent destinations for clones; another indicating whether all descendants should be cloned; and a property to hold a collection of the clones.

I then created a base class for processors of my custom pipeline for cloning:

using Sitecore.ItemWebApi.Pipelines;

namespace Sitecore.Sandbox.ItemWebApi.Pipelines.Clone
{
    public abstract class CloneProcessor : OperationProcessor<CloneArgs>
    {
        protected CloneProcessor()
        {
        }
    }
}

The above class inherits from Sitecore.ItemWebApi.Pipelines.OperationProcessor which is the base class for most Sitecore Item Web API pipelines.

The following class serves as one processor of my custom cloning pipeline:

using System.Collections.Generic;

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

namespace Sitecore.Sandbox.ItemWebApi.Pipelines.Clone
{
    public class CloneItems : CloneProcessor
    {
        public override void Process(CloneArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.ArgumentNotNull(args.Scope, "args.Scope");
            Assert.ArgumentNotNull(args.Destinations, "args.Destinations");
            IList<Item> clones = new List<Item>();
            foreach (Item itemToClone in args.Scope)
            {
                foreach (Item destination in args.Destinations)
                {
                    clones.Add(CloneItem(itemToClone, destination, args.IsRecursive));
                }   
            }

            args.Clones = clones;
        }

        private Item CloneItem(Item item, Item destination, bool isRecursive)
        {
            Assert.ArgumentNotNull(item, "item");
            Assert.ArgumentNotNull(destination, "destination");
            return item.CloneTo(destination, isRecursive);
        }
    }
}

The class above iterates over all Items in scope — these are the Items being cloned — and clones all to the specified destinations (parent Items of the clones).

I then spun up the following class to serve as another processor in my custom cloning pipeline:

using System.Linq;

using Sitecore.Diagnostics;
using Sitecore.Pipelines;

using Sitecore.ItemWebApi.Pipelines.Read;

namespace Sitecore.Sandbox.ItemWebApi.Pipelines.Clone
{
    public class SetResult : CloneProcessor
    {
        public override void Process(CloneArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.ArgumentNotNull(args.Clones, "args.Clones");
            if (args.Result == null)
            {
                ReadArgs readArgs = new ReadArgs(args.Clones.ToArray());
                CorePipeline.Run("itemWebApiRead", readArgs);
                args.Result = readArgs.Result;
            }
        }
    }
}

The above class delegates to the <itemWebApiRead> pipeline which retrieves the clones from Sitecore, and stores these in the Parameter Object instance for the custom cloning pipeline.

In order to handle custom requests in the Sitecore Item Web API, you must create a custom <itemWebApiRequest> pipeline processor. I put together the following class to handle my cloning operation:

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

using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.ItemWebApi;
using Sitecore.ItemWebApi.Pipelines.Request;
using Sitecore.Pipelines;
using Sitecore.Text;
using Sitecore.Web;

using Sitecore.Sandbox.ItemWebApi.Pipelines.Clone;

namespace Sitecore.Sandbox.ItemWebApi.Pipelines.Request
{
    public class ResolveCloneAction : RequestProcessor
    {
        public override void Process(RequestArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.ArgumentNotNullOrEmpty(RequestMethod, "RequestMethod");
            Assert.ArgumentNotNullOrEmpty(MultipleItemsDelimiter, "MultipleItemsDelimiter");
            if (!ShouldProcessRequest(args))
            {
                return;
            }

            IEnumerable<Item> destinations = GetDestinationItems();
            if (!destinations.Any())
            {
                Logger.Warn("Cannot process clone action: there are no destination items!");
                return;
            }
            
            CloneArgs cloneArgs = new CloneArgs(args.Scope) 
            { 
                Destinations = destinations,
                IsRecursive = DoRecursiveCloning() 
            };
            CorePipeline.Run("itemWebApiClone", cloneArgs);
            args.Result = cloneArgs.Result;
        }

        private bool ShouldProcessRequest(RequestArgs args)
        {
            // Is this the request method we care about?
            if (!AreEqualIgnoreCase(args.Context.HttpContext.Request.HttpMethod, RequestMethod))
            {
                return false;
            }

            // are multiple axes supplied?
            if (WebUtil.GetQueryString("scope").Contains(MultipleItemsDelimiter))
            {
                Logger.Warn("Cannot process clone action: multiple axes detected!");
                return false;
            }

            // are there any items in scope?
            if (!args.Scope.Any())
            {
                Logger.Warn("Cannot process clone action: there are no items in Scope!");
                return false;
            }

            return true;
        }

        private static bool AreEqualIgnoreCase(string one, string two)
        {
            return string.Equals(one, two, StringComparison.CurrentCultureIgnoreCase);
        }
        
        private IEnumerable<Item> GetDestinationItems()
        {
            char delimiter;
            Assert.ArgumentCondition(char.TryParse(MultipleItemsDelimiter, out delimiter), "MultipleItemsDelimiter", "MultipleItemsDelimiter must be a single character!");
            ListString destinations = new ListString(WebUtil.GetQueryString("destinations"), delimiter);
            return (from destination in destinations
                    let destinationItem = GetItem(destination)
                    where destinationItem != null
                    select destinationItem).ToList();
        }

        private Item GetItem(string path)
        {
            try
            {
                return Sitecore.ItemWebApi.Context.Current.Database.Items[path];
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
            }

            return null;
        }

        private bool DoRecursiveCloning()
        {
            bool recursive;
            if (bool.TryParse(WebUtil.GetQueryString("recursive"), out recursive))
            {
                return recursive;
            }
            
            return false;
        }

        private string RequestMethod { get; set; }

        private string MultipleItemsDelimiter { get; set; }
    }
}

The above class ascertains whether it should handle the request: is the RequestMethod passed via configuration equal to the request method detected, and are there any Items in scope? I also built this processor to handle only one axe in order to keep the code simple.

Once the class determines it should handle the request, it grabs all destination Items from the context database — this is Sitecore.ItemWebApi.Context.Current.Database which is populated via the sc_database query string parameter passed via the request.

Further, the class above detects whether the cloning operation is recursive: should we clone all descendants of the Items in scope? This is also passed by a query string parameter.

I then glued everything together using the following Sitecore configuration file:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <itemWebApiClone>
        <processor type="Sitecore.Sandbox.ItemWebApi.Pipelines.Clone.CloneItems, Sitecore.Sandbox" />
        <processor type="Sitecore.Sandbox.ItemWebApi.Pipelines.Clone.SetResult, Sitecore.Sandbox" />
      </itemWebApiClone>
      <itemWebApiRequest>
        <processor patch:before="*[@type='Sitecore.ItemWebApi.Pipelines.Request.ResolveAction, Sitecore.ItemWebApi']"
                   type="Sitecore.Sandbox.ItemWebApi.Pipelines.Request.ResolveCloneAction, Sitecore.Sandbox">
          <RequestMethod>clone</RequestMethod>
          <MultipleItemsDelimiter>|</MultipleItemsDelimiter>
        </processor>
      </itemWebApiRequest>
    </pipelines>
  </sitecore>
</configuration>

Let’s clone the following Sitecore Item with descendants to two folders:

item-to-clone-destinations

In order to make this happen, I spun up the following HTML page using jQuery — no doubt the front-end gurus reading this are cringing when seeing the following code, but I am not much of a front-end developer:

<!DOCTYPE html>
<html lang="en">
	<head>
		<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
		<script src="//cdnjs.cloudflare.com/ajax/libs/json3/3.3.2/json3.min.js"></script>
		<script src="//cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.js"></script>
		<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.css" />
	</head>
	<body>
		<img width="400" style="display: block; margin-left: auto; margin-right: auto" src="/assets/img/clone-all-the-things.jpg" />
		<input type="button" id="button" value="Clone" style="width:100px;height:50px;font-size: 24px;" />
		<h2 id="confirmation" style="display: none;">Whoa! Something happened!</h2>
		<div id="working" style="display: none;"><img style="display: block; margin-left: auto; margin-right: auto" src="/assets/img/arrow-working.gif" /></div>
		<pre id="responseContainer" class="prettyprint" style="display: none;"><code id="response" class="language-javascript"></code></pre>
		<script type="text/javascript">
		$('#button').click(function() {
			$('#confirmation').hide();
			$('#responseContainer').hide();
			$('#working').show();
			$.ajax({
					type:'clone',
					url: "http://sandbox7/-/item/v1/sitecore/content/Home/Landing Page One?scope=s&destinations=/sitecore/content/Home/Clones|/sitecore/content/Home/Some More Clones&recursive=true&sc_database=master",
					headers:{
						"X-Scitemwebapi-Username":"extranet\\ItemWebAPI",
						"X-Scitemwebapi-Password":"1t3mW3bAP1"}
				}).done(function(response) {
					$('#confirmation').show();
					$('#response').html(JSON.stringify(response, null, 4));
					$('#working').hide();
					$('#responseContainer').show();
				});
		});
		</script>
	</body>
</html>

Plus, please pardon the hard-coded Sitecore credentials — I know you would never store a username and password in front-end code, right? πŸ˜‰

The above HTML page looks like this on initial load:

clone-items-html-page-no-data

I then clicked the ‘Clone’ button, and saw the following:

cloned-items-html-page

As you can see, the target Item with descendants were cloned to the destination folders set in the jQuery above:

items-cloned-sitecore

If you have any thoughts on this, or have other ideas around customizing the Sitecore Item Web API, please share in a comment.

Leverage the Sitecore Configuration Factory: Inject Dependencies Through Class Constructors

In my previous post, I showed how you can leverage Sitecore’s Configuration Factory to inject dependencies into properties of class instances — this is known as Setter injection in the Dependency injection world — and thought I would share another way you can inject dependencies into instances of classes defined in configuration: through class constructors (this is known as Constructor injection).

Suppose we have the following interface for objects that perform some kind of operation on parameters passed to their DoSomeOtherStuff() method:

namespace Sitecore.Sandbox.Pipelines.SomePipeline
{
    public interface ISomeOtherThing
    {
        void DoSomeOtherStuff(SomeProcessorArgs args, string someString);
    }
}

The following dummy class implements the interface above — sadly, it does not do anything:

namespace Sitecore.Sandbox.Pipelines.SomePipeline
{
    public class SomeOtherThing : ISomeOtherThing
    {
        public void DoSomeOtherStuff(SomeProcessorArgs args, string someString)
        {
            // TODO: add code to do some other stuff
        }
    }
}

In my previous post, I defined the following interface for objects that “do stuff”, and will reuse it here:

namespace Sitecore.Sandbox.Pipelines.SomePipeline
{
    public interface ISomeThing
    {
        void DoStuff(SomeProcessorArgs args);
    }
}

I have modified the SomeThing class from my previous post to consume an instance of a class that implements the ISomeOtherThing interface above along with a string instance:

using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Pipelines.SomePipeline
{
    public class SomeThing : ISomeThing
    {
        public SomeThing(ISomeOtherThing someOtherThing, string someString)
        {
            Assert.ArgumentNotNull(someOtherThing, "someOtherThing");
            Assert.ArgumentNotNullOrEmpty(someString, "someString");
            SomeOtherThing = someOtherThing;
            SomeString = someString;
        }

        public void DoStuff(SomeProcessorArgs args)
        {
            SomeOtherThing.DoSomeOtherStuff(args, SomeString);
        }

        private ISomeOtherThing SomeOtherThing { get; set; }

        private string SomeString { get; set; }
    }
}

The Sitecore Configuration Factory will magically create instances of the types passed to the constructor of the SomeThing class defined above, and you can then assign these instances to members defined in your class.

As far as I know — I did a lot of digging in Sitecore.Kernel.dll for answers, and some code experimentation — the Sitecore Configuration Factory will only magically inject instances of class types and strings: it will not inject .NET primitive types (if I am incorrect on this, please share in a comment).

I have reused the SomeProcessor class from my previous post — I did not change any code in it:

using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Pipelines.SomePipeline
{
    public class SomeProcessor
    {
        public void Process(SomeProcessorArgs args)
        {
            DoSomethingWithArgs(args);
        }

        private void DoSomethingWithArgs(SomeProcessorArgs args)
        {
            Assert.ArgumentNotNull(SomeThing, "SomeThing");
            SomeThing.DoStuff(args);
        }

        private ISomeThing SomeThing { get; set; } // is populated magically via Setter injection!
    }
}

We can then piece everything together using a Sitecore patch configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <somePipeline>
        <processor type="Sitecore.Sandbox.Pipelines.SomePipeline.SomeProcessor, Sitecore.Sandbox">
          <SomeThing type="Sitecore.Sandbox.Pipelines.SomePipeline.SomeThing, Sitecore.Sandbox">
            <param hint="1" type="Sitecore.Sandbox.Pipelines.SomePipeline.SomeOtherThing, Sitecore.Sandbox" />
            <param hint="2">just some string</param>
          </SomeThing>  
        </processor>  
      </somePipeline>
    </pipelines>
  </sitecore>
</configuration>

You must define <param> elements in order to pass arguments to constructors. The “hint” attribute determines the order of the parameters passed to the class constructor, though I believe using this attribute is optional (if I am wrong on this assumption, please share in a comment below).

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

Leverage the Sitecore Configuration Factory: Populate Class Properties with Instances of Types Defined in Configuration

I thought I would jot down some information that frequently comes up when I am asked to recommend plans of attack on projects. The first recommendation I always give for any Sitecore project is: define as much as you possibly can in Sitecore configuration. Doing so introduces seams in your code: code that does not have to change when its underlying behavior changes — think interfaces. πŸ˜‰

When defining types in Sitecore configuration, you are leveraging Sitecore’s built-in Dependency Injection framework: Sitecore’s Configuration Factory will magically inject instances of classes — yes, you would define these in configuration files — into properties of classes that are used for your pipeline processors, event handlers, and other Sitecore configuration-defined objects.

For example, suppose we have the following interface for classes that change state on an instance of a phony subclass of Sitecore.Pipelines.PipelineArgs:

namespace Sitecore.Sandbox.Pipelines.SomePipeline
{
    public interface ISomeThing
    {
        void DoStuff(SomeProcessorArgs args);
    }
}

Let’s create a fake class that implements the ISomeThing interface above:

namespace Sitecore.Sandbox.Pipelines.SomePipeline
{
    public class SomeThing : ISomeThing
    {
        public void DoStuff(SomeProcessorArgs args)
        {
            // it would be nice if we had code in here
        }
    }
}

We can then define a class property with the ISomeThing interface type in a class that serves as a pipeline processor:

using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Pipelines.SomePipeline
{
    public class SomeProcessor
    {
        public void Process(SomeProcessorArgs args)
        {
            DoSomethingWithArgs(args);
        }

        private void DoSomethingWithArgs(SomeProcessorArgs args)
        {
            Assert.IsNotNull(SomeThing, "SomeThing must be set in your configuration!");
            SomeThing.DoStuff(args);
        }

        private ISomeThing SomeThing { get; set; } // this is populated magically!
    }
}

The class above would serve as a processor for the dummy <somePipeline> defined in the following configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <somePipeline>
        <processor type="Sitecore.Sandbox.Pipelines.SomePipeline.SomeProcessor, Sitecore.Sandbox">
          <SomeThing type="Sitecore.Sandbox.Pipelines.SomePipeline.SomeThing, Sitecore.Sandbox" />
        </processor>  
      </somePipeline>
    </pipelines>
  </sitecore>
</configuration>

In the configuration file above, we defined a <SomeThing /> element within the processor element of the <somePipeline> pipeline, and this directly maps to the SomeThing property in the SomeProcessor class shown above. Keep in mind that names matter here, so the name of the configuration element must match the name of the property.

Until next time, have a Sitecoretastic day!

Omit HTML Breaks From Rendered Multi-Line Text Fields in Sitecore

Earlier today while preparing a training session on how to add JavaScript from a Multi-Line Text field to a rendered Sitecore page, I encountered something I had seen in the past but forgot about: Sitecore FieldControls and the FieldRenderer Web Control will convert newlines into HTML breaks.

For example, suppose you have the following JavaScript in a Multi-Line Text field:

javascript-in-field

You could use a Text FieldControl to render it:

<%@ Control Language="c#" AutoEventWireup="true" TargetSchema="http://schemas.microsoft.com/intellisense/ie5" %>
<sc:Text ID="scJavaScript" Field="JavaScript" runat="server" />

Unfortunately, your JavaScript will not work since it will contain HTML breaks:

html-breaks-in-rendered-value

Why does this happen? In the RenderField() method in Sitecore.Web.UI.WebControls.FieldRenderer — this lives in Sitecore.Kernel.dll, and is called by all FieldControls — passes a “linebreaks” parameter to the <renderField> pipeline:

field-renderer-html-breaks

The Process() method in Sitecore.Pipelines.RenderField.GetMemoFieldValue — this serves as one of the “out of the box” processors of the <renderField> pipeline — converts all carriage returns, line feeds, and newlines into HTML breaks:

get-memo-field-value

What can we do to prevent this from happening? Well, you could spin up a new class with a Process() method to serve as a new <renderField> pipeline processor, and use that instead of Sitecore.Pipelines.RenderField.GetMemoFieldValue:

using System;

using Sitecore.Diagnostics;
using Sitecore.Pipelines.RenderField;

namespace Sitecore.Sandbox.Pipelines.RenderField
{
    public class GetRawMemoFieldValueWhenApplicable
    {
        public void Process(RenderFieldArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            if(!AreEqualIgnoreCase(args.FieldTypeKey, "memo") && !AreEqualIgnoreCase(args.FieldTypeKey, "multi-line text"))
            {
                return;
            }

            bool omitHtmlBreaks;
            if (bool.TryParse(args.Parameters["omitHtmlBreaks"], out omitHtmlBreaks))
            {
                return;
            }

            Assert.IsNotNull(DefaultGetMemoFieldValueProcessor, "DefaultGetMemoFieldValueProcessor must be set in your configuration!");
            DefaultGetMemoFieldValueProcessor.Process(args);
        }

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

        private GetMemoFieldValue DefaultGetMemoFieldValueProcessor { get; set; }
    }
}

The Process() method in the class above looks for an “omitHtmlBreaks” parameter, and just exits out of the Process() method when it is set to true — it leaves the field value “as is”.

If the “omitHtmlBreaks”parameter is not found in the RenderFieldArgs instance, or it is set to false, the Process() method delegates to the Process() method of its DefaultGetMemoFieldValueProcessor property — this would be an instance of the “out of the box” Sitecore.Pipelines.RenderField.GetMemoFieldValue, and this is passed to the new <renderField> pipeline processor via the following configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <renderField>
        <processor patch:instead="processor[@type='Sitecore.Pipelines.RenderField.GetMemoFieldValue, Sitecore.Kernel']"
                   type="Sitecore.Sandbox.Pipelines.RenderField.GetRawMemoFieldValueWhenApplicable, Sitecore.Sandbox">
          <DefaultGetMemoFieldValueProcessor type="Sitecore.Pipelines.RenderField.GetMemoFieldValue, Sitecore.Kernel" />
        </processor>  
      </renderField>
    </pipelines>
  </sitecore>
</configuration>

Let’s test this.

I added the “omitHtmlBreaks” parameter to the control I had shown above:

<%@ Control Language="c#" AutoEventWireup="true" TargetSchema="http://schemas.microsoft.com/intellisense/ie5" %>
<sc:Text ID="scJavaScript" Field="JavaScript" Parameters="omitHtmlBreaks=true" runat="server" />

When I loaded my page, I was given a warm welcome:

alert-box-welcome

When I viewed my page’s source, I no longer see HTML breaks:

javascript-no-html-breaks

If you have any thoughts on this, or know of another way to do this, please share in a comment.

Set Default Alternate Text on Images Uploaded to the Sitecore Media Library

For the past couple of years, I have been trying to come up with an idea for adding a custom <getMediaCreatorOptions> pipeline processor — this is no lie or exaggeration — but had not thought of any good reason to do so until today: I figured out that I could add a processor to set default alternate text on an image being uploaded into the Sitecore Media Library.

The following class contains code to serve as a <getMediaCreatorOptions> pipeline processor to set default alternate text on an image Item during upload:

using Sitecore.Diagnostics;
using Sitecore.Pipelines.GetMediaCreatorOptions;

namespace Sitecore.Sandbox.Pipelines.GetMediaCreatorOptions
{
    public class SetDefaultAlternateTextIfNeed
    {
        public void Process(GetMediaCreatorOptionsArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            if (!string.IsNullOrWhiteSpace(args.Options.AlternateText))
            {
                return;
            }

            args.Options.AlternateText = GetAlternateText(args);
        }

        protected virtual string GetAlternateText(GetMediaCreatorOptionsArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            if (string.IsNullOrWhiteSpace(args.Options.Destination) || args.Options.Destination.IndexOf("/") < 0)
            {
                return string.Empty;
            }

            int startofNameIndex = args.Options.Destination.LastIndexOf("/") + 1;
            return args.Options.Destination.Substring(startofNameIndex);
        }
    }
}

The code above will set the AlternateText property of the Options property of the GetMediaCreatorOptionsArgs instance when its not set: I set it to be the name of the Media Library Item by default — I extract this from the path destination of the Item.

I then registered the above class as a <getMediaCreatorOptions> pipeline processor in the following Sitecore configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <getMediaCreatorOptions>
        <processor type="Sitecore.Sandbox.Pipelines.GetMediaCreatorOptions.SetDefaultAlternateTextIfNeed, Sitecore.Sandbox"/>
      </getMediaCreatorOptions>
    </pipelines>
  </sitecore>
</configuration>

Let’s try this out.

I went to my Media Library, and selected an image to upload:

selected-image-upload

During the upload, I did not specify its alternate text.

As you can see, it was given an alternate text value by default:

default-alt-text-set

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

But whatever you do, just don’t let this happen to you:

anxiety-cat