Home » Configuration » Omit Sitecore Bucket Folder Item Names from Page Item URLs

Omit Sitecore Bucket Folder Item Names from Page Item URLs

Sitecore Technology MVP 2016
Sitecore MVP 2015
Sitecore MVP 2014

Enter your email address to follow this blog and receive notifications of new posts by email.

In my two previous posts — this post and this post — I used the Sitecore Rules Engine to determine how bucket folders in the Item Buckets feature should be constructed.

I love having the freedom and flexibility to be able to do this.

However, depending on how you generate these folder structures, you might end up with some pretty yucky — ahem, I mean “interesting” — URLs if you have Actions that generate nonsense bucket folder Item names for bucketed Items.

For example, in my previous post, I built a custom Action that reversed the Item ID of the bucketed Item to generated its bucket folder path:

bucketed-links-bucketed-item-page-bucket-folders-2

Yucky, right?

Yuck

The “out of the box” Item Buckets bucket-folder-structure-generating algorithm creates a nice structure based on when the bucketed Item was created, and this is more palatable when looking at it:

bucketed-links-bucketed-item-page-bucket-folders-1

However, we may not be able to always use this “out of the box” algorithm for whatever reason — who knows what requirements will make us do — so let’s explore some code that can clean up these yucky URLs.

clean-up

Let’s first tackle cleaning up the URL generated by the “out of the box” Sitecore.Links.LinkProvider class.

I decided to implement a custom Sitecore pipeline to clean up the URL generated by the GetItemUrl() of the LinkProvider class, and will call this pipeline from a subclass of it (this code is further down in this post).

If you’re going to create a custom pipeline, you’ll need an arguments object for it — this is technically known as a Parameter Object but I will stick with the name “arguments object” and “arguments class” for the class that these objects are instantiated from throughout this post.

The following class is the arguments class for my custom pipeline:

using System;

using Sitecore.Data.Items;
using Sitecore.Links;
using Sitecore.Pipelines;

namespace Sitecore.Sandbox.Buckets.Pipelines.GetBucketedItemLinkUrl
{
    public class GetBucketedItemLinkUrlArgs : PipelineArgs
    {
        public Func<Item, UrlOptions, string> GetItemUrl { get; set; }

        public UrlOptions UrlOptions { get; set; }

        public Item ItemBucket { get; set; }

        public Item BucketedItem { get; set; }

        public string DefaultUrl { get; set; }

        public string BucketedItemUrl { get; set; }
    }
}

I defined the following abstract class which all processors of the custom pipeline must inherit:

namespace Sitecore.Sandbox.Buckets.Pipelines.GetBucketedItemLinkUrl
{
    public abstract class GetBucketedItemLinkUrlProcessor
    {
        public abstract void Process(GetBucketedItemLinkUrlArgs args);
    }
}

All subclasses must implement the abstract Process method above which takes in the arguments object with the class type defined above.

The following class whose instance is used as the first processor of the custom pipeline checks whether required property values are set on the arguments object passed to the pipeline:

using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Buckets.Pipelines.GetBucketedItemLinkUrl
{
    public class EnsureParameters : GetBucketedItemLinkUrlProcessor
    {
        public override void Process(GetBucketedItemLinkUrlArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            if(!CanProcess(args))
            {
                args.AbortPipeline();
            }
        }

        protected virtual bool CanProcess(GetBucketedItemLinkUrlArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            return args.GetItemUrl != null
                    && args.BucketedItem != null
                    && !string.IsNullOrWhiteSpace(args.DefaultUrl);
        }
    }
}

The BucketedItem, DefaultUrl — this is the “default” URL generated by the GetItemUrl() method on the “out of the box” LinkProvider class, and GetItemUrl — this is a “pointer” to the GetItemUrl method on the LinkProvider class — properties are required by the custom pipeline when initially called, and these are checked here.

This next class who instance is used for the second processor of the pipeline does some vetting of the bucketed Item passed on the arguments object:

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

using Sitecore.Buckets.Extensions;
using Sitecore.Buckets.Managers;

