Home » Configuration (Page 3)

Category Archives: Configuration

One Approach to Render a Custom General Link Field Attribute in a Sitecore MVC View Rendering via Glass.Mapper

In my previous post, I shared a way to add a custom attribute to the General Link field in Sitecore — in that post I called this attribute “Tag” and will continue to do so here — and also showed how to render it on the front-end using the Sitecore Link field control.

You might have been asking yourself when reading that last post “Mike, how would I go about getting this to work in the Sitecore ORM Glass.Mapper?” (well, actually, I planted a seed in that post that I was going to write another post on how to get this to work in Glass.Mapper so you might not have been asking yourself that at all but instead were thinking “Mike, just get on with it!”).

In this post, I am going to show you one approach on how to tweak Glass to render a Tag attribute in the rendered markup of a General Link field (I’m not going reiterate the bits on how to customize the General Link field as I had done in my previous post, so you might want to have a read of that first before reading this post).

I first created a custom Sitecore.Data.Fields.LinkField class:

using Sitecore.Data.Fields;

namespace Sitecore.Sandbox.Data.Fields
{
    public class TagLinkField : LinkField
    {
        public TagLinkField(Field innerField)
            : base(innerField)
        {
        }

        public TagLinkField(Field innerField, string runtimeValue)
            : base(innerField, runtimeValue)
        {
        }

        public string Tag
        {
            get
            {
                return GetAttribute("tag");
            }
            set
            {
                this.SetAttribute("tag", value);
            }
        }   
    }
}

An instance of this class will magically create a XML representation of itself when saving to the General Link field, and will also parse the attributes that are defined in the XML.

Next, we need a Glass.Mapper field like Glass.Mapper.Sc.Fields.Link but with an additional property for the Tag value. This sound like an opportune time to subclass Glass.Mapper.Sc.Fields.Link and add a new property to hold the Tag value πŸ˜‰ :

using Glass.Mapper.Sc.Fields;

namespace Sitecore.Sandbox.Glass.Mapper.Sc.Fields
{
    public class TagLink : Link
    {
        public string Tag { get; set; }
    }
}

There’s nothing much in the above class except for an additional property for the Tag attribute value.

I then built the following Glass.Mapper.Sc.DataMappers.AbstractSitecoreFieldMapper for the TagLink:

using System;

using Sitecore.Data;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Links;
using Sitecore.Resources.Media;

using Glass.Mapper;
using Glass.Mapper.Sc;
using Glass.Mapper.Sc.Configuration;
using Glass.Mapper.Sc.DataMappers;
using Glass.Mapper.Sc.Fields;
using Utilities = Glass.Mapper.Sc.Utilities;

using Sitecore.Sandbox.Data.Fields;
using Sitecore.Sandbox.Glass.Mapper.Sc.Fields;

namespace Sitecore.Sandbox.Glass.Mapper.Sc.DataMappers
{
    public class SitecoreFieldTagLinkMapper : AbstractSitecoreFieldMapper
    {
        private AbstractSitecoreFieldMapper InnerLinkMapper { get; set;}

        public SitecoreFieldTagLinkMapper()
            : this(new SitecoreFieldLinkMapper(), typeof(TagLink))
        {
        }

        public SitecoreFieldTagLinkMapper(AbstractSitecoreFieldMapper innerLinkMapper, Type linkType)
            : base(linkType)
        {
            SetInnerLinkMapper(innerLinkMapper);
        }

        private void SetInnerLinkMapper(AbstractSitecoreFieldMapper innerLinkMapper)
        {
            Assert.ArgumentNotNull(innerLinkMapper, "innerLinkMapper");
            InnerLinkMapper = innerLinkMapper;
        }
        
        public override string SetFieldValue(object value, SitecoreFieldConfiguration config, SitecoreDataMappingContext context)
        {
            return InnerLinkMapper.SetFieldValue(value, config, context);
        }
        
        public override object GetFieldValue(string fieldValue, SitecoreFieldConfiguration config, SitecoreDataMappingContext context)
        {
            return InnerLinkMapper.GetFieldValue(fieldValue, config, context);
        }

        public override object GetField(Field field, SitecoreFieldConfiguration config, SitecoreDataMappingContext context)
        {
            if (field == null || field.Value.Trim().IsNullOrEmpty())
            {
                return null;
            }

            TagLink link = new TagLink();
            TagLinkField linkField = new TagLinkField(field);
            link.Anchor = linkField.Anchor;
            link.Class = linkField.Class;
            link.Text = linkField.Text;
            link.Title = linkField.Title;
            link.Target = linkField.Target;
            link.Query = linkField.QueryString;
            link.Tag = linkField.Tag;

            switch (linkField.LinkType)
            {
                case "anchor":
                    link.Url = linkField.Anchor;
                    link.Type = LinkType.Anchor;
                    break;
                case "external":
                    link.Url = linkField.Url;
                    link.Type = LinkType.External;
                    break;
                case "mailto":
                    link.Url = linkField.Url;
                    link.Type = LinkType.MailTo;
                    break;
                case "javascript":
                    link.Url = linkField.Url;
                    link.Type = LinkType.JavaScript;
                    break;
                case "media":
                    if (linkField.TargetItem == null)
                        link.Url = string.Empty;
                    else
                    {
                        global::Sitecore.Data.Items.MediaItem media =
                            new global::Sitecore.Data.Items.MediaItem(linkField.TargetItem);
                        link.Url = global::Sitecore.Resources.Media.MediaManager.GetMediaUrl(media);
                    }
                    link.Type = LinkType.Media;
                    link.TargetId = linkField.TargetID.Guid;
                    break;
                case "internal":
                    var urlOptions = Utilities.CreateUrlOptions(config.UrlOptions);
                    link.Url = linkField.TargetItem == null ? string.Empty : LinkManager.GetItemUrl(linkField.TargetItem, urlOptions);
                    link.Type = LinkType.Internal;
                    link.TargetId = linkField.TargetID.Guid;
                    link.Text = linkField.Text.IsNullOrEmpty() ? (linkField.TargetItem == null ? string.Empty : linkField.TargetItem.DisplayName) : linkField.Text;
                    break;
                default:
                    return null;
            }

            return link;
        }

        public override void SetField(Field field, object value, SitecoreFieldConfiguration config, SitecoreDataMappingContext context)
        {
            if (field == null)
            {
                return;
            }

            TagLink link = value as TagLink;
            if(link == null)
            {
                return;
            }

            Item item = field.Item;
            TagLinkField linkField = new TagLinkField(field);
            if (link == null || link.Type == LinkType.NotSet)
            {
                linkField.Clear();
                return;
            }
            
            switch (link.Type)
            {
                case LinkType.Internal:
                    linkField.LinkType = "internal";
                    if (linkField.TargetID.Guid != link.TargetId)
                    {
                        if (link.TargetId == Guid.Empty)
                        {
                            ItemLink iLink = new ItemLink(item.Database.Name, item.ID, linkField.InnerField.ID, linkField.TargetItem.Database.Name, linkField.TargetID, linkField.TargetItem.Paths.FullPath);
                            linkField.RemoveLink(iLink);
                        }
                        else
                        {
                            ID newId = new ID(link.TargetId);
                            Item target = item.Database.GetItem(newId);
                            if (target != null)
                            {
                                linkField.TargetID = newId;
                                ItemLink nLink = new ItemLink(item.Database.Name, item.ID, linkField.InnerField.ID, target.Database.Name, target.ID, target.Paths.FullPath);
                                linkField.UpdateLink(nLink);
                                linkField.Url = LinkManager.GetItemUrl(target);
                            }
                            else throw new Exception(String.Format("No item with ID {0}. Can not update Link linkField", newId));
                        }

                    }
                    break;
                case LinkType.Media:
                    linkField.LinkType = "media";
                    if (linkField.TargetID.Guid != link.TargetId)
                    {
                        if (link.TargetId == Guid.Empty)
                        {
                            ItemLink iLink = new ItemLink(item.Database.Name, item.ID, linkField.InnerField.ID, linkField.TargetItem.Database.Name, linkField.TargetID, linkField.TargetItem.Paths.FullPath);
                            linkField.RemoveLink(iLink);
                        }
                        else
                        {
                            ID newId = new ID(link.TargetId);
                            Item target = item.Database.GetItem(newId);

                            if (target != null)
                            {
                                MediaItem media = new MediaItem(target);

                                linkField.TargetID = newId;
                                ItemLink nLink = new ItemLink(item.Database.Name, item.ID, linkField.InnerField.ID, target.Database.Name, target.ID, target.Paths.FullPath);
                                linkField.UpdateLink(nLink);
                                linkField.Url = MediaManager.GetMediaUrl(media);
                            }
                            else throw new Exception(String.Format("No item with ID {0}. Can not update Link linkField", newId));
                        }
                    }
                    break;
                case LinkType.External:
                    linkField.LinkType = "external";
                    linkField.Url = link.Url;
                    break;
                case LinkType.Anchor:
                    linkField.LinkType = "anchor";
                    linkField.Url = link.Anchor;
                    break;
                case LinkType.MailTo:
                    linkField.LinkType = "mailto";
                    linkField.Url = link.Url;
                    break;
                case LinkType.JavaScript:
                    linkField.LinkType = "javascript";
                    linkField.Url = link.Url;
                    break;
            }

            if (!link.Anchor.IsNullOrEmpty())
            {
                linkField.Anchor = link.Anchor;
            }
                
            if (!link.Class.IsNullOrEmpty())
            {
                linkField.Class = link.Class;
            }
                
            if (!link.Text.IsNullOrEmpty())
            {
                linkField.Text = link.Text;
            }
                
            if (!link.Title.IsNullOrEmpty())
            {
                linkField.Title = link.Title;
            }
                
            if (!link.Query.IsNullOrEmpty())
            {
                linkField.QueryString = link.Query;
            }
                
            if (!link.Target.IsNullOrEmpty())
            {
                linkField.Target = link.Target;
            }

            if (!link.Tag.IsNullOrEmpty())
            {
                linkField.Tag = link.Tag;
            }
        }
    }
}

