1

I'm trying to find out the best-performance way to make personalized sidebar menus. Each link has conditions that determine whether or not it should be included in or excluded from the menu.

I understand that I could define different groups of links depending on the situation and include/exclude them in that way (and need 1 rendering for a menu) but that could lead to endless combinations of links so that's not really editor-friendly. Alternatively, I could make every link a separate rendering, but that might also not be the most performant (or is it?). Item-rendering doesn't support personalisation as far as I know.

The conditions on whether to add the menu option is driven by backend data. This could be the status of a purchase or whether the customer already had a certain product. But basic triggers like showing interest by visiting a productpage can also be a future use case. Each link could have different conditions.

Suggestions on the best solution for this use-case would be appreciated.

2
  • Can you share a bit on what controls, whether a menu item should appear or not? like if it's driven by user roles, purchase history or something along those lines.
    – Mark Cassidy
    Commented Sep 13, 2017 at 20:37
  • I have added a bit more context on the triggers/conditions
    – Esther
    Commented Sep 13, 2017 at 21:01

2 Answers 2

1

This calls for the Rules Engine

While I wouldn't normally build up a menu based on child items (rather; I would make up a separate navigational structure for this), your use case might just be the exception to the rule.

Because of the flexibility you express, in determining whether an items should be shown or not, I believe your best bet would be to invoke the Sitecore Rules Engine.

In a pseudo like manner, your code would execute something like this:

for-each menuelement in menuelements
{
    execute "Rules" field
    if result is "include"
        display menuelement
}

The reason I pseudocode this is, that setting up a custom rules field requires a fair amount of legwork; more than I feel comfortable I can hand-compile ad-hoc in this response.

But I can give you some pointers.

What it would take

If you haven't used the Rules Engine before; it is basically what drives e.g. Personalisation on Sitecore. It allows you an Outlook-rule-filter like interface where you can define flexible variable rules. You, being the Content Editor user.

You would need to allow for a mix of pre-baked conditions and presumably some custom ones you construct yourself.

Pre-baked ones could be along the lines of "if the current user has triggered the visited campaign page goal" or "if the current user matches the premium purchaser pattern".

You could then add to this; some of your own. "if the current user has previously made a purchase in the premium category" and so on.

You would then add a Rule field to each of your menu items (child elements, if you must) and your menu generation code would execute the rules on each of them, to determine whether to show the element or not.

An example custom Condition for the Rules Engine

It's not even that complicated. Conditions are very light weight; where most of the heavy UI lifting is done by the Sitecore Rules Engine framework.

As an example:

namespace Website.RulesEngine
{
    public class VariableTemplatesAllowed<T> : IntegerComparisonCondition<T> where T : RuleContext
    {
        public string TemplateId { get; set; }

        protected override bool Execute(T ruleContext)
        {
            return Compare(ruleContext.Item.GetChildren(ChildListOptions.IgnoreSecurity).Count(i => i.TemplateID.ToString().Equals(TemplateId)));
        }
    }
}

In your case, likely you would grab the current user and query against an order index or something similar. And in the case of Sitecore standard conditions, they wouldn't require code at all.

I did a presentation in January about the Rules Engine, with more examples and a full setup. You can refer to it here:

1

Sitecore's personalization is a fantastic and powerful feature, but it can require special performance considerations depending on how you use it.

Code-Based Approach

The first thing that you should ask yourself is do my content authors really need to be able to control the personalization of these menus themselves? If the answer is yes, then feel free to skip the rest of this approach. Otherwise, read on.

The fastest possible solution on all visits that you could implement would be a purely code-based solution. Obviously, I'm not going to recommend that you hard-code links or IDs, but what you can do is put checkbox fields on your links that say whether they display if a predefined condition is met. This is not a rule-based or other dynamic condition; think simple, like "Administrators only" or something similar that your application can be made aware of without having to run rules or other dynamic logic.

If you can make a code-based solution work for your use-case then this will likely be the most performant solution on every visit.

Separate Renderings per Link Approach

In the case that you mentioned, so long as your menus have 10 or fewer links each and only display on each page once, my recommendation would be that you use a separate rendering for each of the links and personalize the rendering to hide (be excluded from the menu) should the exclusion conditions pass. This will likely be the simplest solution for your content authors, and should not have any noticeable impact on page load speed. This solution is, however, less performant than using groups of links.

Groups of Links with One Rendering Approach

