Sitecore 8:在保存时同步存储桶项目

Sitecore 8: Sync a bucket item on save

我已经了解了如何为新创建的存储桶项提供默认条件和默认操作。我也知道我们可以创建一个自定义规则来基于自定义日期字段构建路径。

但是,我们如何在日期字段保存时设置项目路径。 考虑一个例子。我们有一个存储桶项模板 "News",它有一个日期字段 "Release Date"。我们在创建项目时进行了设置,项目路径的创建日期类似于“/News/2015/09/16/item1”。现在,我们需要一些逻辑,当 "item1" 的 "release date" 更新并且项目 Saved.

时,我们可以更改路径

当项目的发布日期更新且项目已保存时,我们如何更新项目路径!!我需要在 OnItemSaved() 方法中实现一些逻辑吗?

我已经浏览过 GeekHive

上的帖子

您需要实现流水线处理器。 您可以通过将以下内容添加到 App_Code/Include 文件夹中的 .config 文件中来执行此操作。

<processors>
    <saveUI>
        <processor mode="on" type="Namespace.ClassName, Your.Assembly" patch:after="processor[last()]" />
    </saveUI>
</processor

您还需要实现 class - 它没有什么特别之处,只是它必须有一个带有 Sitecore.Pipelines.Save.SaveArgs 参数的 public Process 方法。

namespace CustomFunctions
{
    public class SaveAction
    {
        public void Process(SaveArgs args)
        {
            // There's a collection of items
            // I'm not sure what the situation where there's more than one item is though.
            var items = args.SavedItems;

            var bucket = SomeFunctionToGetParent(items);

            BucketManager.Sync(items);
        }
    }
}

我从来没有真正实现过这个,但我认为我的代码应该让你知道如何开始——尽管每次保存项目时都会调用这个管道处理器,所以你需要有效的检查来使确保该项目需要使用您的存储桶同步处理器。

如果我没理解错的话,您希望存储桶项路径基于 更新 日期而不是创建日期?我说得对吗?

如果是,那将不是一件简单的事情。我看到了以下实现方法。

  1. 将您的存储桶配置为按更新日期组织,而不是创建(您提到您已经知道如何配置该行为)。从标准模板派生的每个 Sitecore 项目都应该有统计部分,其中 __Updated 字段(开头有两个下划线)会自动更新每个项目保存的相应事件。您应该使用该字段。

  2. 完成后,同步 所有现有项目以应用该存储项目路径。

  3. 处理 item:saved 事件

  4. item:saved 事件处理程序中:取消存储该特定项目并再次重新存储该项目(使用 item:unbucketitem:bucket 命令)

  5. 您的特定项目将根据您的分桶路径规则分桶。

希望对您有所帮助!

执行此操作的最简单方法是连接到 item:saved 事件并同步其中的存储桶。以下代码未经测试:

public class ItemSavedEventHandler
{
    public void Execute(object sender, ItemSavedEventArgs args)
    {
        Assert.IsNotNull(sender, "sender is null");
        Assert.IsNotNull(args, "args is null");

        // WARNING: using the events disabler is not recommended in this case. 
        // If you do this, the path of the item in question will not be updated and will cause issues when you edit the page and try to specify rendering data sources (the locations won't resolve)
        using (new EventsDisabler())
        {
            var parameter = args.Item;
            if (!BucketManager.IsItemContainedWithinBucket(paremeter))
            {
                return;
            }

            var bucketItem = parameter.GetParentBucketItemOrParent();
            if (!bucketItem.IsABucket())
            {
                return;
            }

            BucketManager.Sync(bucketItem);
        }
    }
}

在包含大量项目的存储桶上,这会大大减慢保存过程。

您可以通过使用 BucketManager 以编程方式将桶中的项目移动到桶的根来实现此目的。这样做会迫使它重新评估存储桶规则并重新组织它:

BucketManager.MoveItemIntoBucket(bucketedItem, bucketItem);

请注意,这与 BucketManager.Sync(bucketItem) 不同,因为它不会同步 整个 存储桶,而是仅处理已更改的单个项目。

在我们的解决方案中,我们通常会创建一个 item:saved 事件处理程序来自动执行此操作:

using Sitecore.Buckets.Managers;
using Sitecore.Buckets.Util;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Events;
using System;
using System.Text.RegularExpressions;