namespace Sitecore.Sandbox.Buckets.Pipelines.GetBucketedItemLinkUrl
{
    public class InspectBucketedItem : GetBucketedItemLinkUrlProcessor
    {
        public override void Process(GetBucketedItemLinkUrlArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.ArgumentNotNull(args.BucketedItem, "args.BucketedItem");
            
            if (!IsItemContainedWithinBucket(args.BucketedItem) || !IsParentBucketFolder(args.BucketedItem))
            {
                args.AbortPipeline();
            }
        }

        protected virtual bool IsItemContainedWithinBucket(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            return BucketManager.IsItemContainedWithinBucket(item);
        }

        protected virtual bool IsParentBucketFolder(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            return item.Parent.IsABucketFolder();
        }
    }
}

The instance of the class above checks to see whether the bucketed Item is a descendant of an Item Bucket, and also sees if it is contained within a bucket folder. If one of these is not true, the pipeline is aborted.

The following class whose instance serves as the third processor of the custom pipeline gets the bucketed Item’s Item Bucket:

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

using Sitecore.Buckets.Extensions;

namespace Sitecore.Sandbox.Buckets.Pipelines.GetBucketedItemLinkUrl
{
    public class SetItemBucket : GetBucketedItemLinkUrlProcessor
    {
        public override void Process(GetBucketedItemLinkUrlArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.ArgumentNotNull(args.BucketedItem, "args.BucketedItem");
            Item item = GetItemBucket(args.BucketedItem);
            if(!IsItemBucket(item))
            {
                args.AbortPipeline();
                return;
            }

            args.ItemBucket = item;
        }

        protected virtual Item GetItemBucket(Item bucketedItem)
        {
            Assert.ArgumentNotNull(bucketedItem, "bucketedItem");
            return bucketedItem.GetParentBucketItemOrParent();
        }

        protected virtual bool IsItemBucket(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            return item != null && item.IsABucket();
        }
    }
}

Code in the GetItemBucket() method uses the GetParentBucketItemOrParent() extension method of the Item class — this lives in the Sitecore.Buckets.Extensions namespace in Sitecore.Buckets.dll — to get the Item Bucket ancestor for the bucketed Item.

If the Item returned is not an Item Bucket — this check is done in the IsItemBucket() method via another Item class extension method, the IsABucket() method, which also lives in the same namespace as the extension method mentioned above — then the pipeline is aborted.

This next class whose instance serves as the last processor of the custom pipeline generates a URL without the bucket folder names in it:

using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Buckets.Pipelines.GetBucketedItemLinkUrl
{
    public class SetBucketedItemUrl : GetBucketedItemLinkUrlProcessor
    {
        public override void Process(GetBucketedItemLinkUrlArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.ArgumentNotNull(args.GetItemUrl, "args.GetItemUrlMethod");
            Assert.ArgumentNotNull(args.ItemBucket, "args.ItemBucket");
            Assert.ArgumentNotNullOrEmpty(args.DefaultUrl, "args.DefaultUrl");
            string bucketedItemUrl = GetBucketedItemUrl(args);
            if(string.IsNullOrWhiteSpace(bucketedItemUrl))
            {
                args.AbortPipeline();
                return;
            }

            args.BucketedItemUrl = bucketedItemUrl;
        }

        protected virtual string GetBucketedItemUrl(GetBucketedItemLinkUrlArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.ArgumentNotNull(args.GetItemUrl, "args.GetItemUrlMethod");
            Assert.ArgumentNotNull(args.ItemBucket, "args.ItemBucket");
            Assert.ArgumentNotNullOrEmpty(args.DefaultUrl, "args.DefaultUrl");
            string itemBucketUrl = args.GetItemUrl(args.ItemBucket, args.UrlOptions);
            if (string.IsNullOrWhiteSpace(itemBucketUrl))
            {
                return string.Empty;
            }

            string baseUrl = GetExtensionlessUrl(itemBucketUrl);
            string pageUrlPart = GetUrlPagePart(args.DefaultUrl);
            if (string.IsNullOrWhiteSpace(pageUrlPart))
            {
                return string.Empty;
            }

            return string.Join("/", baseUrl, pageUrlPart);
        }

        protected virtual string GetUrlPagePart(string url)
        {
            Assert.ArgumentNotNullOrEmpty(url, "url");
            int lastForwardSlashIndex = url.LastIndexOf("/");
            if (lastForwardSlashIndex < 0)
            {
                return string.Empty;
            }

            return url.Substring(lastForwardSlashIndex + 1);
        }

        protected virtual string GetExtensionlessUrl(string url)
        {
            Assert.ArgumentNotNullOrEmpty(url, "url");
            string extensionlessUrl = url;
            int lastDotIndex = extensionlessUrl.LastIndexOf(".");
            if (lastDotIndex < 0)
            {
                return extensionlessUrl;
            }

            return extensionlessUrl.Substring(0, lastDotIndex);
        }
    }
}

