Blocks caching (including edge cases).


Hi all,

We are planning a caching strategy for our project.
Caching whole pages is not an options for us.
We are thinking of caching blocks.

Currently, we are trying to use

[OutputCache(VaryByParam = "currentBlock", VaryByCustom = "language", Duration = 60 * 15)]

with ToString on block overriden like this:

 public override string ToString()
            IContent content = this as IContent;
            IChangeTrackable changetrackable = this as IChangeTrackable;

            var test = EPiServer.ServiceLocation.ServiceLocator.Current.GetInstance();

            if (content != null && changetrackable != null)
                return $"{content.ContentLink}-{changetrackable.Saved.Ticks}-{EPiServer.Editor.PageEditing.PageIsInEditMode}";

            return base.ToString();

and in Global.asax:

 public override string GetVaryByCustomString(HttpContext context, string custom)
            if (custom == "language")
                return ContentLanguage.PreferredCulture.Name;
            return base.GetVaryByCustomString(context, custom);

However, this solution doesn't support the following cases:

  1. blocks in blocks - in case inner block is changed, cache for parent block is not flushed
  2. blocks in RTE property of page/block - same issue as in 1.
  3. Personalized content inside block.

For 1. I have an idea of getting parent blocks and republish them on inner block publish like this:

if (e.Content is IInnerBlockMarker)
                var repository = ServiceLocator.Current.GetInstance();
                var parentBlocks = repository.GetReferencesToContent(e.ContentLink, false).
                    Select(r => r.OwnerID).Select(id => repository.Get(id)).ToArray();
                var filter = new FilterPublished();
                var blocks = parentBlocks.Select(s => s as CacheableBlock).Where(s => s != null).Distinct().ToArray();
                Array.ForEach(blocks, (b) =>

                    var bToUpdate = b.CreateWritableClone() as IContent;

                    (bToUpdate as IChangeTrackable).Saved = DateTime.Now;
                    repository.Save(bToUpdate, EPiServer.DataAccess.SaveAction.Publish, EPiServer.Security.AccessLevel.Publish);

For 3. I found the solution in the Internet which basically is an extenstion of custom string:

if (custom == "visitorGroupHash")
                var helper = new VisitorGroupHelper();
                StringBuilder sb = new StringBuilder();
                foreach (var role in GetRolesForContext(context))
                    sb.Append(helper.IsPrincipalInGroup(context.User, role));
                return sb.ToString();

My wish is to not do more harm than good. My solution for 1. may take a long time on publish if inner block has a lot of parents and I have to also take into account whether the parent block has scheduled publishing or not. And what about block inside a block inside a block? My solution doesn't support that.

My solution for 3. may clutter the memory with multiple versions for each block.

Do you know better output cache strategy?

Thank you in advance for your help.

Edited, Aug 23, 2016 11:46

I normally place my caching above expensive methods (like getting data from external services or tough calculations). Caching normal blocks and pages in Episerver is normally not worth the trouble if you are not getting 1000s of pages in some kind listing / filtering operation. Episerver already does that for you in the background. If you get a page / block from repository or list children etc it will be cached 99.9% of the time. So you get very little actual benefit for a lot of nasty caching code and cache invalidation, like you are thinking about, can be tricky to get correct. So my suggestion is to only cache calls that you know will be problematic.

Then you can also use the standard caching of html in Episerver that you can turn on in web.config.

Aug 23, 2016 15:02
* 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.