Most of the code in the GetField and SetField methods above are taken from the same methods in Glass.Mapper.Sc.DataMappers.SitecoreFieldLinkMapper except for the additional lines for the TagLink.

In both methods a TagLinkField instance is created so that we can get the Tag value from the field.

The follow class is used by an <initialize> pipeline processor that configures Glass on Sitecore application start:

using Glass.Mapper.Configuration;
using Glass.Mapper.IoC;
using Glass.Mapper.Maps;
using Glass.Mapper.Sc;
using Glass.Mapper.Sc.IoC;
using Sitecore.Sandbox.DI;
using Sitecore.Sandbox.Glass.Mapper.Sc.DataMappers;
using IDependencyResolver = Glass.Mapper.Sc.IoC.IDependencyResolver;

namespace Sitecore.Sandbox.Web.Mvc.App_Start
{
    public static class GlassMapperScCustom
    {
		public static IDependencyResolver CreateResolver(){
			var config = new Config();
            DependencyResolver dependencyResolver = new DependencyResolver(config);
            AddDataMappers(dependencyResolver);
            return dependencyResolver;
		}

        private static void AddDataMappers(DependencyResolver dependencyResolver)
        {
            if(dependencyResolver == null)
            {
                return;
            }
            
            dependencyResolver.DataMapperFactory.Replace(15, () => new SitecoreFieldTagLinkMapper());
        }

		public static IConfigurationLoader[] GlassLoaders(){			
			
			/* USE THIS AREA TO ADD FLUENT CONFIGURATION LOADERS
             * 
             * If you are using Attribute Configuration or automapping/on-demand mapping you don't need to do anything!
             * 
             */

			return new IConfigurationLoader[]{};
		}
		public static void PostLoad(){
			//Remove the comments to activate CodeFist
			/* CODE FIRST START
            var dbs = Sitecore.Configuration.Factory.GetDatabases();
            foreach (var db in dbs)
            {
                var provider = db.GetDataProviders().FirstOrDefault(x => x is GlassDataProvider) as GlassDataProvider;
                if (provider != null)
                {
                    using (new SecurityDisabler())
                    {
                        provider.Initialise(db);
                    }
                }
            }
             * CODE FIRST END
             */
		}
		public static void AddMaps(IConfigFactory<IGlassMap> mapsConfigFactory)
        {
			// Add maps here
            ContainerManager containerManager = new ContainerManager();
            foreach (var map in containerManager.Container.GetAllInstances<IGlassMap>())
            {
                mapsConfigFactory.Add(() => map);
            }
        }
    }
}

I added the AddDataMappers method to it. This method replaces the “out of the box” SitecoreFieldLinkMapper with a SitecoreFieldTagLinkMapper instance — the “out of the box” SitecoreFieldLinkMapper lives in the 15th place in the index (I determined this using .NET Reflector on one of the Glass.Mapper assemblies).

Now that the above things are squared away, we need a way to add the Tag attribute with its value to the attributes collection that is passed to Glass so that it can transform this into rendered HTML. I decided to define an interface for classes that do that:

using System;
using System.Linq.Expressions;

namespace Sitecore.Sandbox.Glass.Mapper.Sc.Attributes
{
    public interface ICustomAttributesAdder
    {
        object AddTagAttribute<T>(T model, Expression<Func<T, object>> field, object attributes);
    }
}

Classes that implement the above interface will take in a Glass Model instance, the field we are rendering, and the existing collection of attributes that are to be rendered by Glass.

The following class implements the above interface:

using System;
using System.Collections.Specialized;
using System.Linq.Expressions;

using Sitecore.Configuration;
using Sitecore.Diagnostics;

using Glass.Mapper.Sc;

using Sitecore.Sandbox.Glass.Mapper.Sc.Fields;

namespace Sitecore.Sandbox.Glass.Mapper.Sc.Attributes
{
    public class CustomAttributesAdder : ICustomAttributesAdder
    {
        private static readonly Lazy<ICustomAttributesAdder> current = new Lazy<ICustomAttributesAdder>(() => { return GetCustomAttributesAdder(); });

        public static ICustomAttributesAdder Current
        {
            get
            {
                return current.Value;
            }
        }

        public CustomAttributesAdder()
        {
        }

        public virtual object AddTagAttribute<T>(T model, Expression<Func<T, object>> field, object attributes)
        {
            TagLink tagLink = field.Compile()(model) as TagLink;
            if (tagLink == null || string.IsNullOrWhiteSpace(tagLink.Tag))
            {
                return attributes;
            }

            NameValueCollection attributesCollection;
            if (attributes is NameValueCollection)
            {
                attributesCollection = attributes as NameValueCollection;
            }
            else
            {
                attributesCollection = Utilities.GetPropertiesCollection(attributes, true, true);
            }

            attributesCollection.Add("tag", tagLink.Tag);
            return attributesCollection;
        }

        private static ICustomAttributesAdder GetCustomAttributesAdder()
        {
            ICustomAttributesAdder adder = Factory.CreateObject("sandbox.Glass.Mvc/customAttributesAdder", true) as ICustomAttributesAdder;
            Assert.IsNotNull(adder, "Be sure the configuration for CustomAttributesAdder is correct in utilities/customAttributesAdder of your Sitecore configuration!");
            return adder;
        }
    }
}

The AddTagAttribute method above first determines if the field passed to it is a TagLink. If it’s not, it just returns the attribute collection unaltered.

The method also determines if there is a Tag value. If there is no Tag value, it just returns the attribute collection “as is” since we don’t want to render an attribute with an empty value.

If the field is a TagLink and there is a Tag value, the method adds the Tag attribute name and value into the attributes collection that was passed to it, and then returns the modified NameValueCollection instance.