If you have more than 10 links or if you display your menu multiple times per page, my recommendation would be that you configure groups of links to display for a single rendering. This should be less rules for Sitecore to have to execute when it renders the menu, so that should save you on performance, but it does cost you in the editor-friendliness category.

Cheating and Customizing Sitecore's Caching Approach (Top Choice)

The problem with the personalization-based approaches isn't usually the performance cost of rendering the data (which can be expensive for renderings with lots of business logic), but rather the performance cost of running the rules to figure out what data should actually be rendered. If you need to have editor-friendly personalization control and still have a performant solution, then my advice is to cheat and customize Sitecore's Caching so that you can cache the result once you've rendered it once.

The idea with this approach is that you can add Sitecore Caching support to enable to you cache based on the same conditions that you use for personalizing your renderings. This is a bit of a confusing concept, but it may be easier to wrap your mind around it with an example. Consider that you are personalizing based on favorite color: Red, Blue, Green, Pink, Purple, Orange or Other. The first thing that you do is implement the Separate Renderings Per Link Approach, adding one rendering per link and personalizing each rendering to hide if the exclusion conditions are met. You then customize your Sitecore Caching to include a setting for varying your caching by favorite color. You can call this setting Vary By Favorite Color and include it in the caching options of the rendering. You would then extend the GenerateCacheKey processor to add a key for the current visitor's (or the current user, if favorite color is stored in the User Profile) favorite color, if available. The result would be that the first time the rendering is requested for one of the colors, the personalization rules would run and the page would take a little longer to load. However, on all subsequent loads for a visitor/user with that favorite color, the personalization rules would be skipped, as the output would have already been cached.

The following code sample will get you what you are looking for:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Sitecore.Analytics;
using Sitecore.Data;
using Sitecore.Diagnostics;
using Sitecore.Layouts;
using Sitecore.Mvc.Extensions;
using Sitecore.Mvc.Pipelines.Response.RenderRendering;
using Sitecore.Mvc.Presentation;
using Sitecore.Rules.ConditionalRenderings;

namespace MyNamespace.Pipelines.MvcRenderRendering
{
    public class GenerateCacheKey : Sitecore.Mvc.Pipelines.Response.RenderRendering.GenerateCacheKey
    {
        /// <summary>
        /// Holds the name of the Sitecore checkbox field that controls whether the cache key 
        /// should vary by favorite color of the user/visitor
        /// </summary>
        private const string VaryByFavoriteColorFieldName = "VaryByFavoriteColor";

        /// <summary>
        /// Generates the cache key with all caching variance parts added
        /// </summary>
        protected override string GenerateKey(Rendering rendering, RenderRenderingArgs args)
        {
            var cacheKey = base.GenerateKey(rendering, args);

            // if set to vary caching by favorite color add the favorite color part to the key
            if (IsCacheVaryByFavoriteColor(rendering))
            {
                cacheKey += GetFavoriteColorPart();
            }

            return cacheKey;
        }

        /// <summary>
        /// Checks if the rendering is set to vary caching by favorite color
        /// </summary>
        private bool IsCacheVaryByFavoriteColor(Rendering rendering)
        {
            // so long as the VaryByFavoriteColor checkbox field exists and is checked, we should be varying by favorite color
            return rendering.RenderingItem.InnerItem[VaryByFavoriteColorFieldName] != null && rendering.RenderingItem.InnerItem[VaryByFavoriteColorFieldName] == "1";
        }

        /// <summary>
        /// Gets the part of the cache key that will vary by favorite color
        /// </summary>
        public virtual string GetFavoriteColorPart()
        {           
            ...logic for getting favorite color from user profile or from tracking session...

            return !string.IsNullOrEmpty(favoriteColor)
                ? $"_#favoriteColor:{favoriteColor}" // adds a string unique to the favorite color to the cache key
                : string.Empty;
        }
    }
}

Note that the above processor should replace the native GenerateCacheKey processor and you will need to add the VaryByFavoriteColor field in Sitecore. To add the field, create a new template, named something like "Extended Caching", and add a field section to it, named Caching. Add a checkbox field to the new section, named VaryByFavoriteColor and set the field's title text to "Vary By Favorite Color" (capital "B" in "By" for consistency with Sitecore). Finally, add your new template to the Base Templates of the /sitecore/templates/System/Layout/Sections/Caching template. This is one of the few times that it is appropriate to modify a native Sitecore template.

Once you have completed the above, you should be able to set global caching settings on the rendering.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.