Try our conversational search powered by Generative AI!

IPartialRouter and Catalog Content


Versions: EPiServer CMS 10.10 - EPiServer Commerce 11.1

I'm working on a commerce website that has both product pages, as well as a "support center" in which the same product should be displayed.

I have a normal Hierarchical router setup for commerce content

CatalogRouteHelper.MapDefaultHierarchialRouter(RouteTable.Routes, false);

I also want to retain that same structure under the "Support Center" page, so I have:

routes.RegisterPartialRouter(new SupportCenterCatalogNodeRouter(() => supportCenterContentReference, commerceRootNode, enableOutgoingSeoUri: true));

"SupportCenterCatalogNodeRouter" is just IPartialRouter. It's a direct copy of the EPiServer-provided "HierarchicalCatalogPartialRouter". You can find it at the "EPiServer.Commerce.Routing" namespace.

This part works great, I am able to browse my products using both of these URLs:

  1. /support/commerce-category/commerce-sub-category/product-name
  2. /browse/commerce-category/commerce-sub-category/product-name

Now for the hard part:

What this doesn't allow for is the ability to use two different MVC actions (or different controllers) to render that same catalog content differently. In an attempt to accomplish this, I've added the following lines inside of my SupportCenterCatalogNodeRouter.

Here's the method:

public virtual object RoutePartial(SupportCenterPage content, SegmentContext segmentContext)
      if (segmentContext == null) return null;

      if (!content.ContentLink.CompareToIgnoreWorkID(this.RouteStartingPoint))
           return null;

      SegmentPair nextValue = segmentContext.GetNextValue(segmentContext.RemainingPath);

      if (string.IsNullOrEmpty(nextValue.Next))
           return null;

            CultureInfo cultureInfo = string.IsNullOrEmpty(segmentContext.Language) ? ContentLanguage.PreferredCulture : CultureInfo.GetCultureInfo(segmentContext.Language);
            CatalogContentBase contentRecursive = this.GetCatalogContentRecursive(this.CommerceRoot, nextValue, segmentContext, cultureInfo);

     if (contentRecursive != null)
           segmentContext.RoutedContentLink = contentRecursive.ContentLink;

           // -- Between these lines is the only code changed from the default hierarchical router --
           segmentContext.SetCustomRouteData("action", "Support");

           var httpContext = this._httpContextAccessor();

           httpContext.Request.RequestContext.SetCustomRouteData("action", "Support");
           httpContext.Request.RequestContext.RouteData.Values.Add("action", "Support");
           // -- Between these lines is the only code changed from the default hierarchical router --


   return contentRecursive;

None of the above attempts at setting the action route parameter seem to work, the Index method is called when loading these pages. I can debug and confirm that inside the Action method, the RequestContext.RouteData has my custom "action" route value, but it's clearly not taken into consideration before routing to the Index method.


I know that I can manually redirect to the "Support" method within these controllers if I see the RouteData["action"] is set to "Support", but I feel like there has to be a more official way. I must be missing something.

Any help is appreciated, I can provide additional/full code if necessary, but this should suffice.

Edited, Oct 18, 2017 3:30

If I understand it correctly what you are asking about is what Episerver refers to as TemplateDescriptors.

They don't really support having different actions for rendering the same content in different ways but they do support different controllers.

It is quite an advanced concept but i would suggest you start reading here:

Oct 18, 2017 14:56

Thanks for the response Erik.

Unfortunately, I'm not finding a way to use the TemplateDescriptor to decorate the second controller and choose that template when rendering content under the partial router described above. I am mainly only seeing ways of tagging content partials for rendering content area items. Was there a way you had in mind of selecting the template that should be used based on the partial node it used to route the request?

Edited, Oct 18, 2017 20:37

I have never implemented something like this myself, we normally don't recommend having seperate URLs for "support". Often we point out the advantages of support personnel getting exactly the same view as the customer through impersonation or we control the extra support features through groups to limit its availability based on user.

Back to the question, my first thought would be if there isn't a way to add a tag to the routedata inside the partial router to achieve the same template selection.

Alternatively i was thinking of attempting to use display channels:

Oct 19, 2017 12:41

Ah, okay. I understand your concern about support/customer view, in this case let's pretend the support center provided a completely different template showing post-purchase targeted content (aka Firmware Updates for a Router you bought, or User Manuals). In this case, since both are visitor-targeted just displaying different template/sections of content based on the URL you visit (even though it's a single Product entry), the suggestion based on personalization may not work.

I've moved to use Display Channels until I can find a better resolution. For anyone with a similar request, this is what I've used for now.

Inside my overridden Hierarchical Router shown above:

if (contentRecursive != null)
      segmentContext.RoutedContentLink = contentRecursive.ContentLink;

      this._httpContextAccessor().Request.RequestContext.SetCustomRouteData(SUPPORT_CENTER_ROUTED_DATA_TOKEN_KEY, true);

My display channel:

public class SupportCenterDisplayChannel : DisplayChannel
	public const string RenderingTag = "Support Center";

	public override bool IsActive(HttpContextBase context)
		var requestParamValue = context.Request.RequestContext.GetCustomRouteData<bool>(SupportCenterCatalogNodeRouter.SUPPORT_CENTER_ROUTED_DATA_TOKEN_KEY);

		return requestParamValue;

	public override string ChannelName
		get { return RenderingTag; }

The downside of this approach is that "Support Center" is a display channel across the entire site, as display channels are normally reserved for rendering differently based on a target device/environment like "Mobile" or for whitelabeling. The commerce content is the only part of the site that has a renderer setup for the "Support Center" display channel, making the display channel a little odd to be shown in the Editor Interface for all content.

I don't think Display Channels was the correct way to accomplish the task, but for now this works and makes for an easy way to not only give us separate templates per routed path, but also an editor-previewable display channel to see products as they would appear in the Support Center.

I believe CMS solves for this using RouteTable's extension for "MapContentRoute()" which would allow me to do something similar to:

        name: "SupportCenterCMSRoute",
        url: "{language}/support/{node}",
        defaults: new { action = "Support" },
        parameters: routingParameters);

Which would give all CMS content an optional prefix of /support/<normal_url_path> and automatically route that content to the default action 'Support'.

Unfortunately that doesn't exist for Commerce. I'd love to see it though.

Thanks for the help!

Edited, Oct 19, 2017 23:10
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.