I decided to use the Singleton Pattern for the above class — the type of the class is defined in Sitecore configuration (see the patch configuration file further down in this post — since I am going to reuse it in my next post where I’ll show another approach on rendering a Tag attribute using Glass.Mapper, and I had built both approaches simultaneously (I decided to break these into separate blog posts since this one by itself will already be quite long).

Next, I built a new subclass of Glass.Mapper.Sc.Web.Mvc.GlassView so that I can intercept attributes collection passed to the RenderLink methods on the “out of the box” Glass.Mapper.Sc.Web.Mvc.GlassView (our Razor views will have to inherit from the class below in order for everything to work):

using System;
using System.Collections.Specialized;
using System.Linq.Expressions;
using System.Web;

using Sitecore.Configuration;
using Sitecore.Diagnostics;

using Sitecore.Sandbox.Glass.Mapper.Sc.Attributes;
using Sitecore.Sandbox.Glass.Mapper.Sc.Fields;

using Glass.Mapper.Sc;
using Glass.Mapper.Sc.Fields;
using Glass.Mapper.Sc.Web.Mvc;

namespace Sitecore.Sandbox.Glass.Mapper.Sc.Web.Mvc
{
    public abstract class SandboxGlassView<TModel> : GlassView<TModel> where TModel : class
    {
        private ICustomAttributesAdder attributesAdder;
        private ICustomAttributesAdder AttributesAdder 
        { 
            get
            {
                if (attributesAdder == null)
                {
                    attributesAdder = GetCustomAttributesAdder();
                }

                return attributesAdder;
            }
        }

        public override RenderingResult BeginRenderLink<T>(T model, Expression<Func<T, object>> field, object attributes = null, bool isEditable = false)
        {
            object attributesModified = AttributesAdder.AddTagAttribute(model, field, attributes);
            return base.BeginRenderLink<T>(model, field, attributesModified, isEditable);
        }

        public override HtmlString RenderLink(Expression<Func<TModel, object>> field, object attributes = null, bool isEditable = false, string contents = null)
        {
            object attributesModified = AttributesAdder.AddTagAttribute(Model, field, attributes);
            return base.RenderLink(field, attributesModified, isEditable, contents);
        }

        public override HtmlString RenderLink<T>(T model, Expression<Func<T, object>> field, object attributes = null, bool isEditable = false, string contents = null)
        {
            object attributesModified = AttributesAdder.AddTagAttribute(model, field, attributes);
            return base.RenderLink<T>(model, field, attributesModified, isEditable, contents);
        }

        protected virtual ICustomAttributesAdder GetCustomAttributesAdder()
        {
            return CustomAttributesAdder.Current;
        }
    }
}

I used the CustomAttributesAdder Singleton instance to add the Tag attribute name and value into the passed attributes collection, and then pass it on to the base class to do its magic.

I then strung everything together using the following Sitecore patch configuration file (Note: lots of stuff in this configuration file come from my previous post so I advise having a look at it):

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <controlSources>
      <source mode="on" namespace="Sitecore.Sandbox.Shell.Applications.ContentEditor" assembly="Sitecore.Sandbox" prefix="sandbox-content"/>
    </controlSources>
    <fieldTypes>
      <fieldType name="General Link">
        <patch:attribute name="type">Sitecore.Sandbox.Data.Fields.TagLinkField, Sitecore.Sandbox</patch:attribute>
      </fieldType>
      <fieldType name="General Link with Search">
        <patch:attribute name="type">Sitecore.Sandbox.Data.Fields.TagLinkField, Sitecore.Sandbox</patch:attribute>
      </fieldType>
      <fieldType name="link">
        <patch:attribute name="type">Sitecore.Sandbox.Data.Fields.TagLinkField, Sitecore.Sandbox</patch:attribute>
        </fieldType>
    </fieldTypes>
    <pipelines>
      <dialogInfo>
        <processor type="Sitecore.Sandbox.Pipelines.DialogInfo.SetDialogInfo, Sitecore.Sandbox">
          <ParameterNameAttributeName>name</ParameterNameAttributeName>
          <ParameterValueAttributeName>value</ParameterValueAttributeName>
          <Message>contentlink:externallink</Message>
          <Url>/sitecore/shell/Applications/Dialogs/External link.aspx</Url>
          <parameters hint="raw:AddParameter">
            <parameter name="height" value="300" />
          </parameters>
        </processor>
      </dialogInfo>
      <renderField>
        <processor patch:after="processor[@type='Sitecore.Pipelines.RenderField.GetInternalLinkFieldValue, Sitecore.Kernel']" 
                   type="Sitecore.Sandbox.Pipelines.RenderField.SetTagAttributeOnLink, Sitecore.Sandbox">
          <TagXmlAttributeName>tag</TagXmlAttributeName>
          <TagAttributeName>tag</TagAttributeName>
          <BeginningHtml>&lt;a </BeginningHtml>
        </processor>  
      </renderField>
    </pipelines>
    <sandbox.Glass.Mvc>
      <customAttributesAdder type="Sitecore.Sandbox.Glass.Mapper.Sc.Attributes.CustomAttributesAdder, Sitecore.Sandbox" singleInstance="true" />
    </sandbox.Glass.Mvc>
  </sitecore>
</configuration>

Let’s see this in action!

For testing, I created the following interface for a model for my Sitecore instance’s Home Item (we are using fields defined on the Sample Item template):

using Glass.Mapper.Sc.Configuration.Attributes;

using Sitecore.Sandbox.Glass.Mapper.Sc.Fields;

namespace Sitecore.Sandbox.Models.ViewModels
{
    public interface ISampleItem
    {
        string Title { get; set; }

        string Text { get; set; }

        [SitecoreField("Link One")]
        TagLink LinkOne { get; set; }

        [SitecoreField("Link Two")]
        TagLink LinkTwo { get; set; }
    }
}

Model instances of the above interface will have two TagLink instances on them.

Next, I built the following Glass.Mapper.Sc.Maps.SitecoreGlassMap for my model interface defined above:

using Glass.Mapper.Sc.Maps;

using Sitecore.Sandbox.Models.ViewModels;

namespace Sitecore.Sandbox.Mappings.ViewModels.SampleItem
{
    public class SampleItemMap : SitecoreGlassMap<ISampleItem>
    {
        public override void Configure()
        {
            Map(x =>
            {
                x.AutoMap();
            });
        }
    }
}

Glass.Mapper will create an instance of the above class which will magically create a concrete instance of a class that implements the ISampleItem interface.

We need to plug the above into the front-end. I did this using the following Razor view:

@inherits Sitecore.Sandbox.Glass.Mapper.Sc.Web.Mvc.SandboxGlassView<Sitecore.Sandbox.Models.ViewModels.ISampleItem>
@using Glass.Mapper.Sc.Web.Mvc
@using Sitecore.Sandbox.Glass.Mapper.Sc.Web.Mvc

<div id="Content">
    <div id="LeftContent">
    </div>
    <div id="CenterColumn">
        <div id="Header">
            <img src="/~/media/Default Website/sc_logo.png" id="scLogo" />
        </div>
        <h1 class="contentTitle">
            @Editable(x => x.Title)
        </h1>
        <div class="contentDescription">
            @Editable(x => x.Text)
            <div>
                @RenderLink(x => x.LinkOne)
            </div>
            <div>
                @RenderLink(x => x.LinkTwo)
            </div>
        </div>
    </div>
</div>

The above Razor file inherits from SandboxGlassView so that it can access the RenderLink methods that were defined in the SandboxGlassView class.

I then ensured I had some tag attributes set on some General Link fields on my home Item (I kept these the same as my last blog post):

tag-attributes-raw-values

After doing a build and navigating to my homepage Item, I saw the following in the rendered HTML:

tag-attributes-rendered

As you can see, it worked magically. πŸ™‚

magic

If you have any questions/comments/thoughts on the above, please share in a comment.

Also, I would like to thank Sitecore MVP Nat Mann for helping me on some of the bits above. Without your help Nat, there would be no solution and no blog post.

Until next time, keep on Sitecore-ing. πŸ˜€

Yet Another Way to Store Data Outside of the Sitecore Experience Platform

Last February, Sitecore MVP Nick Wesselman shared an awesome blog post on storing data outside of the Sitecore® Experience Platform™ using the NHibernate ORM framework — if you haven’t had a chance to read this, I strongly recommend that you do — which is complete magic, and a simple solution where you don’t have to worry about spinning up your own database tables for storing information.

But, let’s suppose you aren’t allowed to use an ORM like NHibernate in your solution for some reason — I won’t go into potential reasons but let’s make pretend there is one — and you have to find a way to store Sitecore specific information but don’t want to go through the trouble of spinning up a new Sitecore database due to the overhead involved. What can you do?

Well, you can still store information in a non-Sitecore database using the Sitecore API. The following “proof of concept” does this, and is basically modeled after how Sitecore manages data stored in the IDTable and Links Database.

The code in the following “proof of concept” adds/retrieves/deletes alternative URLs for Sitecore Items in the following custom database table:

itemurls-sql-table

I’m not going to talk much about the SQL table or SQL statements used in this “proof of concept” since it’s beyond the scope of this post.

Of course we all love things that are performant — and our clients love when we make things performant — so I decided to start off my solution using the following adapter — this includes the interface and concrete class — for an instance of Sitecore.Caching.Cache (this lives in Sitecore.Kernel.dll):

using System;
using System.Collections;

using Sitecore.Caching;
using Sitecore.Data;
using Sitecore.Diagnostics.PerformanceCounters;

namespace Sitecore.Sandbox.Caching
{
    public interface ICacheProvider
    {
        bool CacheWriteEnabled { get; set; }

        int Count { get; }

        CachePriority DefaultPriority { get; set; }

        bool Enabled { get; set; }

        AmountPerSecondCounter ExternalCacheClearingsCounter { get; set; }

        ID Id { get; }

        long MaxSize { get; set; }

        string Name { get; }

        long RemainingSpace { get; }

        bool Scavengable { get; set; }

        long Size { get; }

        object SyncRoot { get; }

        object this[object key] { get; }

        void Add(ID key, ICacheable data);

        void Add(ID key, string value);

        void Add(string key, ICacheable data);

        void Add(string key, ID value);

        Cache.CacheEntry Add(string key, string data);

        void Add(ID key, object data, long dataLength);

        void Add(object key, object data, long dataLength);

        void Add(string key, object data, long dataLength);

        void Add(object key, object data, long dataLength, DateTime absoluteExpiration);

        void Add(object key, object data, long dataLength, TimeSpan slidingExpiration);

        Cache.CacheEntry Add(string key, object data, long dataLength, DateTime absoluteExpiration);

        void Add(string key, object data, long dataLength, EventHandler<EntryRemovedEventArgs> removedHandler);

        Cache.CacheEntry Add(string key, object data, long dataLength, TimeSpan slidingExpiration);

        void Add(object key, object data, long dataLength, TimeSpan slidingExpiration, DateTime absoluteExpiration);

        void Clear();

        bool ContainsKey(ID key);

        bool ContainsKey(object key);

        ArrayList GetCacheKeys();

        ArrayList GetCacheKeys(string keyPrefix);

        Cache.CacheEntry GetEntry(object key, bool updateAccessed);

        object GetValue(object key);

        void Remove(object key);

        void Remove<TKey>(Predicate<TKey> predicate);

        void RemoveKeysContaining(string value);

        void RemovePrefix(string keyPrefix);

        void Scavenge();
    }
}
using System;
using System.Collections;

using Sitecore.Caching;
using Sitecore.Data;
using Sitecore.Diagnostics;
using Sitecore.Diagnostics.PerformanceCounters;

namespace Sitecore.Sandbox.Caching
{
    public class CacheProvider : ICacheProvider
    {
        private Cache Cache { get; set; }

        public CacheProvider(string cacheName, string cacheSize)
        {
            Assert.ArgumentNotNullOrEmpty(cacheName, "cacheName");
            Assert.ArgumentNotNullOrEmpty(cacheSize, "cacheSize");
            Cache = new Cache(cacheName, StringUtil.ParseSizeString(cacheSize));
        }

        public bool CacheWriteEnabled 
        {
            get
            {
                return Cache.CacheWriteEnabled;
            }
            set
            {
                Cache.CacheWriteEnabled = value;
            }
        }

        public int Count 
        {
            get
            {
                return Cache.Count;
            }
        }

        public CachePriority DefaultPriority 
        {
            get
            {
                return Cache.DefaultPriority;
            }
            set
            {
                Cache.DefaultPriority = value;
            }
        }

        public bool Enabled 
        {
            get
            {
                return Cache.Enabled;
            }
            set
            {
                Cache.Enabled = value;
            }
        }

        public AmountPerSecondCounter ExternalCacheClearingsCounter 
        {
            get
            {
                return Cache.ExternalCacheClearingsCounter;
            }
            set
            {
                Cache.ExternalCacheClearingsCounter = value;
            }
        }

        public ID Id 
        {
            get
            {
                return Cache.Id;
            }
        }

        public long MaxSize 
        {
            get
            {
                return Cache.MaxSize;
            }
            set
            {
                Cache.MaxSize = value;
            }
        }

        public string Name 
        {
            get
            {
                return Cache.Name;
            }
        }

        public long RemainingSpace
        {
            get
            {
                return Cache.RemainingSpace;
            }
        }

        public bool Scavengable 
        {
            get
            {
                return Cache.Scavengable;
            }
            set
            {
                Cache.Scavengable = value;
            }
        }

        public long Size
        {
            get
            {
                return Cache.Size;
            }
        }

        public object SyncRoot
        {
            get
            {
                return Cache.SyncRoot;
            }
        }

        public object this[object key] 
        { 
            get
            {
                return Cache[key];
            } 
        }

        public void Add(ID key, ICacheable data)
        {
            Cache.Add(key, data);
        }

        public void Add(ID key, string value)
        {
            Cache.Add(key, value);
        }

        public void Add(string key, ICacheable data)
        {
            Cache.Add(key, data);
        }

        public void Add(string key, ID value)
        {
            Cache.Add(key, value);
        }

        public Cache.CacheEntry Add(string key, string data)
        {
            return Cache.Add(key, data);
        }

        public void Add(ID key, object data, long dataLength)
        {
            Cache.Add(key, data, dataLength);
        }

        public void Add(object key, object data, long dataLength)
        {
            Cache.Add(key, data, dataLength);
        }

        public void Add(string key, object data, long dataLength)
        {
            Cache.Add(key, data, dataLength);
        }

        public void Add(object key, object data, long dataLength, DateTime absoluteExpiration)
        {
            Cache.Add(key, data, dataLength, absoluteExpiration);
        }

        public void Add(object key, object data, long dataLength, TimeSpan slidingExpiration)
        {
            Cache.Add(key, data, dataLength, slidingExpiration);
        }

        public Cache.CacheEntry Add(string key, object data, long dataLength, DateTime absoluteExpiration)
        {
            return Cache.Add(key, data, dataLength, absoluteExpiration);
        }

        public void Add(string key, object data, long dataLength, EventHandler<EntryRemovedEventArgs> removedHandler)
        {
            Cache.Add(key, data, dataLength, removedHandler);
        }

        public Cache.CacheEntry Add(string key, object data, long dataLength, TimeSpan slidingExpiration)
        {
            return Cache.Add(key, data, dataLength, slidingExpiration);
        }

        public void Add(object key, object data, long dataLength, TimeSpan slidingExpiration, DateTime absoluteExpiration)
        {
            Cache.Add(key, data, dataLength, slidingExpiration, absoluteExpiration);
        }

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

        public bool ContainsKey(ID key)
        {
            return Cache.ContainsKey(key);
        }

        public bool ContainsKey(object key)
        {
            return Cache.ContainsKey(key);
        }

        public ArrayList GetCacheKeys()
        {
            return Cache.GetCacheKeys();
        }

        public ArrayList GetCacheKeys(string keyPrefix)
        {
            return Cache.GetCacheKeys(keyPrefix);
        }

        public Cache.CacheEntry GetEntry(object key, bool updateAccessed)
        {
            return Cache.GetEntry(key, updateAccessed);
        }
        
        public object GetValue(object key)
        {
            return Cache.GetValue(key);
        }

        public void Remove(object key)
        {
            Cache.Remove(key);
        }

        public void Remove<TKey>(Predicate<TKey> predicate)
        {
            Cache.Remove<TKey>(predicate);
        }

        public void RemoveKeysContaining(string value)
        {
            Cache.RemoveKeysContaining(value);
        }

        public void RemovePrefix(string keyPrefix)
        {
            Cache.RemovePrefix(keyPrefix);
        }

        public void Scavenge()
        {
            Cache.Scavenge();
        }
    }
}

I’m not going to talk about the above interface or class since it just wraps Sitecore.Caching.Cache, and there isn’t much to talk about here.

Next, I spun up the following class that represents an entry in our custom SQL table:

using System;

using Sitecore.Caching;
using Sitecore.Data;
using Sitecore.Reflection;

using Newtonsoft.Json;

namespace Sitecore.Sandbox.Data.Providers.ItemUrls
{
    public class ItemUrlEntry : ICacheable, ICloneable
    {
        public ID ItemID { get; set; }

        public string Site { get; set; }
        
        public string Database { get; set; }
        
        public string Url { get; set; }

        bool cacheable;
        bool ICacheable.Cacheable
        {
            get
            {
                return cacheable;
            }
            set
            {
                cacheable = value;
            }
        }

        bool ICacheable.Immutable
        {
            get
            {
                return true;
            }
        }

        event DataLengthChangedDelegate ICacheable.DataLengthChanged
        {
            add
            {
            }
            remove
            {
            }
        }

        long ICacheable.GetDataLength()
        {
            return TypeUtil.SizeOfID()
                    + TypeUtil.SizeOfString(Site) 
                    + TypeUtil.SizeOfString(Database) 
                    + TypeUtil.SizeOfString(Url);
        }

        public object Clone()
        {
            return new ItemUrlEntry { ItemID = ItemID, Site = Site, Database = Database, Url = Url };
        }

        public override string ToString()
        {
            return JsonConvert.SerializeObject(this);
        }
    }
}

Entries can contain the ID of the Sitecore Item; the specific site we are storing this url for; and the target Database.

You’ll notice I’ve implemented the Sitecore.Caching.ICacheable interface. I’ve done this so I can store entries in cache for performance. I’m not going to go much into the details of how this works since there isn’t much to point out.

I also override the ToString() method for testing purposes. You’ll see this in action later on when we test this together.

Next, we need some sort of provider to manage these entries. I’ve defined the following interface for such a provider:

using System.Collections.Generic;

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Sites;

namespace Sitecore.Sandbox.Data.Providers.ItemUrls
{
    public interface IItemUrlsProvider
    {
        void AddEntry(ItemUrlEntry entry);
        
        void RemoveEntry(ItemUrlEntry entry);

        Item GetItem(ItemUrlEntry entry);

        ItemUrlEntry GetEntry(ItemUrlEntry entry);

        IEnumerable<ItemUrlEntry> GetAllEntries();
    }
}

IItemUrlsProviders should have the ability to add/remove/retrieve entries. They should also offer the ability to get all entries — I need this for testing later on in this post.

Plus, as a “nice to have”, these providers should return a Sitecore Item for a given entry. Such would be useful when retrieving and setting the context Sitecore Item via a custom Item Resolver (you would typically have an <httpRequestBegin> pipeline processor that does this).

I then created the following class that implements the IItemUrlsProvider interface defined above. This class is specific to adding/removing/retrieving entries from a custom SQL database:

using System;
using System.Collections.Generic;

using Sitecore.Caching;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.DataProviders.Sql;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;

using Sitecore.Sandbox.Caching;

namespace Sitecore.Sandbox.Data.Providers.ItemUrls.SqlServer
{
    public class SqlServerItemUrlsProvider : IItemUrlsProvider
    {
        private SqlDataApi SqlDataApi { get; set; }

        protected ICacheProvider CacheProvider { get; private set; }

        protected string CachePrefix { get; private set; }

        public SqlServerItemUrlsProvider(SqlDataApi sqlDataApi, ICacheProvider cacheProvider, string cachePrefix)
        {
            Assert.ArgumentNotNull(sqlDataApi, "sqlDataApi");
            Assert.ArgumentNotNull(cacheProvider, "cacheProvider");
            Assert.ArgumentNotNullOrEmpty(cachePrefix, "cachePrefix");
            SqlDataApi = sqlDataApi;
            CacheProvider = cacheProvider;
            CachePrefix = cachePrefix;
        }

        public void AddEntry(ItemUrlEntry entry)
        {
            Assert.ArgumentNotNull(entry, "entry");
            Assert.ArgumentCondition(!ID.IsNullOrEmpty(entry.ItemID), "entry.ItemID", "entry.ItemID cannot be null or empty");
            Assert.ArgumentNotNullOrEmpty(entry.Site, "entry.Site");
            Assert.ArgumentNotNullOrEmpty(entry.Database, "entry.Database");
            Assert.ArgumentNotNullOrEmpty(entry.Url, "entry.Url");
            const string addEntrySql = "INSERT INTO {0}ItemUrls{1} ( {0}ItemID{1}, {0}Site{1}, {0}Database{1}, {0}Url{1} ) VALUES ( {2}itemID{3}, {2}site{3}, {2}database{3}, {2}url{3} )";
            var success = Factory.GetRetryer().Execute(() =>
            {
                object[] parameters = new object[] { "itemID", entry.ItemID, "site", entry.Site, "database", entry.Database, "url", entry.Url };
                return SqlDataApi.Execute(addEntrySql, parameters) > 0;
            });

            if (success)
            {
                AddToCache(entry);
            }
        }

        public void RemoveEntry(ItemUrlEntry entry)
        {
            const string deleteEntrySql = "DELETE FROM {0}ItemUrls{1} WHERE {0}Site{1} = {2}site{3} AND {0}Database{1} = {2}database{3} AND {0}Url{1} = {2}url{3}";
            var success = Factory.GetRetryer().Execute(() =>
            {
                object[] parameters = new object[] { "site", entry.Site, "database", entry.Database, "url", entry.Url };
                return SqlDataApi.Execute(deleteEntrySql, parameters) > 0;
            });

            if (success)
            {
                RemoveFromCache(entry);
            }
        }

        public Item GetItem(ItemUrlEntry entry)
        {
            ItemUrlEntry foundEntry = GetEntry(entry);
            if(foundEntry == null)
            {
                return null;
            }

            Database database = Factory.GetDatabase(foundEntry.Database);
            if(database == null)
            {
                return null;
            }

            try
            {
                return database.Items[foundEntry.ItemID];
            }
            catch(Exception ex)
            {
                Log.Error(ToString(), ex, this);
            }

            return null;
        }

        public ItemUrlEntry GetEntry(ItemUrlEntry entry)
        {
            ItemUrlEntry foundEntry = GetFromCache(entry);
            if (foundEntry != null)
            {
                return foundEntry;
            }

            const string getEntrySql = "SELECT {0}ItemID{1} FROM {0}ItemUrls{1} WHERE {2}Site = {2}site{3} AND {2}Database{3} = {2}database{3} AND {0}Url{1} = {2}url{3}";
            object[] parameters = new object[] { "site", entry.Site, "database", entry.Database, "url", entry.Url };
            using (DataProviderReader reader = SqlDataApi.CreateReader(getEntrySql, parameters))
            {
                if (!reader.Read())
                {
                    return null;
                }

                ID itemID = ID.Parse(SqlDataApi.GetGuid(0, reader));
                if (ID.IsNullOrEmpty(itemID))
                {
                    return null;
                }

                foundEntry = entry.Clone() as ItemUrlEntry;
                foundEntry.ItemID = itemID;
                AddToCache(entry);
                return foundEntry;
            }
        }

        public IEnumerable<ItemUrlEntry> GetAllEntries()
        {
            const string getAllEntriesSql = "SELECT {0}ItemID{1}, {0}Site{1}, {0}Database{1}, {0}Url{1} FROM {0}ItemUrls{1}";
            IList<ItemUrlEntry> entries = new List<ItemUrlEntry>();
            using (DataProviderReader reader = SqlDataApi.CreateReader(getAllEntriesSql, new object[0]))
            {
                while(reader.Read())
                {
                    ID itemID = ID.Parse(SqlDataApi.GetGuid(0, reader));
                    if (!ID.IsNullOrEmpty(itemID))
                    {
                        entries.Add
                        (
                            new ItemUrlEntry 
                            {
                                ItemID = itemID, 
                                Site = SqlDataApi.GetString(1, reader), 
                                Database = SqlDataApi.GetString(2, reader), 
                                Url =  SqlDataApi.GetString(3, reader)
                            }
                        );
                    } 
                }
            }

            return entries;
        }

        protected virtual void AddToCache(ItemUrlEntry entry)
        {
            CacheProvider.Add(GetCacheKey(entry), entry);
        }

        protected virtual void RemoveFromCache(ItemUrlEntry entry)
        {
            CacheProvider.Remove(GetCacheKey(entry));
        }

        protected virtual ItemUrlEntry GetFromCache(ItemUrlEntry entry)
        {
            return CacheProvider[GetCacheKey(entry)] as ItemUrlEntry;
        }

        protected virtual string GetCacheKey(ItemUrlEntry entry)
        {
            Assert.ArgumentNotNull(entry, "entry");
            Assert.ArgumentNotNull(entry.Site, "entry.Site");
            Assert.ArgumentNotNull(entry.Database, "entry.Database");
            Assert.ArgumentNotNull(entry.Url, "entry.Url");
            return string.Join("#", CachePrefix, entry.Site, entry.Database, entry.Url);
        }
    }
}

Sitecore.Data.DataProviders.Sql.SqlDataApi and ICacheProvider instances along with a cache prefix are injected into the class instance’s constructor using the Sitecore Configuration Factory (you’ll get a better idea of how this happens when you have a look at the patch configuration file towards the bottom of this post). These are saved to properties on the class instance so they can be leveraged by the methods on the class.

One thing I would like to point out is the Sitecore.Data.DataProviders.Sql.SqlDataApi class is an abstraction — it’s an abstract class that is subclassed by Sitecore.Data.SqlServer.SqlServerDataApi in Sitecore.Kernel.dll. This concrete class does most of the leg work on talking to the SQL Server database, and we just utilize methods on it for adding/deleting/removing entries.

The AddEntry() method delegates the database saving operation to the Sitecore.Data.DataProviders.Sql.SqlDataApi instance, and then uses the ICacheProvider instance for storing the entry in cache.

The RemoveEntry() method also leverages the Sitecore.Data.DataProviders.Sql.SqlDataApi instance for deleting the entry from the database, and then removes the entry from cache via the ICacheProvider instance.

The GetEntry() method does exactly what you think it does. It tries to get the entry first from cache via the ICacheProvider instance and then the database via the Sitecore.Data.DataProviders.Sql.SqlDataApi instance if the entry was not found in cache. If the Item was not in cache but was in the database, the GetEntry() method then saves the entry to cache.

I then created the following Singleton for testing:

using System;
using System.Collections.Generic;

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

namespace Sitecore.Sandbox.Data.Providers.ItemUrls
{
    public class ItemUrlsProvider : IItemUrlsProvider
    {
        private static readonly Lazy<IItemUrlsProvider> lazyInstance = new Lazy<IItemUrlsProvider>(() => new ItemUrlsProvider());

        public static IItemUrlsProvider Current { get { return lazyInstance.Value; } }

        private IItemUrlsProvider InnerProvider { get; set; }

        private ItemUrlsProvider()
        {
            InnerProvider = GetInnerProvider();
        }

        public void AddEntry(ItemUrlEntry entry)
        {
            InnerProvider.AddEntry(entry);
        }

        public void RemoveEntry(ItemUrlEntry entry)
        {
            InnerProvider.RemoveEntry(entry);
        }

        public Item GetItem(ItemUrlEntry entry)
        {
            return InnerProvider.GetItem(entry);
        }

        public ItemUrlEntry GetEntry(ItemUrlEntry entry)
        {
            return InnerProvider.GetEntry(entry);
        }

        public IEnumerable<ItemUrlEntry> GetAllEntries()
        {
            return InnerProvider.GetAllEntries();
        }

        protected virtual IItemUrlsProvider GetInnerProvider()
        {
            IItemUrlsProvider provider = Factory.CreateObject("itemUrlsProvider", true) as IItemUrlsProvider;
            Assert.IsNotNull(provider, "itemUrlsProvider must be set in configuration!");
            return provider;
        }
    }
}

The Singleton above basically decorates the IItemUrlsProvider instance defined in Sitecore configuration — see the configuration file below — and delegates method calls to it.

I then wired everything together using the following patch configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <itemUrlsProvider id="custom" type="Sitecore.Sandbox.Data.Providers.ItemUrls.$(database).$(database)ItemUrlsProvider, Sitecore.Sandbox" singleInstance="true">
      <param type="Sitecore.Data.$(database).$(database)DataApi, Sitecore.Kernel" desc="sqlDataApi">
        <param connectionStringName="$(id)"/>
      </param>
      <param type="Sitecore.Sandbox.Caching.CacheProvider, Sitecore.Sandbox" desc="cacheProvider">
        <param desc="cacheName">[ItemUrls]</param>
        <param desc="cacheSize">500KB</param>
      </param>
      <param desc="cachePrefix">ItemUrlsEntry</param>
    </itemUrlsProvider>
  </sitecore>
</configuration>

For testing, I whipped up a standalone ASP.NET Web Form (yes, there are more elegant ways to do this but it’s Sunday so cut me some slack πŸ˜‰ ):

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

using Sitecore.Data.Items;

using Sitecore.Sandbox.Data.Providers.ItemUrls;

namespace Sitecore.Sandbox.Web.tests
{
    public partial class ItemUrlsProviderTest : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            IItemUrlsProvider provider = ItemUrlsProvider.Current;
            Item home = Sitecore.Context.Database.GetItem("/sitecore/content/home");
            StringBuilder output = new StringBuilder();
            
            ItemUrlEntry firstEntry = new ItemUrlEntry { ItemID = home.ID, Site = Sitecore.Context.Site.Name, Database = Sitecore.Context.Database.Name, Url = "/this/does/not/exist" };
            output.AppendFormat("Adding {0} as an entry.<br />", firstEntry);
            provider.AddEntry(firstEntry);

            ItemUrlEntry secondEntry = new ItemUrlEntry { ItemID = home.ID, Site = Sitecore.Context.Site.Name, Database = Sitecore.Context.Database.Name, Url = "/fake/url" };
            output.AppendFormat("Adding {0} as an entry.<br />", secondEntry);
            provider.AddEntry(secondEntry);

            ItemUrlEntry thirdEntry = new ItemUrlEntry { ItemID = home.ID, Site = Sitecore.Context.Site.Name, Database = Sitecore.Context.Database.Name, Url = "/another/fake/url" };
            output.AppendFormat("Adding {0} as an entry.<hr />", thirdEntry);
            provider.AddEntry(thirdEntry);

            ItemUrlEntry fourthEntry = new ItemUrlEntry { ItemID = home.ID, Site = Sitecore.Context.Site.Name, Database = Sitecore.Context.Database.Name, Url = "/blah/blah/blah" };
            output.AppendFormat("Adding {0} as an entry.<hr />", fourthEntry);
            provider.AddEntry(fourthEntry);

            ItemUrlEntry fifthEntry = new ItemUrlEntry { ItemID = home.ID, Site = Sitecore.Context.Site.Name, Database = Sitecore.Context.Database.Name, Url = "/i/am/a/url" };
            output.AppendFormat("Adding {0} as an entry.<hr />", fifthEntry);
            provider.AddEntry(fifthEntry);

            output.AppendFormat("Current saved entries:<br /><br />{0}<hr />", string.Join("<br />", provider.GetAllEntries().Select(entry => entry.ToString())));

            output.AppendFormat("Removing entry {0}.<br /><br />", firstEntry.ToString());
            provider.RemoveEntry(firstEntry);

            output.AppendFormat("Current saved entries:<br /><br />{0}", string.Join("<br />", provider.GetAllEntries().Select(entry => entry.ToString())));
            litResults.Text = output.ToString();
        }
    }
}