I’m not going to go too much into the details of all the code above. It is basically getting the web page name piece of the URL set in the DefaultUrl property on the arguments object, and appends it to the end of the URL of the Item Bucket without an extension (who uses extensions on URLs anyhow? ¯\_(ツ)_/¯ That’s like so 5 years ago. 😉 ).

The resulting URL is set in the BucketedItemUrl property of the arguments object.

Now that we have code to generate bucket-folder-less URLs, we need to use it. I built the following subclass of Sitecore.Links.LinkProvider which calls it:

using System.Collections.Specialized;

using Sitecore.Buckets.Extensions;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Links;
using Sitecore.Pipelines;

using Sitecore.Sandbox.Buckets.Pipelines.GetBucketedItemLinkUrl;

namespace Sitecore.Sandbox.Buckets.LinkProviders
{
    public class BucketedItemLinkProvider : LinkProvider
    {
        private string GetBucketedItemLinkUrlPipeline { get; set; }

        public override void Initialize(string name, NameValueCollection config)
        {
            Assert.ArgumentNotNullOrEmpty(name, "name");
            Assert.ArgumentNotNull(config, "config");
            base.Initialize(name, config);
            GetBucketedItemLinkUrlPipeline = config["getBucketedItemLinkUrlPipeline"];
        }

        public override string GetItemUrl(Item item, UrlOptions options)
        {
            string url = GetItemUrlFromBase(item, options);
            bool shouldGetBucketedItemUrl = !string.IsNullOrWhiteSpace(GetBucketedItemLinkUrlPipeline) 
                                                && !string.IsNullOrWhiteSpace(url) 
                                                && IsParentBucketFolder(item);
            if (!shouldGetBucketedItemUrl)
            {
                return url;
            }

            string bucketedItemUrl = GetBucketedItemUrl(item, options, url);
            if(string.IsNullOrWhiteSpace(bucketedItemUrl))
            {
                return url;
            }

            return bucketedItemUrl;
        }

        protected virtual bool IsParentBucketFolder(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            return item.Parent.IsABucketFolder();
        }

        protected virtual string GetBucketedItemUrl(Item bucketedItem, UrlOptions options, string defaultUrl)
        {
            GetBucketedItemLinkUrlArgs args = new GetBucketedItemLinkUrlArgs
            {
                GetItemUrl = ((someItem, urlOptions) => GetItemUrlFromBase(someItem, urlOptions)),
                UrlOptions = options,
                BucketedItem = bucketedItem,
                DefaultUrl = defaultUrl
            };

            CorePipeline.Run(GetBucketedItemLinkUrlPipeline, args);
            return args.BucketedItemUrl;
        }

        protected virtual string GetItemUrlFromBase(Item item, UrlOptions options)
        {
            return base.GetItemUrl(item, options);
        }
    }
}

I’ve extended the Initialize() method on the base LinkProvider class to read in the name of the custom pipeline (this is set in the patch configuration file further down in this post).

The overridden GetItemUrl() method grabs the URL generated by the same method on the base class for the passed Item. If the custom pipeline’s name is set; the generated URL isn’t null or empty; and the item lives in a bucket folder, the custom pipeline is called with the required parameters set on the arguments object.

If the custom pipeline generated a URL, it is returned to the caller. If not, the URL generated by the base class’ GetItemUrl() method is returned.

You might be thinking “Excellent, Mike! We have code that fixes the issue. Are we done yet?” Not so fast, dear reader — we need write code so Sitecore can resolve these bucket-folder-less URLs.

Since the URLs no longer contain the full path to the bucketed Item, we need a way to find this Item under the Item Bucket. I created the following interface for classes that can find a bucketed Item by name whose ancestor is the Item Bucket:

using Sitecore.Data.Items;

namespace Sitecore.Sandbox.Buckets.Providers.Items
{
    public interface IFindBucketedItemProvider
    {
        Item FindBucketedItemByName(Item bucketItem, string bucketedItemName);
    }
}

