Home » 2014 (Page 4)

Yearly Archives: 2014

Rename Sitecore Clones When Renaming Their Source Item

Earlier today I discovered that clones in Sitecore are not renamed when their source Items are renamed — I’m baffled over how I have not noticed this before since I’ve been using Sitecore clones for a while now :-/

I’ve created some clones in my Sitecore instance to illustrate:

clones-not-renamed-yet

I then initiated the process for renaming the source item:

clones-renaming

As you can see the clones were not renamed:

clones-not-renamed-sad-face

One might argue this is expected behavior for clones — only source Item field values are propagated to its clones when there are no data collisions (i.e. a source Item’s field value is pushed to the same field in its clone when that data has not changed directly on the clone — and the Item name should not be included in this process since it does not live in a field.

Sure, I see that point of view but one of the requirements of the project I am currently working on mandates that source Item name changes be pushed to the clones of that source Item.

So what did I do to solve this? I created an item:renamed event handler similar to the following (the one I built for my project is slightly different though the idea is the same):

using System;
using System.Collections.Generic;

using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Events;
using Sitecore.Links;
using Sitecore.SecurityModel;

namespace Sitecore.Sandbox.Data.Clones
{
    public class ItemEventHandler
    {
        protected void OnItemRenamed(object sender, EventArgs args)
        {
            Item item = GetItem(args);
            if (item == null)
            {
                return;
            }

            RenameClones(item);
        } 

        protected virtual Item GetItem(EventArgs args)
        {
            if (args == null)
            {
                return null;
            }

            return Event.ExtractParameter(args, 0) as Item;
        }

        protected virtual void RenameClones(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            using (new LinkDisabler())
            {
                using (new SecurityDisabler())
                {
                    using (new StatisticDisabler())
                    {
                        Rename(GetClones(item), item.Name);
                    }
                }
            }
        }

        protected virtual IEnumerable<Item> GetClones(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            IEnumerable<Item> clones = item.GetClones();
            if (clones == null)
            {
                return new List<Item>();
            }

            return clones;
        }

        protected virtual void Rename(IEnumerable<Item> items, string newName)
        {
            Assert.ArgumentNotNull(items, "items");
            Assert.ArgumentNotNullOrEmpty(newName, "newName");
            foreach (Item item in items)
            {
                Rename(item, newName);
            }
        }

        protected virtual void Rename(Item item, string newName)
        {
            Assert.ArgumentNotNull(item, "item");
            Assert.ArgumentNotNullOrEmpty(newName, "newName");
            if (!item.Access.CanRename())
            {
                return;
            }
            
            item.Editing.BeginEdit();
            item.Name = newName;
            item.Editing.EndEdit();
        }
    }
}

The handler above retrieves all clones for the Item being renamed, and renames them using the new name of the source Item — I borrowed some logic from the Execute method in Sitecore.Shell.Framework.Pipelines.RenameItem in Sitecore.Kernel.dll (this serves as a processor of the <uiRenameItem> pipeline).

If you would like to learn more about events and their handlers, I encourage you to check out John West‘s post about them, and also take a look at this page on the
Sitecore Developer Network (SDN).

I then registered the above event handler in Sitecore using the following configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <events>
      <event name="item:renamed">
        <handler type="Sitecore.Sandbox.Data.Clones.ItemEventHandler, Sitecore.Sandbox" method="OnItemRenamed"/>
      </event>
    </events>
  </sitecore>
</configuration>

Let’s take this for a spin.

I went back to my source item, renamed it back to ‘My Cool Item’, and then initiated another rename operation on it:

clones-renaming

As you can see all clones were renamed:

clones-renamed

If you have any thoughts/concerns on this approach, or ideas on other ways to accomplish this, please share in a comment.

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

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

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

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

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

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

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

                return _IDTablePrefixes;
            }
        }

        private string IDTablePrefixesConfigPath { get; set; }

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

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

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

            return entries;
        }

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

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

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

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

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

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

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

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

                return _IDTablePrefixes;
            }
        }

        private string IDTablePrefixesConfigPath { get; set; }

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

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

            DeleteItemEntries(item);
        }

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

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

            return entries;
        }

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

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

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

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

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

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

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

