How do I allow editors to Dynamically update caching for assets that are updated frequently

  • Page Owner: Not Set
  • Last Reviewed: 2020-05-18

During COVID-19 I received a request from Lurie Children's about an issue they were having. They were linking to PDF documents that they had in Episerver to provide users with information about COVID. These documents were being updated frequently. The problem was that the documents were being cached, so when new information was uploaded it wasn't being shown to users until the cache expired. The solution I came up with was to allow editors to manually turn caching on or off for individual PDF files.

NOTE: I believe this solution is specific to DXP and Cloudflare. https://world.episerver.com/documentation/developer-guides/digital-experience-platform/development-considerations/cdn-recommendations/

Caching in Cloudflare is managed via the cache-control header. This can be turned on or off (no-cache or public) via the HttpContext.Response.Cache.SetCacheability method.

In order to to set this header when an asset is request you need to implement a Custom Media Handler. https://world.episerver.com/documentation/developer-guides/CMS/Content/assets-and-media/Media-types-and-templates/

In the situation with Lurie we have our .pdf, .doc and .docx files under the model DocumentMedia, my custom media handler is essentially pulled stratight from the documentation with a few minor changes and looks like this:

public class CustomDocumentMediaHandler : BlobHttpHandler, IHttpHandler, IRenderTemplate<DocumentMedia>
{
    private readonly IContentRouteHelper _contentRouteHelper;
    private readonly IAccessDeniedHandler _accessDeniedHandler;

    // we need to provide a parameterless constructor
    public CustomDocumentMediaHandler(IContentRouteHelper contentRouteHelper, IAccessDeniedHandler accessDeniedHandler)
    {
        _contentRouteHelper = contentRouteHelper;
        _accessDeniedHandler = accessDeniedHandler;
    }

    public CustomDocumentMediaHandler() : this(ServiceLocator.Current.GetInstance<IContentRouteHelper>(), ServiceLocator.Current.GetInstance<IAccessDeniedHandler>())
    {
    }

    public new bool IsReusable => false;

    protected override Blob GetBlob(HttpContextBase context)
    {

        //Get file content
        var content = _contentRouteHelper.Content;

        if (content == null)
        {
            throw new HttpException(404, "Not Found.");
        }

        //Check access
        if (!content.QueryDistinctAccess(AccessLevel.Read))
        {
            _accessDeniedHandler.AccessDenied(context);
        }

        //Cast to custom file
        var customFile = content as DocumentMedia;
        if (customFile?.BinaryData == null)
        {
            throw new HttpException(404, "Not Found.");
        }

        //Set caching policy
        if (customFile.TurnOffCaching)
        {
            context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
        }
        else
        {
            context.Response.Cache.SetCacheability(HttpCacheability.Public);
        }
        

        return customFile.BinaryData;
    } 

You can see that on the DocumentMedia model I added the property TurnOffCaching which when checked will render the cache-control: no-cache header value which will then turn off caching and force users load the latest document every time it's requested.

This can probably be expanded to allow editors to dynamically set the cache expiration as well via the context.Response.Cache.SetExpires method.

Comments

  • Unexpected surprise is that this file ties itself into the system, and there's no extra steps to make a service or anything similar.

Additional Posts

An update on this. This solution works but only for assets that haven't already been cached. If an asset has been cached you will need to go into the PaaS portal (DXP specific) and clear the Cloudflare cache for the whole site after selecting the property to set the no-cache header.

I asked Episerver if it was possible to get a API token so that we could maybe purge the cache for the asset on publish and this was their response as of 5/27/2020:

We do not currently allow Cloudflare access. The only options at this time are to purge through the PaasPortal or place a ticket with the request.

Interestingly enough Episerver suggests this or something similar to this as a solution as well:

https://github.com/episerver/EPiServer.CdnSupport