LibrarySites.Banner

Rules Engine Actions to Remove Old Versions in the Sitecore ASP.NET CMS

This blog post provides a base and three prototype implementations of actions for the Sitecore ASP.NET web Content Management System (CMS) rules engine that take some action on older versions beyond a threshold that you pass as a parameter to the action. Pruning unnecessary versions can improve performance and usability for CMS users.

The abstract base class implements the Apply() method to determine the old versions to process, and calls the HandleVersion() method for each. Rules engine actions that derive from this abstract base class implement the HandleVersion() method.

The abstract base class implements two properties:

  • MinVersions: Rules engine actions that derive from this abstract base class will not invoke the HandleVersion() method for items less than this distance from the latest item.
  • MinUpdatedDays: Rules engine actions that derive from this abstract base class will not invoke the HandlerVersion() method for versions updated within the this number of days.

Passing these two parameters to the action seems a little inconsistent because logic involving parameters generally belongs in conditions. This approach is appropriate in this case because it avoids the need to pass any data from the condition to the action.

The three concrete classes are:

  • RecycleOldVersions: Moves old versions to the recycle bin if one is available, otherwise removes them permanently.
  • ArchiveOldVersions: Archives old versions if an archive is available, otherwise recycles them if a recycle bin is available, and otherwise removes them permanently.
  • DeleteOldVersions: Removes old versions permanently.

You can implement an OnItemSaved rule to invoke this logic every time a user saves an item and/or run a rule against all items to invoke one of these actions.

namespace Sitecore.Sharedsource.Rules.Actions
{
  using System;
  using Assert = Sitecore.Diagnostics.Assert;
  using SC = Sitecore;
  
  public abstract class MinVersionsAction<T> : 
    SC.Rules.Actions.RuleAction<T> where T : SC.Rules.RuleContext
  {
    /// <summary>
    /// Backs the MinVersions property.
    /// </summary>
    private int _minVersions;
  
    /// <summary>
    /// Gets or sets a value that indicates 
    /// the minimum number of versions to retain. 
    /// Set by rule parameters, or defaults to 30. 
    /// </summary>
    public int MinVersions
    {
      get
      {
        return this._minVersions < 1 ? 30 : this._minVersions;
      }
  
      set
      {
        this._minVersions = value;
      }
    }
  
    /// <summary>
    /// Gets or sets a value that indicates the minimum age of versions to 
    /// remove, in days. Actions that derive from this abstract base class
    /// will not process Versions updated within this number of days.
    /// </summary>
    public int MinUpdatedDays { get; set; }
  
    /// <summary>
    /// Apply the rule.
    /// </summary>
    /// <param name="ruleContext">Rule processing context.</param>
    public override void Apply(T ruleContext)
    {
      Assert.ArgumentNotNull(ruleContext, "ruleContext");
      Assert.ArgumentNotNull(ruleContext.Item, "ruleContext.Item");
  
      // for each language available in the item
      foreach (SC.Globalization.Language lang in ruleContext.Item.Languages)
      {
        SC.Data.Items.Item item = ruleContext.Item.Database.GetItem(
          ruleContext.Item.ID,
          lang);
  
        if (item == null)
        {
          continue;
        }
  
        // to prevent the while loop from reaching MinVersions,
        // only process this number of items
        int limit = item.Versions.Count - this.MinVersions;
        int i = 0;
  
        while (item.Versions.Count > this.MinVersions && i < limit)
        {
          SC.Data.Items.Item version = item.Versions.GetVersions()[i++];
          Assert.IsNotNull(version, "version");
  
          if (this.MinUpdatedDays < 1
            || version.Statistics.Updated.AddDays(this.MinUpdatedDays) < DateTime.Now)
          {
            this.HandleVersion(version);
          }
        }
      }
    }
  
    /// <summary>
    /// Classes that derive from this abstract base class
    /// implement this method to remove versions.
    /// </summary>
    /// <param name="version">The version to process.</param>
    public abstract void HandleVersion(Sitecore.Data.Items.Item version);
  }
  
