The edit mode page tree is pretty fast when site has hierarchical structure. Even for few levels of nested pages the navigation works really smoothly. Unfortunately it doesn’t look that good when structure is flat. Starting from few hundreds child pages the user experience become worse, the waiting time for server to load children is more annoying and it’s more difficult for Editors to know what is current parent in the structure.

site structure

There were many articles, including mine, about improving this area. Tables, lists and advanced grigs – all of those plugins displays pages inside custom views or gadgets. This time I tried some experiments with automatic “container” pages.

By saying “containers” I mean pages that groups children in edit mode. They have no templates and they are used only to help with page tree navigation.

virtual containers sample

Don’t judge me too quickly

We can’t forget that container pages will speed up tree performance for flat structure, but of course there are significant drawbacks around this concept! You can read more about this Epinova blog (When I read this article again, I realized that this blog post is now six years old, time goes by so fast).

One solution we can take to avoid parts of the URL that don’t have view mode representation is to use partial router. The container page part will be removed when resolving the URL. For example, for:

we will have:

I started the implementation and after a few days of work I had a ready-to-use configurable addon that automatically grouped children using containers pages. Additionally, I added a generic router responsible for removing parts of the container pages from URL. I also had a schedule job that was fixing broken containers structure and admin mode plugin that allowed to clean structure if needed. Everything worked as expected, but when I looked at the final solution I thought that it was far away from what I wanted to achieve.

My biggest concern is that solving performance and usability problem in edit mode will affect the view mode. It is really odd, that I need to create physical container pages and then handle routing of those pages in view mode. What if I have a bug in partial router and it doesn’t work as expected in edge case scenarios. And also, I had to keep physical containers structure synchronized with the pages. I can imagine the risk of running an indexing for 2000 child pages on a live website.

I began to consider a completely different solution.

Virtual Containers

I took a closer look at the response time of the ContentStructure-> GetChildren query. First, I generated 1000 articles below the News List page. Then, in the server code, I added some Server-Timing headers to see how long the request was processed on the server.

server timing result

I had to wait a few seconds before the tree node was expanded. Sometimes it was 3 seconds sometimes 6, but GetChildren query itself (the cache + eventual database + initial conversion to IContent) was always unnoticeable and took just a few milliseconds.

It seemed that up to few thousands children, there was no need to create persistent containers, because the server was not overloaded. And this means that I could have „virtual” containers generated on the server for each request without the need for caching or indexing. These containers would only be displayed in the UI and would not be stored in the database. The idea is that query is fast enough that I can get all 1000 pages from the repository, calculate the containers, get children slice for selected container and return the data on client side.

In general, the concept of Virtual Containers is:

  • this solution doesn’t affect the view mode – this is the biggest advantage. No partial router, no strange parts of the URL, no indexing, no risk of breaking something
  • groups structure can be changed back and forth with no need to rebuild physical pages
  • feature can be turned on and off at any time
  • virtual pages can’t be clicked, drag and drop, selected, etc. They are used only to group children in page tree

Virtual containers nuget package

I’ve created a nuget package that allows to configure a set of content grouping rules. Site may contain more than one registered configuration. For example a list of recipes grouped by first letter of a recipe name under Recipes page and a list of news grouped by creation date under News page.

Each configuration can be added from code or, using admin mode plugin, via the database.

How does it looks like

When a page has grouping configured, the icon and tooltip in Edit mode will change. The Editor will see that page is the root of containers and pages below are virtual.

virtual containers custom incons

In addition, groups below the container root have different icon, cannot be used as regular pages – no context menu, not clickable or moveable nor D&D to content areas, etc.

container pages

Container name generators

The name of the virtual container is not limited to the first letter of the content name. The parent container name is generated from the processed Content. It can be a content name, creation date, other properties or custom values.

There are few built-in generator types that I think can be used in common:

  • Name – groups pages by part of the page name. By default it’s first letter, but it can be any part of the content name string. Generator has 3 input parameters, the start index and length specifies the part of the content name used to build the container name. The last parameter is DefaultValue is used as a container name, when name cannot be generated, e.g. when content name is too short. Name generator supports unicode characters.
  • Created Date – groups children by page created date. Name is generated based on the date format. For example „yyyy” groups pages by year. The generator has two parameters, “DateFormat” is the format of date used to create container name. “DefaultValue” is used to create container name when content does not implement IChangeTrackable interface and does not have CreatedDate property.
  • Expression – a generator that creates a container name from a custom expression. The only parameter is an expression function that returns the container name from Content. This generator is not available in the database configuration.

Nested structure

When page contains few thousands children, then one level of structure might not be enough. Therefore, we can define a nested structure of virtual containers and the configuration class contains list of generators.

nested structure

Configuring containers from code

Registering containers requires only few lines of code. For example, we can use InitialzableModule and the extension methods from IContentChildrenGroupsRegistration interface.

In the code above I have registered two containers, one is grouping pages by letter and other is grouping by created date.

There is also more generic method for registering containers with the „ContainerConfiguration” object:

In the code above I registered nested structure of containers, first level by first letter of content name, then second by year of page created date.

Admin mode plugin

When the configuration cannot be defined in the code, but needs to be added dynamically for running application, then we can use the admin mode plugin. The configurations set in the plugin are stored in DDS.

Plugin is available int the Tools -> Configure Virtual Containers menu.

It allows to manage database configurations, but also preview configurations added from code (they are not editable).

Admin mode overview

The plugin allows you to set the same fields as for configurations added from code.

Admin editing

Custom generators

If no built-in generator fits the structure of the site, we can create our own generator. It must implement IGroupNameGenerator interface. The interface has only one method, GetName, which returns the container name when saving the content.

For example, we can try to implement a range generator.

Range generator will allow grouping children by first letter, but in more specific way than default name generator. All pages that has name starting from letters from A to F should be in “A-F” group, from H to N in “H-N” group and so on.

To make this work we need to create a RangeGenerator class:

And then register container using InitializableModule:

After running the code we should see grouped content.

Range generator

External page providers

It is worth mentioning that virtual containers supports external Content Providers.

For example, for XML Content Provider with content defined in the XML file:

We just need to define configuration root same as content provider root.

External providers

Search command

One last small feature is an extra search command. It’s available from page context menu (but only for pages with configured containers).

Search command

After clicking this command Editor will see dialog with search field:

Search dialog

that allows to search children, but only under container:

Search demo

As you can see virtual containers are not listed in search result.


Plugin has few global settings that can be changed using options. Most of them are switches that allow to turn off some unwanted functionalities. For example, you can turn off search command if it’s not needed.

Option Type Description Default value
Enabled Boolean When true, then Virtual containers are enabled. true
DatabaseConfigurationsEnabled Boolean When true, then containers can be configured using admin plugin.
When all configurations are from code, this option can be set to false to improve loading configurations performance.
CustomIconsEnabled Boolean When true, then container icon is replaced with custom icon. true
SearchCommandEnabled Boolean When true, then search commands are available in page tree. true

Options can be configured using configuration module.

In admin mode plugin there is “Show plugin information” link where you can check current configuration.

Show options in admin mode

Using virtual containers

Below is an animated GIF showing how virtual containers work

Using virtual containers

The code is available as nuget package.