This is another post, which uses Umbraco’s Request Pipeline to customize the backoffice – see my other post Using Umbraco’s Request Pipeline to Conceal Nodes.

In some cases it is very useful to be able to use folders in Umbraco’s backoffice to group content pages without affecting the URL – I like to call these nodes grouping nodes or “ghost” nodes. These nodes can also have custom properties that can be used by all child nodes.

Example:

Home
Group 1
Page 1
Page 2
Group 2
Page 3
Page 4

In the example, the Page nodes will have URLs that will not include the Group nodes – /home/page-1.

Solution

To implement this, we will create an IUrlProvider and IContentFinder. The grouping nodes will have a hideFromUrl property set to true (in my case, I use a uComponenets Toggle Box, which uses 1 and 0 for true and false).

You should register the IUrlProvider and IContentFinder implementations at Application Startup and insert them before the other implementations.

See the following PDF document on how the providers and resolvers should be used: The Umbraco 6 RequestPipeline

IUrlProvider

The implementation of the IUrlProvider is straitforward – it derives from the DefaultUrlProvider and detects parents of the current node, which have the property “hideFromUrl” set to true. If there are such nodes, their URL segments are excluded from the final URL.

IContentFinder

The GroupingNodeContentFinder will use an XPath expression to find nodes that have “hideFromUrl” set to false and skipping their grouping node parents.

The request URL gets split into segments and converted to an XPath:

/page-1/page-2

XPath:

//[@isDoc and @urlName="page-1" and hideFromUrl="0"]//[@isDoc and @urlName="page-2" and hideFromUrl="0"]

Warning

The generated XPath expression has the following flaws:

  • If we have the following NON-grouping pages /page-1/page-2/page-3, and a request for /page-1/page-3, the XPath will retrieve page-3.
  • If we have the following pages /page-1/group/page-2, the default content finder will be able to find page-2 by its full /page-1/group/page-2 URL and they will also find /page-1/group.

Code

Edit: Updated the XPath from

@"//*[@isDoc and @urlName=""{0}"" and hideFromUrl=""0""]"

to

@"//*[@isDoc and @urlName=""{0}"" and not(hideFromUrl=""1"")]"
using System;
using System.Linq;
using System.Text;

namespace Umbraco.Extensions
{
    public class GroupingUrlProvider : DefaultUrlProvider
    {
        public override string GetUrl(UmbracoContext umbracoContext, int id, Uri current, UrlProviderMode mode)
        {
            IPublishedContent content = umbracoContext.ContentCache.GetById(id);
            if (content == null)
            {
                return null;
            }

            IEnumerable hiddenNodes = content
                .AncestorsOrSelf()
                .Where(x => x.Parent == null || (x.HasProperty("hideFromUrl") && Convert.ToBoolean(x.GetPropertyValue("hideFromUrl")))).ToArray();

            string url;
            if (hiddenNodes.Any())
            {
                StringBuilder builder = new StringBuilder();
                foreach (IPublishedContent node in
                content.AncestorsOrSelf().Except(hiddenNodes, new PublishedContentComparer()))
                {
                    builder.AppendFormat("/{0}", node.UrlName);
                }

                url = builder.ToString();
            }
            else
            {
                url = base.GetUrl(umbracoContext, id, current, mode);
            }
            return url;
        }
    }

    public class PublishedContentComparer : IEqualityComparer
    {
        public bool Equals(IPublishedContent x, IPublishedContent y)
        {
            if (ReferenceEquals(x, y)) return true;

            if (ReferenceEquals(x, null) || ReferenceEquals(y, null)) return false;

            return x.Id == y.Id;
        }

        public int GetHashCode(IPublishedContent obj)
        {
            if (ReferenceEquals(obj, null)) return 0;

            return obj.Id.GetHashCode();
        }
    }
}

4 Comments

Craig Morris · June 17, 2015 at 19:22

Does this work with Umbraco 7?

Sam · May 20, 2016 at 13:25

Can I have full example with IApplicationEventHandler.

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *