Implementing Cache Settings in Sitecore Content Hub

Hello, Sitecorians!👋 In this blog post, I will guide you through a user scenario where we implement cache control based on access levels to manage how assets are cached. We'll focus on setting up cache control for two types of access: Public, Client and Employee. 


Introduction to Caching in Sitecore Content Hub 

Sitecore Content Hub is a powerful content management system that allows organizations to manage, store, and distribute digital assets efficiently. One of the key features of Sitecore Content Hub is its ability to implement caching strategies, which can significantly improve content delivery times and reduce server load.


Understanding Cache Control Headers 

Cache control headers are crucial in dictating how browsers and CDNs cache content. The Cache-Control header allows you to define the caching policies for your digital assets. The policies we'll focus on are public, private, and max-age. 

public: Indicates that the response can be cached by any cache. 

private: Indicates that the response is intended for a single user and must not be stored by shared caches. 

max-age: Specifies the maximum amount of time (in seconds) that the asset can be cached.


Scenario: Implementing Cache Settings Based on Access Levels 

In this scenario, we aim to implement cache control settings in Sitecore Content Hub to differentiate between assets based on their access levels. The goal is to ensure that public assets are readily available for longer periods, while restricting cache duration for assets with restricted access.

 

Public Access Assets 

Assets with a Public access level should be widely available and cacheable by browsers and CDNs to optimize delivery. For these assets, we will set the cache control to public, max-age=86400. This setting allows the assets to be cached publicly for 24 hours (86400 seconds), reducing the need for frequent server requests. 


Client and Employee Access Assets 

For assets with Client or Employee access levels, we need to enforce stricter cache control to ensure that these assets are not cached in shared environments or by unauthorized users. We will set the cache control to private, max-age=900 for these assets. This setting ensures that the assets are cached privately, only in the user's browser, for 15 minutes (900 seconds). This approach strikes a balance between performance and security for sensitive assets.


Implementing the Custom Cache Control Logic

To implement this differentiated cache control strategy, I utilized the GetPageRendering pipeline within Sitecore. This pipeline is critical for adjusting the rendering process of pages and assets, providing an ideal hook to apply custom logic for setting cache headers based on the access level of the requested asset.

This is a process that runs every time a page is loaded. We customize this process to check who's asking for content and apply the right caching rules based on the content's type (public or private). It's like having a gatekeeper who decides how fast and to whom the content should be delivered.

Configuration Settings:

DisableBrowserCaching set to false: This setting enables browser caching, essential for leveraging the browser's cache to store and reuse responses, enhancing performance. We turn on a setting that lets web browsers remember content. This is important for making the website load faster for visitors by using previously stored content.

MediaResponse.Cacheability set to Public: This default setting facilitates the caching of media assets by public caches. However, our custom processor refines this setting by applying more restrictive cache control headers for assets with limited access, thus providing a tailored solution to balance performance with security requirements. We set a default rule to treat all content as public unless specified otherwise. This means content is generally stored for faster access, but our custom rules for private content ensure it remains secure.

Pipeline Code Implementation:

using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Mvc.Pipelines.Response.GetPageRendering;
using System;
using System.Web;
using WebApplication1.Helper;

namespace WebApplication1.Pipeline
{
    public class CustomBrowserCaching : GetLayoutRendering
    {
        public override void Process(GetPageRenderingArgs args)
        {
            Assert.ArgumentNotNull((object)args, nameof(args));
            Profiler.StartOperation("Update browser caching headers.");
            HttpContext context = HttpContext.Current;
            if (context == null) return;
            SetCacheHeaders(context);
            Profiler.EndOperation();
        }