The following class implements the interface above:

using System;
using System.Linq;
using System.Linq.Expressions;

using Sitecore.Buckets.Util;
using Sitecore.ContentSearch;
using Sitecore.ContentSearch.Linq;
using Sitecore.ContentSearch.Linq.Utilities;
using Sitecore.ContentSearch.SearchTypes;
using Sitecore.ContentSearch.Utilities;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Buckets.Providers.Items
{
    public class FindBucketedItemProvider : IFindBucketedItemProvider
    {
        private ID bucketFolderTemplateId;
        private ID BucketFolderTemplateId
        {
            get
            {
                if(ID.IsNullOrEmpty(bucketFolderTemplateId))
                {
                    bucketFolderTemplateId = GetBucketFolderTemplateId();
                }

                return bucketFolderTemplateId;
            }
        }

        private string PreviewSearchIndexName { get; set; }

        private string LiveSearchIndexName { get; set; }

        private ISearchIndex previewSearchIndex;
        private ISearchIndex PreviewSearchIndex
        {
            get
            {
                if (previewSearchIndex == null)
                {
                    previewSearchIndex = GetPreviewSearchIndex();
                }

                return previewSearchIndex;
            }
        }

        private ISearchIndex liveSearchIndex;
        private ISearchIndex LiveSearchIndex
        {
            get
            {
                if (liveSearchIndex == null)
                {
                    liveSearchIndex = GetLiveSearchIndex();
                }

                return liveSearchIndex;
            }
        }
       
        public virtual Item FindBucketedItemByName(Item bucketItem, string bucketedItemName)
        {
            Assert.ArgumentCondition(!ID.IsNullOrEmpty(BucketFolderTemplateId), "BucketFolderTemplateId", "GetBucketFolderTemplateId() cannot return a null or empty Item ID!");
            Assert.ArgumentNotNull(bucketItem, "bucketItem");
            Assert.ArgumentNotNull(bucketedItemName, "bucketedItemName");

            ISearchIndex searchIndex = GetSearchIndex();
            using (IProviderSearchContext searchContext = searchIndex.CreateSearchContext())
            {
                var predicate = GetSearchPredicate<SearchResultItem>(bucketItem.ID, bucketedItemName);
                IQueryable<SearchResultItem> query = searchContext.GetQueryable<SearchResultItem>().Filter(predicate);
                SearchResults<SearchResultItem> results = query.GetResults();
                if (results.Count() < 1)
                {
                    return null;
                }

                SearchHit<SearchResultItem> hit = results.Hits.First();
                return hit.Document.GetItem();
            }
        }

        protected virtual ISearchIndex GetSearchIndex()
        {
            if (Context.PageMode.IsPreview)
            {
                Assert.IsNotNull(PreviewSearchIndex, "PreviewSearchIndex is null. Double-check the SearchIndexName configuration setting!");
                return PreviewSearchIndex;
            }

            Assert.IsNotNull(LiveSearchIndex, "LiveSearchIndex is null. Double-check the SearchIndexName configuration setting!");
            return LiveSearchIndex;
        }

        protected virtual ISearchIndex GetPreviewSearchIndex()
        {
            Assert.IsNotNullOrEmpty(PreviewSearchIndexName, "PreviewSearchIndexName is empty. Double-check its configuration setting!");
            return GetSearchIndex(PreviewSearchIndexName);
        }

        protected virtual ISearchIndex GetLiveSearchIndex()
        {
            Assert.IsNotNullOrEmpty(LiveSearchIndexName, "LiveSearchIndexName is empty. Double-check its configuration setting!");
            return GetSearchIndex(LiveSearchIndexName);
        }

        protected virtual ISearchIndex GetSearchIndex(string searchIndexName)
        {
            Assert.ArgumentNotNullOrEmpty(searchIndexName, "searchIndexName");
            return ContentSearchManager.GetIndex(searchIndexName);
        }

        protected virtual Expression<Func<T, bool>> GetSearchPredicate<T>(ID bucketItemId, string bucketedItemName) where T : SearchResultItem
        {
            var predicate = PredicateBuilder.True<T>();
            predicate = predicate.And(item => item.Paths.Contains(bucketItemId));
            predicate = predicate.And(item => item.TemplateId != BucketFolderTemplateId);
            predicate = predicate.And(item => item.Parent != bucketItemId);
            predicate = predicate.And(item => item.ItemId != bucketItemId);
            predicate = predicate.And(item => string.Equals(item.Name, bucketedItemName, StringComparison.CurrentCultureIgnoreCase));
            return predicate;
        }

        protected virtual ID GetBucketFolderTemplateId()
        {
            return BucketConfigurationSettings.BucketTemplateId;
        }
    }
}

