Skip to main content

Outline

At a glance
  • Retrieval Core: IContentLoader serves as the primary read-only interface, leveraging built-in object caching for efficiency.
  • Security First: FilterForVisitor ensures content visibility respects publication status, access rights, and active date ranges.
  • Pattern Choice: ViewComponents provide the standard architecture for recursive menu logic and breadcrumb generation.
  • Performance: Fragment caching in Razor prevents redundant execution of navigation logic on every request.

Navigation is the functional manifestation of a website's Information Architecture. In Optimizely CMS 12, menu generation is a decoupled process where technical teams leverage the Content API to retrieve, filter, and render hierarchical data. For high-performance PaaS environments, implementing efficient traversal logic and robust caching strategies is mandatory.

1. The Retrieval API: IContentLoader

For read-only navigation operations, IContentLoader is the primary interface. Unlike IContentRepository, which provides capabilities for CRUD operations, IContentLoader is optimized for retrieval and automatically participates in the Optimizely object cache.

Key Retrieval Methods

  • GetChildren<T>(ContentReference): Retrieves the immediate children of a specific node. This is the bedrock of level-by-level menu generation.
  • GetAncestors(ContentReference): Retrieves the path from the current node back to the root. This is primarily utilized for breadcrumb generation.
  • GetDescendants(ContentReference): Retrieves the entire subtree. While powerful, this should be used sparingly in navigation due to the potential for excessive database and memory overhead in large trees.

2. Technical Filtering: FilterForVisitor

Retrieving content from the tree is only the first step. To maintain security and editorial intent, the resulting collection must be processed through the system's filtering engine.

The Role of FilterForVisitor

The FilterForVisitor class performs critical security checks, ensuring that:

  • The content is in a "Published" state.
  • The current user has "Read" access to the content.
  • The content is within its "Active" time range (Start/Stop publish dates).

Respecting VisibleInMenu

Editorial governance is maintained through the built-in VisibleInMenu property. Developers must manually check this property during menu generation to allow editors to hide specific technical or landing pages from the primary navigation.

var children = _contentLoader.GetChildren<PageData>(ContentReference.StartPage); // Apply standard visitor filters and check UI visibility settings var filteredMenu = children .Where(x => x.VisibleInMenu) .FilterForVisitor();

3. ViewComponent Implementation Pattern

In CMS 12, the recommended pattern for modular UI elements like navigation is the ViewComponent. This allows for a clean separation between retrieval logic and Razor rendering.

Recursive Menu Logic

A scalable menu implementation often requires recursive logic to handle multi-level dropdowns or mega-menus.

public class MainMenuViewComponent : ViewComponent { private readonly IContentLoader _contentLoader; public MainMenuViewComponent(IContentLoader contentLoader) { _contentLoader = contentLoader; } public IViewComponentResult Invoke(ContentReference rootLink) { var model = new MenuViewModel { MenuItems = GetMenuItems(rootLink ?? ContentReference.StartPage) }; return View(model); } private IEnumerable<MenuItem> GetMenuItems(ContentReference parentLink) { var children = _contentLoader.GetChildren<PageData>(parentLink) .Where(x => x.VisibleInMenu) .FilterForVisitor(); return children.Select(x => new MenuItem { Page = x, // Recursive call for sub-level items Children = GetMenuItems(x.ContentLink) }); } }

4. Breadcrumb Generation

Breadcrumbs enhance UX and SEO by providing clear path indicators. Implementation utilizes the GetAncestors method to build the path from the current page up to the site's start page.

public IEnumerable<PageData> GetBreadcrumbs(ContentReference currentPageLink) { var ancestors = _contentLoader.GetAncestors(currentPageLink) .OfType<PageData>() .Reverse(); // Reverse to get Root -> Parent -> Current order // Optionally include the current page itself var breadcrumbs = ancestors.SkipWhile(x => x.ContentLink != ContentReference.StartPage); return breadcrumbs; }

5. Performance and Caching Strategies

Navigation components are often executed on every page request. In a PaaS environment, unoptimized navigation results in significant database strain.

Object Caching

By using IContentLoader, developers benefit from the built-in Optimizely object cache. However, for complex menus requiring significant computation (e.g., custom sorting or external metadata merging), implement explicit caching using ISignaledCache or IObjectInstanceCache.

Fragment Caching in Razor

For the final rendered HTML, leverage ASP.NET Core's cache tag helper. This prevents the entire ViewComponent logic from re-executing for every visitor.

&commat;* RECOMMENDED: Fragment caching for the menu output *&commat; <cache vary-by-user="true" vary-by-header="Cookie"> &commat;await Component.InvokeAsync("MainMenu", new { rootLink = ContentReference.StartPage }) </cache>

Conclusion

Creating effective navigation in Optimizely CMS 12 requires a synthesis of efficient retrieval via IContentLoader, strict filtering via FilterForVisitor, and optimized rendering with ViewComponents. By prioritizing the VisibleInMenu property and implementing robust fragment caching, technical teams ensure that site navigation remains an asset to both the user experience and the system's overall scalability.