The test above adds five entries, and then deletes one. It also outputs what’s in the database after specific operations.

After doing a build, I pulled up the above Web Form in my browser and saw this once the page was done rendering:

ItemUrlsProviderTest

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

Until next time, have a Sitecoredatalicious day!

Make Incompatible Class Interfaces Work Together using the Adapter Pattern in Sitecore

This post is a continuation of a series of blog posts I’m putting together around using design patterns in Sitecore, and will share a “proof of concept” on employing the Adapter pattern — a structural pattern used when you need classes of different interfaces to work together. In other words, you need one class’ interface to “adapt” to another.

Believe it or not, most developers — and hopefully most reading this post — are already quite familiar with the Adapter pattern even if it’s not recognizable by name.

How so?

Well, I don’t know about you but I spend a lot of time making code from different APIs work together. I typically have to do this when making use of a third-party library that I cannot change, and usually do this by having one class “wrap” another and its methods. Commonly, the Adapter pattern is known as a “wrapper”.

I showcased the following “proof of concept” during my presentation at SUGCON Europe 2015. This code flips images upside down after they are uploaded to the Media Library — yeah, I know, pretty useful, right? πŸ˜‰ Keep in mind this code is for educational purposes only, and serves no utility in any practical sense in your Sitecore solutions — if you do have a business need for flipping images upside down after uploading them to the Media Library, please share in a comment.