idtables-before-publish

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

idtables-after-publish-both-items

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

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

idtables-deleted-from-master

It was removed from the IDTable in the master database.

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

idtables-published-deletion

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

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

Synchronize IDTable Entries Across Multiple Sitecore Databases Using a Composite IDTableProvider

The other day Sitecore MVP Kyle Heon asked:

This tweet got the wheels turning — more like got the hamster wheel spinning in my head — and I began experimenting on ways to synchronize IDTable entries across different Sitecore databases.

In this post, I will show the first solution I had come up with — yes I’ve come up with two solutions to this although no doubt there are more (if you have ideas for other solutions, or have tackled this problem in the past, please share in a comment) — but before I show that solution, I’d like to explain what the IDTable in Sitecore is, and why you might want to use it.

Assuming you’re using SQL Server for Sitecore, the “out of the box” IDTable in Sitecore is a database table that lives in all Sitecore databases — though Sitecore’s prepackaged configuration only points to the IDTable in the master database.

One has the ability to store key/value pairs in this table in the event you don’t want to “roll your own” custom database table — or use some other data store — and don’t want to expose these key/value pair relationships in Items in Sitecore.

The key must be a character string, and the value must be a Sitecore Item ID.

Plus, one has the ability to couple these key/value pairs with “custom data” — this, like the key, is stored in the database as a string which makes it cumbersome around storing complex data structures (having some sort of serialization/deserialization paradigm in place would be required to make this work).

Alex Shyba showed how one could leverage the IDTable for URLs for fictitious products stored in Sitecore in this post, and this article employed the IDTable for a custom Data Provider.

If you would like to know more about the IDTable, please leave a comment, and I will devote a future post to it.

Let’s take a look at the first solution I came up with:

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

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

using Sitecore.Sandbox.Data.IDTables;

namespace Sitecore.Sandbox.Data.SqlServer
{
    public class CompositeSqlServerIDTable : IDTableProvider
    {
        private IDictionary<string, IDTableProvider> _IDTableProviders;
        private IDictionary<string, IDTableProvider> IDTableProviders
        {
            get
            {
                if (_IDTableProviders == null)
                {
                    _IDTableProviders = new Dictionary<string, IDTableProvider>();
                }

                return _IDTableProviders;
            }
        }

        private string DatabaseName { get; set; }

        public CompositeSqlServerIDTable()
            : this(GetDefaultDatabaseName())
        {
        }

        public CompositeSqlServerIDTable(string databaseName)
        {
            SetDatabaseName(databaseName);
        }

        private void SetDatabaseName(string databaseName)
        {
            Assert.ArgumentNotNullOrEmpty(databaseName, "databaseName");
            DatabaseName = databaseName;
        }

        public override void Add(IDTableEntry entry)
        {
            foreach (IDTableProvider provider in IDTableProviders.Values)
            {
                provider.Add(entry);
            }
        }

        public override IDTableEntry GetID(string prefix, string key)
        {
            return GetContextIDTableProvider().GetID(prefix, key);
        }

        public override IDTableEntry[] GetKeys(string prefix)
        {
            return GetContextIDTableProvider().GetKeys(prefix);
        }

        public override IDTableEntry[] GetKeys(string prefix, ID id)
        {
            return GetContextIDTableProvider().GetKeys(prefix, id);
        }

        protected virtual IDTableProvider GetContextIDTableProvider()
        {
            IDTableProvider provider;
            if (IDTableProviders.TryGetValue(DatabaseName, out provider))
            {
                return provider;
            }

            return new NullIDTable();
        }

        public override void Remove(string prefix, string key)
        {
            foreach (IDTableProvider provider in IDTableProviders.Values)
            {
                provider.Remove(prefix, key);
            }
        }