        private static void SetCacheHeaders(HttpContext context)
        {
            var id = context.Request.QueryString["id"];
            var isValid = Guid.TryParse(id, out _);
            if (isValid)
            {
                Item assetItem = Sitecore.Context.Database.GetItem(id);
                if (assetItem != null)
                {
                    var accessLevel = assetItem.Fields["Access Level"].Value;
                    SetCacheHeadersByAccessLevel(context, accessLevel);
                }
            }

            else
            {
                var connector = new ContentHubConnector();
                var status = connector.GetAssetDetails(id);
                if (status != null)
                {
                    if (status.related_paths.AccessLevelToAsset?.Count > 0)
                    {
                        foreach (var accessLevelList in status.related_paths.AccessLevelToAsset)
                        {
                            foreach (var accessLevel in accessLevelList)
                            {
                                var getAccess = accessLevel.values["en-US"];
                                SetCacheHeadersByAccessLevel(context, getAccess);
                            }
                        }
                    }
                    else if (status.related_paths.AccessLevelToAsset == null)
                    {
                        SetCacheHeadersByAccessLevel(context, null);
                    }
                }
            }
        }

        private static void SetCacheHeadersByAccessLevel(HttpContext context, string accessLevel)
        {
            if (string.Equals(accessLevel, "Public", StringComparison.OrdinalIgnoreCase) || accessLevel == null)
            {
                context.Response.Cache.SetCacheability(HttpCacheability.Public);
                context.Response.Cache.SetMaxAge(TimeSpan.FromSeconds(86400));
            }
            else if (string.Equals(accessLevel, "Client", StringComparison.OrdinalIgnoreCase) || string.Equals(accessLevel, "Employee", StringComparison.OrdinalIgnoreCase)
                || string.Equals(accessLevel, "Client Login Required", StringComparison.OrdinalIgnoreCase)
                || string.Equals(accessLevel, "Employee Login Required", StringComparison.OrdinalIgnoreCase))
            {
                context.Response.Cache.SetCacheability(HttpCacheability.Private);
                context.Response.Cache.SetMaxAge(TimeSpan.FromSeconds(900));
            }
        }
    }
}


Code Snippet for Content Hub Connector Class:

To fetch asset details from Sitecore Content Hub, I have developed a ContentHubConnector class. This class is responsible for authenticating against the Content Hub's API and retrieving asset information. Authentication is achieved by sending credentials to the /api/authenticate endpoint, which returns a token used in subsequent API calls to fetch asset details.

using log4net.Repository.Hierarchy;
using Microsoft.Ajax.Utilities;
using Newtonsoft.Json;
using Sitecore.ApplicationCenter.Applications;
using Sitecore.StringExtensions;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Web;
using System.Web.Helpers;
using WebApplication1.Models.ContentHubModel;