I first started off with the following interface:

using Sitecore.Data.Items;

namespace Sitecore.Sandbox.Resources.Media
{
    public interface IMediaImageFlipper
    {
        MediaItem MediaItem { get; set; }

        void Flip();
    }
}

Classes that implement the interface above basically flip images within Sitecore.Data.Items.MediaItem instances — this is defined in Sitecore.Kernel.dll — upside down via their Flip() method.

The following class implements the above interface:

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

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

using ImageProcessor;

namespace Sitecore.Sandbox.Resources.Media
{
    public class ImageFactoryFlipper : IMediaImageFlipper
    {
        public MediaItem MediaItem { get; set; }

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

        private ImageFactory ImageFactory { get; set; }

        public ImageFactoryFlipper()
            : this(new ImageFactory())
        {
        }

        public ImageFactoryFlipper(ImageFactory imageFactory)
        {
            TargetMimeTypes = new List<string>();
            Assert.ArgumentNotNull(imageFactory, "imageFactory");
            ImageFactory = imageFactory;
        }

        public void Flip()
        {
            if (!ShouldFlip(MediaItem))
            {
                return;
            }

            using (MemoryStream outputStream = new MemoryStream())
            {
                ImageFactory.Load(MediaItem.GetMediaStream()).Rotate(180.0f).Save(outputStream);

                using (new EditContext(MediaItem))
                {
                    MediaItem.InnerItem.Fields["Blob"].SetBlobStream(outputStream);
                }
            }
        }

        protected virtual bool ShouldFlip(MediaItem mediaItem)
        {
            if (mediaItem == null || string.IsNullOrWhiteSpace(mediaItem.MimeType) || !TargetMimeTypes.Any() || ImageFactory == null)
            {
                return false;
            }

            return TargetMimeTypes.Any(targetMimeType => string.Equals(targetMimeType, mediaItem.MimeType, StringComparison.CurrentCultureIgnoreCase));
        }
    }
}

In the above class, I am “wrapping” an ImageFactory class instance — this class comes with the ImageProcessor .NET library which does some image manipulation (I found this .NET library via a Google search and have no idea how good it is, but it’s good enough for this “proof of concept”) — and inject it using Poor man’s dependency injection via the default constructor.

The Flip() method is where the magic happens. It calls the ShouldFlip() method which ascertains whether the MediaItem property is set on the class instance and whether the image found within it should be flipped — an image should be flipped if it has a MIME type that is within the list of MIME types that are injected into the class instance via the Sitecore Configuration Factory (see the patch configuration file below).

If the image should be flipped, the Flip() method uses the ImageFactory instance to flip the image upside down — it does this by rotating it 180 degrees — and then saves the flipped image contained within the MemoryStream instance into the MediaItem’s Blob field (this is where images are saved on Media Library Items).