namespace Custom.Events.ItemSaved
{
    public class ReorganizeBucketedItemInBucket
    {
        public void OnItemSaved(object sender, EventArgs args)
        {
            var bucketedItem = Event.ExtractParameter(args, 0) as Item;

            // If we don't have an item or we're not saving in the master DB, ignore this save
            if (bucketedItem == null || !"master".Equals(bucketedItem.Database?.Name, StringComparison.OrdinalIgnoreCase))
                return;

            if (!bucketedItem.TemplateID.Equals(new ID("{bucketed-item-template-id}"))) return;

            var itemChanges = Event.ExtractParameter(args, 1) as ItemChanges;

            // If there were no changes or the changes didn't include the date field, ignore this save
            if (itemChanges == null || !itemChanges.HasFieldsChanged || !itemChanges.IsFieldModified(new ID("{field-id-of-date-field}")))
                return;

            Item bucketItem = bucketedItem.Axes.SelectSingleItem($"{EscapePath(bucketedItem.Paths.FullPath)}/ancestor-or-self::*[@@templateid = '{{bucket-container-template-id}}']");

            // If this item isn't in a bucket (or is in a bucket of another, unexpected type), ignore it
            if (bucketItem == null) return;

            Item parent = bucketedItem.Parent;
            BucketManager.MoveItemIntoBucket(bucketedItem, bucketItem);

            // Delete empty ancestor bucket folders
            while (parent != null && !parent.HasChildren && parent.TemplateID == BucketConfigurationSettings.BucketTemplateId)
            {
                Item tempParent = parent.Parent;
                parent.Delete();
                parent = tempParent;
            }
        }

        /// <summary>
        /// Wraps each segment of a sitecore path with "#"'s
        /// </summary>
        public string EscapePath(string path)
        {
            return Regex.Replace(path, @"([^/]+)", "##").Replace("#*#", "*");
        }
    }
}

当然不要忘记你的补丁配置:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
        <events>
            <event name="item:saved">
                <handler type="Custom.Events.ItemSaved.ReorganizeBucketedItemInBucket, Custom.Events" method="OnItemSaved"></handler>
            </event>
        </events>
    </sitecore>
</configuration>

基于一些答案,以下是大多数用例的最易读/最高效的解决方案:

using Sitecore.Buckets.Extensions;
using Sitecore.Buckets.Managers;
using Sitecore.Data.Events;
using Sitecore.Diagnostics;
using System;
using Sitecore.Data.Items;
using Sitecore.Events;

namespace XXXX.Project.Web.Infrastructure.Pipelines
{
    public class MoveItemIntoBucketOnSave
    {
        public void OnItemSaved(object sender, EventArgs args)
        {
            Assert.IsNotNull(sender, "sender is null");
            Assert.IsNotNull(args, "args is null");

            var savedItem = Event.ExtractParameter(args, 0) as Item;

            if (savedItem == null || savedItem.Database.Name.ToLower() != "master" || !savedItem.IsItemBucketable())
            {
                return;
            }

            // WARNING: see update below about EventDisabler
            using (new EventDisabler())
            {
                if (!BucketManager.IsItemContainedWithinBucket(savedItem))
                {
                    return;
                }

                var bucketItem = savedItem.GetParentBucketItemOrParent();
                if (!bucketItem.IsABucket())
                {
                    return;
                }

                // If you want to sync the entire bucket
                // BucketManager.Sync(bucketItem);

                BucketManager.MoveItemIntoBucket(savedItem, bucketItem);
            }
        }
    }
}

我不担心此操作后有任何空的存储桶文件夹,因为它们将在完整的存储桶同步期间被清理,并且内容作者通常不会遍历存储桶树,因为他们应该使用搜索。

配置如下:

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <events>
      <event name="item:saved">
        <handler type="XXXX.Project.Web.Infrastructure.Pipelines.MoveItemIntoBucketOnSave, XXXX.Project.Web" method="OnItemSaved" />
      </event>
    </events>
  </sitecore>
</configuration>

更新: 我不推荐使用 EventDisabler。如果您添加一个新页面,然后尝试向该页面添加渲染并为其指定数据源,则数据源位置将无法解析,因为 Sitecore 仍然认为新创建项目的路径是存储桶项目的直接子项,而不是比项目被移动到桶内的任何地方。有关详细信息,请参阅 this question

更新 2: 请注意,当创建新的分桶项时,此方法将被调用两次。您应该非常仔细地考虑这对您意味着什么,以及您是否应该在调用此方法中的任何其他代码之前添加任何其他检查。