I’m leveraging the Sitecore.ContentSearch API in the class above to find a bucketed Item with a given name — this is passed as a parameter to the FindBucketedItemByName() method — which is a descendant of the passed Item Bucket.

The GetSearchPredicate() method builds up a “predicate” which basically says “hey, we need an Item that is a descendant of the Item Bucket who is not a bucket folder; isn’t a child of the Item Bucket; isn’t the Item Bucket itself; and has a certain name (though we are ignoring case here)”, and is used by the Sitecore.ContentSearch API code in the FindBucketedItemByName() method.

“Mike, what’s up with the two ISearchIndex instances on the above class?” I have defined two here: one for when we are in Preview mode — I’m using the master search index here — and the other for when we aren’t — this uses the web search index instead.

If results are found, we return the Item instance from the first result in the results collection to the caller.

“Mike, could there ever be multiple Items returned?” Yes, this could happen if we have more than one bucketed Item with the same name, and only the first one in the results collection will be returned. In a future blog post, I will share a solution which will enforce unique Item names for bucketed Items.

Now that we have code that can find a bucketed Item by name under an Item Bucket, we need a custom Item Resolver — this is just a custom <httpRequestBegin> pipeline processor — that uses an instance of the class above to set the context Item for these bucket-folder-less URLs.

The following class does just that:

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

using Sitecore.Buckets.Extensions;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Pipelines.HttpRequest;

using Sitecore.Sandbox.Buckets.Providers.Items;

namespace Sitecore.Sandbox.Buckets.Pipelines.HttpRequest
{
    public class BucketedItemResolver : HttpRequestProcessor
    {
        private List<string> TargetSites { get; set; }

        private IFindBucketedItemProvider FindBucketedItemProvider { get; set; }

        public BucketedItemResolver()
        {
            TargetSites = new List<string>();
        }

        public override void Process(HttpRequestArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            if(!ShouldProcess(args))
            {
                return;
            }

            StartProfilerOperation();
            string path = MainUtil.DecodeName(args.Url.ItemPath);
            if (string.IsNullOrWhiteSpace(path))
            {
                EndProfilerOperation();
                return;
            }

            int lastForwardSlashIndex = path.LastIndexOf("/");
            if (lastForwardSlashIndex < 0)
            {
                EndProfilerOperation();
                return;
            }

            string parentPath = path.Substring(0, lastForwardSlashIndex);
            Item parentItem = args.GetItem(parentPath);
            if(parentItem == null)
            {
                EndProfilerOperation();
                return;
            }

            if (!parentItem.IsABucket())
            {
                EndProfilerOperation();
                return;
            }
            
            string bucketedItemName = path.Substring(lastForwardSlashIndex + 1);
            Item bucketedItem = FindBucketedItemByName(parentItem, bucketedItemName);
            if(bucketedItem == null)
            {
                EndProfilerOperation();
                return;
            }

            Context.Item = bucketedItem;
            EndProfilerOperation();
        }

        protected virtual bool ShouldProcess(HttpRequestArgs args)
        {
            return Context.Item == null
                    && Context.Database != null
                    && IsTargetSite()
                    && !string.IsNullOrWhiteSpace(args.Url.ItemPath);
        }

        protected virtual bool IsTargetSite()
        {
            return Context.Site != null
                    && TargetSites != null
                    && TargetSites.Any(site => string.Equals(site, Context.Site.Name, StringComparison.CurrentCultureIgnoreCase));
        }

        protected virtual void StartProfilerOperation()
        {
            Profiler.StartOperation("Resolve current bucketed item.");
        }

        protected virtual void EndProfilerOperation()
        {
            Profiler.EndOperation();
        }

