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
Configuration Settings:
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:
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:
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:
<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>