Sitecore MVC Controller cache vs error handling

I faced a problem where I need to control the Sitecore html cache from within a controller action. There are probably many scenarios where this is needed. In my particular case, the output from the controller is cacheable in the Sitecore html cache, but my controller action is also dependent on a third party system. The communication with this system could sometimes fail. Essentially it means that if the controller fails, I don’t want the html output to be cached either. If it is cached, as it is by default, my error message will stay there until the html cache is cleared, regardless if the third party system is online or not.

So the scenario is quite simple, but it turned out to be quite complex to solve. I found a rather hackish way to do it, by hooking into the <mvc.renderRendering< pipeline, like this:

public class ControllerCacheDisabler : RenderRenderingProcessor
{
	public static void DisableCache()
	{
		var id = Sitecore.Mvc.Presentation.RenderingContext.Current?.Rendering?.UniqueId ?? Guid.Empty;
		var ctx = HttpContext.Current;
		if (id != Guid.Empty && ctx != null)
		{
			ctx.Items[$"HtmlOutputCacheDisabler_{id}"] = "true";
		}
	}

	public override void Process(RenderRenderingArgs args)
	{
		Assert.ArgumentNotNull(args, "args");

		if (HttpContext.Current == null || !args.Cacheable || string.IsNullOrWhiteSpace(args.CacheKey))
		{
			return;
		}

		var id = args.Rendering.UniqueId;
		if (!string.IsNullOrWhiteSpace(HttpContext.Current.Items[$"HtmlOutputCacheDisabler_{id}"] as string))
		{
			args.Cacheable = false;
		}
	}
}

From within my Sitecore controller action, I can call the static ControllerCacheDisabler.DisableCache() method and it’ll register that this particular rendering shouldn’t be cached. Then I add this processor just before the AddRecordedHtmlToCache processor like this:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:x="http://www.sitecore.net/xmlconfig/">
	<sitecore>
		<pipelines>
			<mvc.renderRendering>
				<processor
					patch:before="processor[@type='Sitecore.Mvc.Pipelines.Response.RenderRendering.AddRecordedHtmlToCache, Sitecore.Mvc']" 
					type="Your.Namespace.ControllerCacheDisabler, Your.Assembly" />
			</mvc.renderRendering>
		</pipelines>
	</sitecore>
</configuration>

Essentially this means that a cached output of the controller will be rendered. If it’s not cached, it’ll normally execute. If successful, it’ll be cached and if unsuccessful it’ll not be added to the cache.

Perhaps I’ve missed something obvious. This solution feels like over complicating things. Dealing with errors and exceptions from Sitecore MVC Controllers and have the result non-cached doesn’t seem to be a very uncommon scenario. Perhaps there is a way to get the RenderRenderingArgs object from a controller, so I can just set the Cacheable property to false directly in the controller.

Please leave a comment if you know a better solution to this.

Leave a Reply