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?
Nikolay Arhangelov · June 17, 2015 at 20:01
I haven’t tested it.
Sam · May 20, 2016 at 13:25
Can I have full example with IApplicationEventHandler.
Nikolay Arhangelov · May 20, 2016 at 13:37
Hello Sam,
All the necessary code is posted – you just need to add the GroupingUrlProvider and GroupingNodeContentFinder classes to your project. Then register them in application starting, like this post does – see the last section called ‘Registering the NaviHideContentFinder on ApplicationStarting’. Now the classes will work on navigation. You’ll need to add a property to your Umbraco pages, which will specify if the page should be skipped – I called mine ‘hideFromUrl’.