Now that we have a class that flips images, we need a MediaCreator — a subclass of Sitecore.Resources.Media.MediaCreator (this lives in Sitecore.Kernel.dll) — to leverage an instance of the IMediaImageFlipper to do the image manipulation. The follow class does this:

using System.IO;

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

namespace Sitecore.Sandbox.Resources.Media
{
    public class ImageFlipperMediaCreator : MediaCreator
    {
        private IMediaImageFlipper Flipper { get; set; }

        public override Item CreateFromStream(Stream stream, string filePath, bool setStreamIfEmpty, MediaCreatorOptions options)
        {
            MediaItem mediaItem = base.CreateFromStream(stream, filePath, setStreamIfEmpty, options);
            if (Flipper == null)
            {
                return mediaItem;
            }

            Flipper.MediaItem = mediaItem;
            Flipper.Flip();
            return mediaItem;
        }
    }
}

After an image is uploaded to the Media Library, we pass the new MediaItem to the IMediaImageFlipper instance — this instance is injected using the Sitecore Configuration Factory (see the configuration file below) — and invoke its Flip() method to flip the image, and return the new MediaItem when complete.

I then utilize an instance of the MediaCreator above in a subclass of Resources.Media.MediaProvider.MediaProvider (I am going to replace the “out of the box” MediaProvider with the following class using the configuration file below):

using Sitecore.Diagnostics;
using Sitecore.Resources.Media;

namespace Sitecore.Sandbox.Resources.Media
{
    public class ImageFlipperMediaProvider : MediaProvider
    {
        private MediaCreator FlipperCreator { get; set; }

        public override MediaCreator Creator
        {
            get
            {
                return FlipperCreator ?? base.Creator;
            }
            set
            {
                Assert.ArgumentNotNull(value, "value");
                FlipperCreator = value;
            }
        }
    }
}

The MediaCreator that lives in the FlipperCreator property is injected into the class instance through the Sitecore Configuration Factory (see the patch configuration file below), and is returned by the Creator property’s accessor if it’s not null.

I then registered all of the above in Sitecore using the following 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.ImageFlipperMediaProvider, Sitecore.Sandbox</patch:attribute>
        <FlipperCreator type="Sitecore.Sandbox.Resources.Media.ImageFlipperMediaCreator, Sitecore.Sandbox">
          <Flipper type="Sitecore.Sandbox.Resources.Media.ImageFactoryFlipper, Sitecore.Sandbox">
            <TargetMimeTypes hint="list">
              <TargetMimeType>image/jpeg</TargetMimeType>
              <TargetMimeType>image/png</TargetMimeType>
            </TargetMimeTypes>
          </Flipper>
		    </FlipperCreator>
      </mediaProvider>
    </mediaLibrary>
  </sitecore>
</configuration>

Let’s test this.

I selected the following images for uploading to the Media Library:

selected-some-files-to-upload

As you can see, all uploaded images were flipped upside down:

images-upside-down

If you have any thoughts on this, or examples where you’ve employed the Adapter pattern in your Sitecore solutions, please share in a comment.

Until next time, have a Sitecorelicious day!

Use the Factory Method Pattern for Object Creation in Sitecore

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

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

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

using Sitecore.Data.Fields;

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

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

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

using System;

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

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

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

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

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

using System;

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

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

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

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

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

using Sitecore.Data.Fields;

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

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

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

using Sitecore.Data.Fields;

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

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

The following class implements the IFieldsComparerFactory interface above:

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

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

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

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

                return current;
            }
        }

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

        private IFieldsComparer NullFieldsComparer { get; set; }

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

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

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

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

            return comparer;
        }

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

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

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

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

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

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

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

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

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

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

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

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

using System.Runtime.Serialization;

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

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

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

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

                return fieldOneName;
            }
        }

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

                return fieldTwoName;
            }
        }

        public FieldOneValueLessThanOrEqualToFieldTwoValueValidator()
        {
        }

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

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

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

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

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

            return IsFieldOneLessThanOrEqualToFieldTwo(fieldOne, fieldTwo);
        }

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

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

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

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

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

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

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

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

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

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

I then set up the Item Validator for Integer fields:

integer-comparer-item-validator

I also set up another Item Validator for Datetime fields:

datetime-item-validator

Let’s take this for a spin!

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

integer-fields-item

As you can see, we get a warning.

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

datetime-fields-item

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

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

Utilize the Strategy Design Pattern for Content Editor Warnings in Sitecore

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

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

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

content-editor-warning-example

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

using System.Collections.Generic;

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

        string Message { get; set; }

        List<CommandLink> Links { get; set; }

        bool HasContent();

        IWarning Clone();
    }
}

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

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

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

using System.Collections.Generic;

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

        public string Message { get; set; }

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

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

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

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

            return clone;
        }
    }
}

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

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

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


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

        public string Command { get; set; }
    }
}

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