namespace WebApplication1.Helper
{
    public class ContentHubConnector
    {
        public AssetDetailsResponse GetAssetDetails(string assetId)
        {
            if (!string.IsNullOrEmpty(assetId))
            {
                var token = GetAccessToken();
                if (!string.IsNullOrEmpty(token))
                {
                    var httpWebRequest = (HttpWebRequest)WebRequest.Create(https://{{www.marketingcontenthub.com}} + "/api/entities/" + assetId);
                    httpWebRequest.ContentType = "application/json";
                    httpWebRequest.Method = "GET";
                    httpWebRequest.Headers.Add("X-Auth-Token", token);
                    var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
                    using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
                    {
                        var result = streamReader.ReadToEnd();
                        if (!string.IsNullOrEmpty(result))
                        {
                            var response = JsonConvert.DeserializeObject<AssetDetailsResponse>(result);
                            return response;
                        }
                    }
                }
            }
            return null;
        }

        public string GetAccessToken()
        {
            var httpWebRequest = (HttpWebRequest)WebRequest.Create(https://{{www.marketingcontenthub.com}} + "/api/authenticate");
            httpWebRequest.ContentType = "application/json";
            httpWebRequest.Method = "POST";
            using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
            {
                string json = "{\"user_name\":" + "\"" + "your user name" + "\"" + "," +
                              "\"password\":" + "\"" + "your password" + "\"" + "}";

                streamWriter.Write(json);
            }

            var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
            using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
            {
                var result = streamReader.ReadToEnd();
                if (!string.IsNullOrEmpty(result))
                {
                    var response = JsonConvert.DeserializeObject<TokenStatus>(result);
                    return response.Token;
                }
            }
            return string.Empty;
        }
    }
}


Code Snippet for Model Class:

The model class TokenStatus is created to store the authentication token necessary for API requests, ensuring secure communication with Sitecore Content Hub. The AssetDetailsResponse class captures comprehensive details about assets, including metadata and relations. Additional classes like Properties, RelatedPaths, and Renditions break down the asset's information into manageable segments, facilitating easy access and manipulation of specific asset properties, categorizations, and available file formats.

public class TokenStatus
{
    public string Token { get; set; }
}

public class AssetDetailsResponse
{
    public int id { get; set; }
    public string identifier { get; set; }
    public Properties properties { get; set; }
    public RelatedPaths related_paths { get; set; }
    public Renditions renditions { get; set; }    
}

public class Properties
{
    public string FileName { get; set; }
    public string Title { get; set; }
    public Dictionary<string, string> Description { get; set; }
}
public class RelatedPaths
{
    public List<List<CountryToAsset>> CountryToAsset { get; set; }
    public List<List<AccessLevelToAsset>> AccessLevelToAsset { get; set; }
}

public class Renditions
{
    public List<RenditionItem> downloadOriginal { get; set; }
    public List<RenditionItem> downloadAlternative { get; set; }
    public List<RenditionItem> metadata { get; set; }
    public List<RenditionItem> pdf { get; set; }
    public List<RenditionItem> preview { get; set; }
    public List<RenditionItem> bigthumbnail { get; set; }
    public List<RenditionItem> thumbnail { get; set; }
    public List<RenditionItem> video_mp4 { get; set; }
}

public class RenditionItem
{
    public string href { get; set; }
}


Add the Configuration Patch File: 

After implementing the custom caching logic in our pipeline, we need to ensure Sitecore applies these changes. To do this, add a config patch file named zzz.CacheSettings.config under the App_Config/Include/ directory of your Sitecore solution. Placing the file in this location with a 'zzz' prefix ensures it loads last, overriding any previous settings as needed. This patch file is crucial for applying our custom cache settings across the Sitecore application, ensuring our caching logic works as intended for different access levels of content.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
    <sitecore>
        <settings>
            <setting name="DisableBrowserCaching">
                <patch:attribute name="value">false</patch:attribute>
            </setting>
            <setting name="MediaResponse.Cacheability">
                <patch:attribute name="value">Public</patch:attribute>
            </setting>
        </settings>
        <pipelines>
            <mvc.getPageRendering>
                <processor patch:after="*[@type='Sitecore.Mvc.Pipelines.Response.GetPageRendering.GetLayoutRendering, Sitecore.Mvc']" type="WebApplication1.Pipeline.CustomBrowserCaching, WebApplication1">
                </processor>
            </mvc.getPageRendering>
        </pipelines>
    </sitecore>
</configuration>


Results: 

To verify your cache control settings are correctly applied, you can use browser developer tools. Simply open the developer tools in your browser, navigate to the Network tab, and reload your webpage. Click on an asset to inspect its headers, where you'll find the Cache-Control settings reflecting your configurations.



Conclusion:

Implementing cache control based on access levels in Sitecore Content Hub allows for optimized content delivery while maintaining necessary security and privacy for sensitive assets. By customizing the GetPageRendering pipeline and applying specific configuration settings, we ensure that our content is delivered efficiently and securely to the appropriate audience. 

As I wrap up this guide, I encourage you to continue exploring and learning. The world of Sitecore Content Hub is vast, with many more opportunities to enhance your website's performance and user experience. Stay tuned for my next blog, where we'll dive into more insights and tips to help you on your Sitecore journey. Keep exploring and learning!
Post a Comment (0)
Previous Post Next Post