Skip to main content

Outline

At a glance:
  • SEO Extension: Maps URL segments to non-page data like external databases or the Dynamic Data Store (DDS)
  • Core Interface: Uses IPartialRouter to resolve incoming requests and generate outgoing virtual paths for dynamic content
  • State Management: Leverages IHttpContextAccessor to pass routed data to controllers, bypassing immutable route values in CMS 12
  • Performance: Requires robust caching and segment validation to maintain site speed and prevent routing loops

In complex enterprise architectures, content often exists outside the standard CMS page tree. Optimizely CMS 12 provides Partial Routing as a robust mechanism to map specific URL segments to dynamic data sources such as external databases, product catalogs, or the Dynamic Data Store (DDS). This enables search-engine-friendly (SEO) URLs for non-page content while maintaining the context of a "parent" CMS page.

1. Technical Purpose and Use Cases

Standard routing in Optimizely resolves URLs by matching segments to the IContent hierarchy. Partial routing extends this logic by allowing a "pointing" content item (often a page) to handle the remaining URL segments manually. Common scenarios include:

  • Dynamic External Data: Routing to news articles, blog entries, or product details stored in an external SQL database or DDS.
  • Hierarchical Dynamic Routing: Managing complex URL structures like /news/category/article-name/ where /news/ is a CMS page and the rest is dynamic.
  • Pagination and Filtering: Handling URL segments purely for data filtering without creating physical child pages in the CMS tree.

2. The IPartialRouter<TContent, TRoutedData> Interface

The core of partial routing is implementing the IPartialRouter interface from the EPiServer.Core.Routing namespace. It uses two generic type parameters:

  • TContent: The CMS content type that acts as the routing anchor - for example, NewsContainerPage.
  • TRoutedData: The non-page data type to which the URL resolves - for example, a NewsContent object from an external store.

Core Interface Methods

Select a method to expand and read the details.

RoutePartial

Invoked during incoming request resolution. It parses the URL segments following the resolved TContent anchor and determines which TRoutedData instance matches them. It must update RemainingSegments on the context to signal how much of the URL was consumed.

GetPartialVirtualPath

Invoked during outgoing URL generation. It constructs a friendly URL based on a TRoutedData instance and its associated TContent anchor, enabling standard CMS helpers like Url.ContentUrl to generate accurate paths for dynamic objects.

3. Implementation Patterns for CMS 12

In CMS 12 (.NET 6/8), partial routing integrates with the underlying ASP.NET Core routing system. A critical change from legacy versions is the immutability of UrlResolverContext.RouteValues - routed data must be passed via alternative mechanisms.

Resolving Data in RoutePartial

The implementation reads URL segments from SegmentContext and updates the context to indicate how much of the URL was consumed.

C#
public object RoutePartial(NewsContainer content, UrlResolverContext urlResolverContext) { // Retrieve the next segment (e.g., 'sports') var categorySegment = urlResolverContext.GetNextSegment(); if (string.IsNullOrEmpty(categorySegment.Next)) { return null; } // Retrieve the article segment (e.g., 'local-match') var articleSegment = urlResolverContext.GetNextSegment(categorySegment.Remaining); // Logic to fetch content from an external store var newsItem = _newsStore.GetArticle(categorySegment.Next, articleSegment.Next); if (newsItem != null) { // Mark the segments as handled by setting the remaining path urlResolverContext.RemainingSegments = articleSegment.Remaining; return newsItem; } return null; }

Passing Data to Controllers

Since RouteValues cannot be directly modified in CMS 12, use IHttpContextAccessor to store routed data in the request's Items dictionary:

C#
// Inside RoutePartial _httpContextAccessor.HttpContext.Items["RoutedNewsItem"] = newsItem;

The corresponding controller retrieves the data from the same Items dictionary:

C#
public IActionResult Index(NewsContainer currentContent) { var newsItem = _httpContextAccessor.HttpContext.Items["RoutedNewsItem"] as NewsContent; return View(new NewsViewModel(currentContent, newsItem)); }

4. System Registration

Registration in CMS 12 is handled through the DI container - typically in the ConfigureServices method in Program.cs or a dedicated IServiceCollection extension method.

Note: Partial routers should be registered as Singleton since they hold no per-request state - the routed data is passed via HttpContext.Items, not the router instance itself.

C#
public void ConfigureServices(IServiceCollection services) { // Register the router implementation services.AddSingleton<IPartialRouter, MyCustomPartialRouter>(); }

5. Generating Outgoing URLs

To ensure links to dynamic content are SEO-friendly, the GetPartialVirtualPath method must be correctly implemented. This allows standard CMS helper methods like Url.ContentUrl to generate accurate paths for dynamic objects rather than falling back to raw IDs.

C#
public PartialRouteValues GetPartialVirtualPath(NewsContent content, UrlGeneratorContext urlGeneratorContext) { return new PartialRouteValues { // Reference to the CMS anchor page BaseContentLink = _options.NewsContainerRoot, // The dynamic segments to append to the anchor URL PartialPath = $"{content.Category}/{content.UrlSegment}" }; }

6. Performance and Best Practices

  • Caching: Partial routers are executed on every matching request. Ensure all lookup logic (database queries, external API calls) is backed by a robust caching strategy using IContentCacheKey or ISignaledCache for automatic invalidation.
  • GUID Consistency: When routing to content that implements IContent but is not in the page tree, ensure it has a stable, unique GUID to support routing and Optimizely Graph indexing.
  • Segment Management: Always check for null or empty segments to avoid infinite routing loops or unnecessary database hits for malformed or crawler-generated URLs.

Pro tip: In high-traffic environments, wrap your data lookup in a memory cache keyed on the URL segment. An uncached RoutePartial that hits a database on every request will quickly become a bottleneck under load.

Conclusion

Partial Routers provide a sophisticated method for merging external and dynamic data into the standard Optimizely CMS URL structure. By leveraging IPartialRouter and modern .NET DI, developers can create highly scalable, performant, and SEO-optimized navigation systems that exist beyond the traditional content tree boundaries.