        protected virtual Item FindBucketedItemByName(Item bucketItem, string bucketedItemName)
        {
            Assert.IsNotNull(FindBucketedItemProvider, "IFindBucketedItemProvider must be set in configuration!");
            return FindBucketedItemProvider.FindBucketedItemByName(bucketItem, bucketedItemName);
        }
    }
}

Not to go too much into all of the code above, the Process() method basically determines whether it should move forward on processing the request.

When should it do that? If there isn’t already context Item set; the context Database is set; the request is being made in a targeted site — basically this is just a list of site names sourced in the patch configuration file below which is a list of websites we want this code to run in (we don’t want to this code to run in the “shell” website which is what the Sitecore Desktop and Content Editor use); and have an Item path, then the Process() method should continue.

Other code in the Process() method is extracting out the parent’s page’s Item path; grabs an instance of the parent Item; determines whether it is an Item Bucket — if it’s not, then the code exits; the name of the page Item from URL; and then passes the parent Item and Item name to the FindBucketedItemByName() method which delegates to the FindBucketedItemByName() method on the IFindBucketedItemProvider instance to find the bucketed Item.

If a bucketed Item was found, the Process() method sets this as the context Item. Otherwise, it just exits.

I’m also using profiling code in the above class just as can be seen in the “out of the box” Sitecore.Pipelines.HttpRequest.ItemResolver class — this lives in Sitecore.Kernel.dll.

I then super-glued all of the code above in the following patch configuration file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <buckets>
      <providers>
        <items>
          <findBucketedItemProvider type="Sitecore.Sandbox.Buckets.Providers.Items.FindBucketedItemProvider, Sitecore.Sandbox">
            <PreviewSearchIndexName>sitecore_master_index</PreviewSearchIndexName>
            <LiveSearchIndexName>sitecore_web_index</LiveSearchIndexName>
          </findBucketedItemProvider>
        </items>
      </providers>
    </buckets>
    <linkManager>
      <providers>
        <add name="sitecore">
          <patch:attribute name="type">Sitecore.Sandbox.Buckets.LinkProviders.BucketedItemLinkProvider, Sitecore.Sandbox</patch:attribute>
          <patch:attribute name="getBucketedItemLinkUrlPipeline">getBucketedItemLinkUrl</patch:attribute>
        </add>
      </providers>
    </linkManager>
    <pipelines>
      <getBucketedItemLinkUrl>
        <processor type="Sitecore.Sandbox.Buckets.Pipelines.GetBucketedItemLinkUrl.EnsureParameters, Sitecore.Sandbox" />
        <processor type="Sitecore.Sandbox.Buckets.Pipelines.GetBucketedItemLinkUrl.InspectBucketedItem, Sitecore.Sandbox" />
        <processor type="Sitecore.Sandbox.Buckets.Pipelines.GetBucketedItemLinkUrl.SetItemBucket, Sitecore.Sandbox" />
        <processor type="Sitecore.Sandbox.Buckets.Pipelines.GetBucketedItemLinkUrl.SetBucketedItemUrl, Sitecore.Sandbox" />
      </getBucketedItemLinkUrl>
      <httpRequestBegin>
        <processor patch:before="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']"
                   type="Sitecore.Sandbox.Buckets.Pipelines.HttpRequest.BucketedItemResolver, Sitecore.Sandbox">
          <TargetSites hint="list">
            <site>website</site>
          </TargetSites>
          <FindBucketedItemProvider ref="buckets/providers/items/findBucketedItemProvider" />
        </processor>  
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

Let’s see if this works.

Let’s insert some bucketed Item links into this Rich Text field on my home Item:

bucketed-links-insert-link-rtf

Let’s insert an internal link to this Item:

bucketed-links-insert-link-1

Let’s insert another link to this Item:

bucketed-links-insert-link-2

Let’s insert yet another internal link — let’s insert a link to this Item:

bucketed-links-insert-link-3

After publishing and navigating to my home page, I see this in the html for the links:

bucketed-links-html-rendered

After clicking on the first link, I’m brought to the bucketed Item but have a look at the URL:

bucketed-links-bucketed-item-page

As you can see it worked!

When I finally got this working, I found myself doing a happy dance:

techno-chicken

techno-chickens

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


2 Comments

  1. […] Omit Sitecore Bucket Folder Item Names From Page Item Urls […]

  2. […] Omit Sitecore Bucket Folder Item Names from Page Item URLs […]

Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.