        protected virtual void AddIDTable(XmlNode configNode)
        {
            if (configNode == null || string.IsNullOrWhiteSpace(configNode.InnerText))
            {
                return;
            }

            IDTableProvider idTable = Factory.CreateObject<IDTableProvider>(configNode);
            if (idTable == null)
            {
                Log.Error("Configuration invalid for an IDTable!", this);
                return;
            }

            XmlAttribute idAttribute = configNode.Attributes["id"];
            if (idAttribute == null || string.IsNullOrWhiteSpace(idAttribute.Value))
            {
                Log.Error("IDTable configuration should have an id attribute set!", this);
                return;
            }

            if(IDTableProviders.ContainsKey(idAttribute.Value))
            {
                Log.Error("Duplicate IDTable id encountered!", this);
                return;
            }

            IDTableProviders.Add(idAttribute.Value, idTable);
        }

        private static string GetDefaultDatabaseName()
        {
            if (Context.ContentDatabase != null)
            {
                return Context.ContentDatabase.Name;
            }

            return Context.Database.Name;
        }
    }
}

The above class uses the Composite design pattern — it adds/removes entries in multiple IDTableProvider instances (these instances are specified in the configuration file that is shown later in this post), and delegates calls to the GetKeys and GetID methods of the instance that is referenced by the database name passed in to the class’ constructor.

The CompositeSqlServerIDTable class also utilizes a Null Object by creating an instance of the following class when the context database does not exist in the collection:

using System.Collections.Generic;

using Sitecore.Data;
using Sitecore.Data.IDTables;

namespace Sitecore.Sandbox.Data.IDTables
{
    public class NullIDTable : IDTableProvider
    {
        public NullIDTable()
        {
        }

        public override void Add(IDTableEntry entry)
        {
            return;
        }

        public override IDTableEntry GetID(string prefix, string key)
        {
            return null;
        }

        public override IDTableEntry[] GetKeys(string prefix)
        {
            return new List<IDTableEntry>().ToArray();
        }

        public override IDTableEntry[] GetKeys(string prefix, ID id)
        {
            return new List<IDTableEntry>().ToArray();
        }

        public override void Remove(string prefix, string key)
        {
            return;
        }
    }
}

The NullIDTable class above basically has no behavior, returns null for the GetID method, and an empty collection for both GetKeys methods. Having a Null Object helps us avoid checking for nulls when performing operations on the IDTableProvider instance when an instance cannot be found in the IDTableProviders Dictionary property — although we lose visibility around the context database not being present in the Dictionary (I probably should’ve included some logging code to capture this).

I then glued everything together using the following configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <IDTable patch:instead="IDTable[@type='Sitecore.Data.$(database).$(database)IDTable, Sitecore.Kernel']" type="Sitecore.Sandbox.Data.$(database).Composite$(database)IDTable" singleInstance="true">
      <IDTables hint="raw:AddIDTable">
        <IDTable id="core" type="Sitecore.Data.$(database).$(database)IDTable, Sitecore.Kernel" singleInstance="true">
          <param connectionStringName="$(id)"/>
          <param desc="cacheSize">500KB</param>
        </IDTable>
        <IDTable id="master" type="Sitecore.Data.$(database).$(database)IDTable, Sitecore.Kernel" singleInstance="true">
          <param connectionStringName="$(id)"/>
          <param desc="cacheSize">500KB</param>
        </IDTable>
        <IDTable id="web" type="Sitecore.Data.$(database).$(database)IDTable, Sitecore.Kernel" singleInstance="true">
          <param connectionStringName="$(id)"/>
          <param desc="cacheSize">500KB</param>
        </IDTable>
      </IDTables>
    </IDTable>
  </sitecore>
</configuration>

To test the code above, I created the following web form which basically invokes add/remove methods on the instance of our IDTableProvider, and displays the entries in our IDTables after each add/remove operation:

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

using Sitecore.Configuration;
using Sitecore.Data.IDTables;