using System.Collections.Generic;

using Sitecore.Data.Items;

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

        IEnumerable<IWarning> Generate();
    }
}

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

using System.Collections.Generic;

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

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

        private IWarning Warning { get; set; }

        public Item Item { get; set; }

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

            return new[] { Warning };
        }

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

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

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

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

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

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

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

        private string Conjunction { get; set; }

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

        private IWarning Warning { get; set; }

        public Item Item { get; set; }

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

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

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

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

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

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

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

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

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

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

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

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

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

using System.Collections.Generic;

using Sitecore.Data.Items;

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

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

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

using System.Collections.Generic;

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

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

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

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

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

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

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

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

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

        private IWarningsGeneratorContext WarningsGeneratorContext { get; set; }

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

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

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

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

            return warnings;
        }

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

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

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

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

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

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

            return Translate.Text(text);
        }

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

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

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

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

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

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

Let’s test this out.

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

strategy-1

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

Let’s convert the Item into an Item Bucket:

strategy-2

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

strategy-3

Let’s fix the Item’s name:

strategy-4

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

strategy-5

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

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

So, what’s the benefit here?

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

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

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

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

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

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

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

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

content-editor-warning-example

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

using System.Collections.Generic;

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

        string Message { get; set; }

        List<CommandLink> Links { get; set; }

        bool HasContent();

        IWarning Clone();
    }
}

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

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

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

using System.Collections.Generic;

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

        public string Message { get; set; }

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

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

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

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

            return clone;
        }
    }
}

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

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

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


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

        public string Command { get; set; }
    }
}

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

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

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

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

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

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

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

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

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

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

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

            return Translate.Text(text);
        }
    }
}

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

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

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

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

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

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

        private IWarning Warning { get; set; }

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

            return new[] { Warning };
        }

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

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

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

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

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

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

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

        private string Conjunction { get; set; }

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

        private IWarning Warning { get; set; }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Let’s see if this works.

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

no-content-editor-warnings

As you can see, there are no warnings.

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

both-warnings-appear

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

Let’s now rename the Item:

rename-dialog

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

item-bucket-click-1

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

item-bucket-click-2

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

item-bucket-click-3

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

item-bucket-click-4

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

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

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

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

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

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

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

using Sitecore.Shell.Framework.Commands;

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

        void SetCommand(string commandName);

        void SetNextInvoker(ICommandInvoker nextInvoker);

        bool CanInvoke(CommandContext commandContext);

        void Invoke(CommandContext commandContext);
    }
}

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

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

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

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

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

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

        public bool HasCommand()
        {
            return false;
        }

        public void SetCommand(string commandName)
        {
        }

        public void SetNextInvoker(ICommandInvoker nextInvoker)
        {
        }

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

        public void Invoke(CommandContext commandContext)
        {
        }
    }
}

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

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

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

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

        private Command Command { get; set; }

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

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

        public CommandInvoker()
        {
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

using Sitecore.Sandbox.Invokers.Commands;

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

                return commandInvoker;
            }
        }

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

            CommandInvoker.Invoke(context);
        }

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

            return CommandState.Enabled;
        }

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

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

            return firstInvoker;
        }

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

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

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

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

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

            return GetDelimitersString(parameters).ToCharArray();
        }

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

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

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

            return xmlNode.Attributes[attributeName].Value;
        }

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

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

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

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

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

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

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

item:MoveRenamePublish:

move-rename-publish-ribbon-core

item:MoveLastPublish:

move-last-publish-ribbon-core

Let’s take this for a spin.

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

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

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

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

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

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

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

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

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

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

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

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

As you can see, this worked as well.

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

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

Use them wisely.

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

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

Augment Functionality in Sitecore Using the Decorator Design Pattern

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

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

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

So, what exactly did I come up with?

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

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

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

        private HttpRequestProcessor InnerProcessor { get; set; }

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

            Context.Item = item; 
        }

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

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

            return GetItem(entry.ID);
        }

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

            return database.GetItem(id);
        }

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

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

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

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

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

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

Let’s see this in action!

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

mario-content-editor

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

url-rewrite-id-table

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

resolved-url

As you can see, it worked.

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

mario-resolved

The above worked because the inner processor resolved it.

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

cat-page-five-sitecore

As you can see, that also worked:

cat-page-five

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

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!

Turn Off the Attaching of Files in Emails Sent by Web Forms for Marketers in Sitecore

The other day Michael West pinged me to see if I could help someone in one of the SDN forums via this tweet:

The poster had asked if there were an easy way to disable the attachment of files on emails sent by the Web Forms for Marketers (WFFM) module in Sitecore.

After quickly peeking in the WFFM assemblies, I gave a potential solution — check out the thread to see what it was — though it did not work.

Tonight I looked further into how to accomplish this, and came up with the following solution:

using System;
using System.Net;

using Sitecore.Data;
using Sitecore.Form.Core.Client.Data.Submit;
using Sitecore.Form.Core.Pipelines.ProcessMessage;
using Sitecore.Pipelines;

namespace Sitecore.Sandbox.Form.Submit
{
    public class SendMessage : Sitecore.Form.Submit.SendMessage
    {
        protected override void ExecuteMail(ID form, AdaptedResultList fields)
        {
            ProcessMessageArgs args = new ProcessMessageArgs(form, fields, this.MessageType);
            base.To = base.To.TrimEnd(new char[] { ',', ';' });
            base.LocalFrom = base.From.TrimEnd(new char[] { ',', ';' });
            args.To.Append(base.To.Replace(";", ","));
            args.From = base.From.Replace(";", ",");

            // magically populated by WFFM via XML
            args.IncludeAttachment = IncludeAttachments; 

            args.Mail.Append(base.Mail);
            args.Subject.Append(base.Subject);
            args.Recipient = this.Recipient;
            args.RecipientGateway = this.RecipientGateway;
            args.Host = base.Host;
            args.Port = base.Port;
            args.IsBodyHtml = this.IsBodyHtml;
            args.EnableSsl = this.EnableSsl;
            args.Data.Add("FromPhone", this.FromPhone ?? string.Empty);
            string[] strArray = string.IsNullOrEmpty(base.Login) ? new string[] { string.Empty } : base.Login.Split(new char[] { '\\' });
            if ((strArray.Length > 0) && !string.IsNullOrEmpty(strArray[0]))
            {
                if ((strArray.Length == 2) && !string.IsNullOrEmpty(strArray[1]))
                {
                    args.Credentials = new NetworkCredential(strArray[1], base.Password, strArray[0]);
                }
                else
                {
                    args.Credentials = new NetworkCredential(strArray[0], base.Password);
                }
            }
            if (!string.IsNullOrEmpty(base.CC))
            {
                base.CC = base.CC.TrimEnd(new char[] { ',', ';' });
                args.CC.Append(base.CC.Replace(";", ","));
            }
            if (!string.IsNullOrEmpty(base.BCC))
            {
                base.BCC = base.BCC.TrimEnd(new char[] { ',', ';' });
                args.BCC.Append(base.BCC.Replace(";", ","));
            }
            CorePipeline.Run("processMessage", args);
        }

        // magically populated by WFFM via XML
        private bool IncludeAttachments { get; set; }
    }
}

The class above inherits from Sitecore.Form.Submit.SendMessage in Sitecore.Forms.Custom.dll, and overrides the ExecuteMail() method — this method is declared virtual in Sitecore.Form.Submit.SendMail which is the base class of Sitecore.Form.Submit.SendMessage.

Most of the code in ExecuteMail() above is from the ExecuteMail() method on Sitecore.Form.Submit.SendMessage, though with one minor difference: I have added a boolean property named “IncludeAttachments” which is magically populated by WFFM via an XML property declaration for this class:

wffm-no-attachments

I spun up a form quickly with some File Upload fields, and mapped the above Save Action to it:

form-save-action-no-attachment

I then navigated to my form, attached two images, and clicked the submit button:

attached-two-images

As you can see, the images were not attached to the email:

no-attachments-email

I then went back to my Save Action; set the <IncludeAttachments> XML element’s value to be true; saved the form Item; published it; and resubmitted the form:

with-attachments-email

As you can see, the images were attached this time.

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