  /// <summary>
  /// Rules engine action that recycles old versions of items.
  /// </summary>
  public class RecycleOldVersions<T> : 
    MinVersionsAction<T> where T : SC.Rules.RuleContext
  {
    /// <summary>
    /// Recycle the old version.
    /// </summary>
    /// <param name="version">The old version to recycle.</param>
    public override void HandleVersion(SC.Data.Items.Item version)
    {
      Assert.ArgumentNotNull(version, "version");
      SC.Diagnostics.Log.Audit(
        this,
        "Recycle version : {0}",
        new string[] { SC.Diagnostics.AuditFormatter.FormatItem(version) });
      version.RecycleVersion();
    }
  }
  
  /// <summary>
  /// Rules engine action that archives old versions of items.
  /// </summary>
  public class ArchiveOldVersions<T> : 
    MinVersionsAction<T> where T : SC.Rules.RuleContext
  {
    /// <summary>
    /// Archives the old version.
    /// </summary>
    /// <param name="version">The old version to archive.</param>
    public override void HandleVersion(SC.Data.Items.Item version)
    {
      Assert.ArgumentNotNull(version, "version");
      SC.Data.Archiving.Archive archive = version.Database.Archives["archive"];
  
      if (archive != null)
      {
        SC.Diagnostics.Log.Audit(
          new object(), 
          "Archive version: {0}"
          new string[] { SC.Diagnostics.AuditFormatter.FormatItem(version) });
        archive.ArchiveVersion(version);
      }
      else
      {
        SC.Diagnostics.Log.Audit(
          this
          "Recycle version : {0}"
          new string[] { SC.Diagnostics.AuditFormatter.FormatItem(version) });
        version.RecycleVersion();
      }
    }
  }
  
  /// <summary>
  /// Rules engine action that deletes old versions of items.
  /// </summary>
  public class DeleteOldVersions<T> : MinVersionsAction<T> where T : SC.Rules.RuleContext
  {
    /// <summary>
    /// Deletes the old version.
    /// </summary>
    /// <param name="version">The old version to delete.</param>
    public override void HandleVersion(SC.Data.Items.Item version)
    {
      Assert.ArgumentNotNull(version, "version");
      SC.Diagnostics.Log.Audit(
        this
        "Delete version : {0}"
        new string[] { SC.Diagnostics.AuditFormatter.FormatItem(version) });
      version.Versions.RemoveVersion();
    }
  }
}

To register the actions, in the Content Editor:

  1. Navigate to the /sitecore/system/Settings/Rules/Common/Actions item.
  2. Insert items named Recycle Old Versions, Archive Old Versions, and Delete Old Versions using the System/Rules/Action data template.
  3. In the new items, set the Type field in the Script section to the signatures of the classes.
  4. In the new items, set the Text field in the Data section to the following values:
    recycle versions beyond [MinVersions,positiveinteger,,number] older than [MinUpdatedDays,positiveinteger,,number] days 
    archive versions beyond [MinVersions,positiveinteger,,number] older than [MinUpdatedDays,positiveinteger,,number] days
    delete versions beyond [MinVersions,positiveinteger,,number] older than [MinUpdatedDays,positiveinteger,,number] days
  5. In the new items, set the Text field in the Data section to the following value:
    recycle versions beyond [MinVersions,positiveinteger,,number] older than [MinUpdatedDays,positiveinteger,,number] days

To create a rule that runs each time a user saves an item, in the Content Editor:

  1. Navigate to the /sitecore/system/Settings/Rules/Item Saved/Rules item.
  2. Insert an item using the System/Rules/Rule data template.
  3. In the rule, select any necessary conditions, and then select the appropriate action created previously.

screen capture of a rule for recycling old versions

Resources

  • As always, thanks John, good information here. I think you wrote/blogged about a task to do something similar to this which we've implemented and use in all of our installations nowadays. We usually allow up to 10 versions. We started doing this after we had a client with 100+ versions for a few translated items on a multiple language home page, it was nearly unloadable until we were able to clip some of the old versions.