namespace Sandbox
{
    public partial class IDTableTest : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            AddEntries();
            ShowEntries();
            RemoveOneEntry();
            ShowEntries();
            RemoveAllEntries();
            ShowEntries();
        }

        private void AddEntries()
        {
            Response.Write("Adding two entries...<br /><br />");
            IDTable.Add("IDTableTest", "/mycoolhomepage1.aspx", Sitecore.Data.ID.Parse("{110D559F-DEA5-42EA-9C1C-8A5DF7E70EF9}"));
            IDTable.Add("IDTableTest", "/someotherpage1.aspx", Sitecore.Data.ID.Parse("{BAB78AE5-8118-4476-B1B5-F8981DAE1779}"));
        }

        private void RemoveOneEntry()
        {
            Response.Write("Removing one entry...<br /><br />");
            IDTable.RemoveKey("IDTableTest", "/mycoolhomepage1.aspx");
        }

        private void RemoveAllEntries()
        {
            Response.Write("Removing all entries...<br /><br />");
            foreach (IDTableEntry entry in IDTable.GetKeys("IDTableTest"))
            {
                IDTable.RemoveKey(entry.Prefix, entry.Key);
            }
        }

        private void ShowEntries()
        {
            ShowEntries("core");
            ShowEntries("master");
            ShowEntries("web");
        }

        private void ShowEntries(string databaseName)
        {
            IDTableProvider provider = CreateNewIDTableProvider(databaseName);
            IEnumerable<IDTableEntry> entries = IDTable.GetKeys("IDTableTest");
            Response.Write(string.Format("{0} entries in IDTable in the {1} database:<br />", entries.Count(), databaseName));
            foreach (IDTableEntry entry in provider.GetKeys("IDTableTest"))
            {
                Response.Write(string.Format("key: {0} id: {1}<br />", entry.Key, entry.ID));
            }
            Response.Write("<br />");
        }

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

When I loaded up the web form above in a browser, I saw that things were working as expected:

idtable-composite-test-run

Although I had fun in writing all of the code above, I feel this isn’t exactly ideal for synchronizing entries in IDTables across multiple Sitecore databases. I cannot see why one would want an entry for a Item in master that has not yet been published to the web database.

If you know of a scenario where the above code could be useful, or have suggestions on making it better, please drop a comment.

In my next post, I will show you what I feel is a better approach to synchronizing entries across IDTables — a solution that synchronizes entries via publishing and Item deletion.

Until next time, have a Sitecorelicious day!

Periodically Rebuild Link Databases using an Agent in Sitecore

Last week a colleague had asked me whether rebuilding the Link Database would solve an issue she was seeing. That conversation got me thinking: wouldn’t it be nice if we could automate the rebuilding of the Link Database for each Sitecore database at a scheduled time?

I am certain others have already created solutions to do this — if you know of any, please share in a comment — but I didn’t conduct a search to find any (I normally advocate not reinventing the wheel for code solutions but wanted to have some fun building a new solution).

In the spirit of my post on putting Sitecore to work for you, I built the following Sitecore agent (check out John West’s blog post on Sitecore agents to learn more):

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

using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Diagnostics;
using Sitecore.Jobs;

namespace Sitecore.Sandbox.Tasks
{
    public class RebuildLinkDatabasesAgent
    {
        private static readonly IList<Database> Databases = new List<Database>();
        private static readonly Stopwatch Stopwatch = Stopwatch.StartNew();

        public void Run()
        {
            JobManager.Start(CreateNewJobOptions());
        }

        protected virtual JobOptions CreateNewJobOptions()
        {
            return new JobOptions("RebuildLinkDatabasesAgent", "index", Context.Site.Name, this, "RebuildLinkDatabases");
        }

        protected virtual void RebuildLinkDatabases()
        {
            Job job = Context.Job;
            try
            {
                RebuildLinkDatabases(Databases);
            }
            catch (Exception ex)
            {
                job.Status.Failed = true;
                job.Status.Messages.Add(ex.ToString());
            }

            job.Status.State = JobState.Finished;
        }

        private void RebuildLinkDatabases(IEnumerable<Database> databases)
        {
            Assert.ArgumentNotNull(databases, "databases");
            foreach (Database database in databases)
            {
                Stopwatch.Start();
                RebuildLinkDatabase(database);
                Stopwatch.Stop();
                LogEntry(database, Stopwatch.Elapsed.Milliseconds);
            }
        }

        protected virtual void RebuildLinkDatabase(Database database)
        {
            Assert.ArgumentNotNull(database, "database");
            Globals.LinkDatabase.Rebuild(database);
        }

        protected virtual void LogEntry(Database database, int elapsedMilliseconds)
        {
            Assert.ArgumentNotNull(database, "database");
            if (string.IsNullOrWhiteSpace(LogEntryFormat))
            {
                return;
            }

            Log.Info(string.Format(LogEntryFormat, database.Name, elapsedMilliseconds), this);
        }

        private static void AddDatabase(XmlNode configNode)
        {
            if (configNode == null || string.IsNullOrWhiteSpace(configNode.InnerText))
            {
                return;
            }

            Database database = TryGetDatabase(configNode.InnerText);
            if (database != null)
            {
                Databases.Add(database);
            }
        }

        private static Database TryGetDatabase(string databaseName)
        {
            Assert.ArgumentNotNullOrEmpty(databaseName, "databaseName");
            try
            {
                return Factory.GetDatabase(databaseName);
            }
            catch (Exception ex)
            {
                Type agentType = typeof(RebuildLinkDatabasesAgent);
                Log.Error(agentType.ToString(), ex, agentType);
            }

            return null;
        }

        private string LogEntryFormat { get; set; }
    }
}

Logic in the class above reads in a list of databases set in a configuration file, adds them to a list for processing — these are only added to the list if they exist — and rebuilds the Link Database in each via a Sitecore job.

I added some timing logic to see how long it takes to rebuild each database, and capture this information in the Sitecore log.

I then wired up the above class in Sitecore using the following patch include configuration file:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <scheduling>
      <agent type="Sitecore.Sandbox.Tasks.RebuildLinkDatabasesAgent" method="Run" interval="00:01:00">
        <databases hint="raw:AddDatabase">
          <database>core</database>
          <database>master</database>
          <database>web</database>
        </databases>
        <LogEntryFormat>Rebuilt link database: {0} in {1} milliseconds.</LogEntryFormat>
      </agent>
    </scheduling>
  </sitecore>
</configuration>

I’ve set this agent to run every minute for testing, but it would probably be wise to have this run no more than once or twice a day.

After waiting a bit, I saw the following in my Sitecore log:

rebuilt-link-database

I do question the rebuild times. These seem quite small, especially when it takes a while to rebuild the Link Databases via the Sitecore Control Panel. If you have any ideas/thoughts on why there is an incongruence between the times in my log and how long it takes to rebuild these via the Sitecore Control Panel, please share in a comment.

Further, if you have any recommendations on making this code better, or have other ideas on automating the rebuilding of Link Databases in Sitecore, please drop a comment.

Until next time, have a Sitecoretastic day!

Add Buttons to Field Types in Sitecore

In a previous post I showed how one could go about removing a button from a field type in Sitecore, and felt a complementary post on adding a new button to a field type was in order.

In this post I will show you how I added a ‘Tomorrow’ button to a new Date field type — I duplicated the existing Date field type (you’ll see this later on in this post) — although I am uncertain how useful such a button would be. In other words, I’m not advocating the Date field type have a ‘Tomorrow’ button. I have created it only as an example.

I first subclassed Sitecore.Shell.Applications.ContentEditor.Date in Sitecore.Kernel.dll so that our new Date control — I’ve named this DateExtended — contains all functionality of the “out of the box” Date control:

using System;

using Sitecore.Diagnostics;
using Sitecore.Web.UI.Sheer;
using Sitecore.Shell.Applications.ContentEditor;

namespace Sitecore.Sandbox.Shell.Applications.ContentEditor
{
    public class DateExtended : Date
    {
        public override void HandleMessage(Message message)
        {
            if (!ShouldHandleMessage(message))
            {
                return;
            }

            if (IsTomorrowClick(message))
            {
                SetDateTomorrow();
                return;
            }

            base.HandleMessage(message);
        }

        private bool ShouldHandleMessage(Message message)
        {
            return IsCurrentControl(message)
                    && !string.IsNullOrWhiteSpace(message.Name);
        }

        private bool IsCurrentControl(Message message)
        {
            return AreEqualIgnoreCase(message["id"], ID);
        }

        private static bool IsTomorrowClick(Message message)
        {
            return AreEqualIgnoreCase(message.Name, "contentdate:tomorrow");
        }

        private void SetDateTomorrow()
        {
            SetValue(GetDateTomorrow());
        }

        protected virtual string GetDateTomorrow()
        {
            return DateUtil.ToIsoDate(System.DateTime.Today.AddDays(1));
        }

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

I overrode the HandleMessage() method above to ascertain whether we should handle the message passed to it — only the “contentdate:tomorrow” message should be handled, and this is passed when the ‘Tomorrow’ button is clicked (later on in this post, you will see how this message is associated with the ‘Tomorrow’ button in Sitecore) — and delegate to the base class when another message is passed, and that message is for the current control (this is when message[“id”] is equal to the ID property on the control).

If the ‘Tomorrow’ button was clicked, we derive tomorrow’s date, convert it to the ISO date format, and set it as the value of the field via the SetValue() method which is defined in the Sitecore.Shell.Applications.ContentEditor.Date class.

I then registered our library of controls — a library composed of the one lonely control shown above — with Sitecore via a patch configuration file:

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <controlSources>
      <source mode="on" namespace="Sitecore.Sandbox.Shell.Applications.ContentEditor" assembly="Sitecore.Sandbox" prefix="content"/>
    </controlSources>
  </sitecore>
</configuration>

I then duplicated the Date field type to a new field type:

new-field-type-core-db

The Control field contains the “content” prefix defined in the patch configuration file above and the name of the DateExtended class — both are joined together by a colon.

Under the new field type I added the new Menu item (button) for ‘Tomorrow’, and associated the “contentdate:tomorrow” message with it:

tomorrow-button-core-db

Let’s try this out!

I created a field on a template using our new field type above:

new-field-date-extended-type

I navigated to an item using the template above, and clicked the ‘Tomorrow’ button:

clicked-tomorrow-button

As you can see the next day’s date was set on the field.

If you have any examples of where you had to add a new button to an existing field type, or ideas for new buttons that might be useful, please share in a comment.

Remove Buttons from Field Types in Sitecore

This topic has been in my queue for quite some time, and I felt it was long overdue for me to write something up about it.

The idea for this post originated from a comment by Sitecore MVP Dan Solovay on my post covering how to resolve media library item aliases.

Dan had asked about the business need for resolving media library item aliases. This question got me thinking: perhaps one might not want to resolve media library aliases.

So what can be done to prevent media library items from being linked in Sitecore alias items? Well, you could remove the ‘Insert Media Link’ button from the General Link field type.

Removing buttons from existing field types in Sitecore is extremely easy, and no code changes are required. All you have to do is delete “Menu” items under field types in the Core database — although I would recommend that you duplicate an existing field type which results in a new field type, and then delete “Menu” items under the duplicate item just as I’ve done here on the General Link field type:

remove-buttons-core-db

You might be asking why I deleted the WebEdit Buttons folder which drives the insert link functionality for the Page Editor. Removing buttons from the Page Editor isn’t as straightforward as deleting items in Sitecore, and I might cover this in a future blog post.

I then used my new field type:

chose-field-type

As you can see buttons were removed:

field-on-item-less-buttons

If you have any examples of where you had to remove buttons from field types in Sitecore, please drop a comment.

In a future post, I will cover adding new buttons to field types — unlike this post, such a change requires adding code.

Until next time, have a Sitecoretastic day, and Happy New Year!