Implement an Azure Redis Cache with Storage Blobs - Console C#

This tutorial shows how to implement Azure storage blobs in an Azure Cache for Redis using the LEADTOOLS JS Document Service Demo.

Overview  
Summary This tutorial covers how to use an Azure Redis Cache with Storage Blobs in a LEADTOOLS Document Library application.
Completion Time 30 minutes
Platform .NET Framework
IDE Visual Studio 2019, 2022
Runtime License Download LEADTOOLS

Required Knowledge

This implementation is a fully-fledged example that can be used in a production environment. It uses Azure Storage Blobs to store binary data instead of storing directly to the cache. This approach is suitable if documents are to be accessible from multiple servers. For an example of saving large binary data directly to the cache, refer to Implement an Azure Redis Cache.

Before any functionality from the SDK can be leveraged, a valid runtime license will have to be set.

For instructions on how to obtain a runtime license refer to Obtaining a License.

Open the Project

The functionality in this tutorial is built on the functionality of the .NET Document Service and JS Document Viewer. In order to open the project, navigate to <INSTALL_DIR>\Examples\Viewers\JS\DocumentViewerDemo and open the DocumentViewerDemo.sln solution file.

Configure the Project

This example code requires the following NuGet packages:

Create and Implement the RedisWithBlobsObjectCache Class

In the Solution Explorer, right-click Class1.cs and select Rename from the context menu. Rename the class file to RedisWithBlobsObjectCache.cs and press Enter.

Open the RedisWithBlobsObjectCache.cs file and add the following using statements to the top:

C#
using Leadtools.Caching; 
using Leadtools.Codecs; 
using Leadtools.Svg; 
 
using Azure.Storage.Blobs; 
using Newtonsoft.Json; 
using StackExchange.Redis; 
 
using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Threading.Tasks; 
using Azure.Storage.Blobs.Models; 

In the Leadtools.Services.Tools.Cache namespace define the RedisWithBlobsObjectCache class and add it to the ObjectCache class derivation list, as shown below.

C#
/// <summary>  
/// Wraps a Redis Cache IDatabase object to be used with the LEADTOOLS Document Library.  
/// </summary>  
/// <remarks>  
/// <para>  
///   This implementation adds support for storing the binary data into a Cloud Blob storage.  
///   The user specifies a container name and the binary data is stored in block blob with names = "regionName/key".  
/// </para>  
/// </remarks>  
namespace Leadtools.Services.Tools.Cache 
{    
   public class RedisWithBlobsObjectCache : ObjectCache 
   { 
   } 
} 

Implement the below code to the RedisWithBlobsObjectCache class.

C#
public class RedisWithBlobsObjectCache : ObjectCache 
{ 
   private RedisWithBlobsObjectCache() { } 
   /// <summary>  
   /// Initializes a LEADTOOLS Object Cache wrapper from a Redis cache database object.  
   /// </summary>  
   /// <param name="cache">Fully-initialized Redis database object ready to be used.</param>  
   /// <param name="blobClient">Azure blob client ready to be used.</param>  
   public RedisWithBlobsObjectCache(StackExchange.Redis.IDatabase cache, BlobServiceClient blobClient, string containerName = "ContainerName") 
   { 
      this.Cache = cache; 
      this.BlobServiceClient = blobClient; 
      this.ContainerName = "ContainerName"; 
   } 
 
   /// <summary>  
   /// The Redis cache database object being used.  
   /// </summary>  
   public StackExchange.Redis.IDatabase Cache { get; private set; } 
 
   /// <summary>  
   /// The Azure Blob client object being used.  
   /// </summary>  
   public BlobServiceClient BlobServiceClient { get; private set; } 
 
   /// <summary>  
   /// The Azure Blob Container name being used.  
   /// </summary>  
   public string ContainerName { get; private set; } 
    
   /// --------------------------------------------------------------------------------------  
   // These members must be implemented by our class and are called by the Document toolkit  
   // --------------------------------------------------------------------------------------  
 
   public override string Name 
   { 
      get 
      { 
         return "RedisWithBlobObjectCache"; 
      } 
   } 
 
   public override CacheSerializationMode PolicySerializationMode 
   { 
      get 
      { 
         // Redis do not use this so we will just assume it is binary  
         return CacheSerializationMode.Binary; 
      } 
 
       set { throw new NotSupportedException(); } 
   } 
 
   public override CacheSerializationMode DataSerializationMode 
   { 
      get 
      { 
         // Binary meaning we will do our own serialization  
         return CacheSerializationMode.Binary; 
      } 
      set { throw new NotSupportedException(); } 
   } 
 
   public override DefaultCacheCapabilities DefaultCacheCapabilities 
   { 
      get 
      { 
         // We support serialization: Meaning, the toolkit can send us "fat" .NET objects  
         // we will serialize them and not change the original reference  
         return DefaultCacheCapabilities.Serialization; 
      } 
   } 
 
   public override CacheItem<T> AddOrGetExisting<T>(CacheItem<T> item, CacheItemPolicy policy) 
   { 
      // Method called when a cache item is added.  
      // Must return the old value  
       
      // Resolve the key. Remember, we do not have regions  
      var resolvedKey = ResolveCacheKey(item.RegionName, item.Key); 
 
      CacheItem<T> oldItem = null; 
 
      // Get the old value (if any)  
      if (this.Cache.KeyExists(resolvedKey)) 
      { 
         RedisValue existingValue = this.Cache.StringGet(resolvedKey); 
         var oldValue = GetFromCacheAndBlobStorage<T>(existingValue, item.RegionName, item.Key); 
         oldItem = new CacheItem<T>(item.Key, (T)oldValue, item.RegionName); 
      } 
 
      // Add new value  
      AddToCacheAndBlobStorage(item, policy); 
 
      // Return old item  
      return oldItem; 
   } 
 
   public override CacheItem<T> GetCacheItem<T>(string key, string regionName) 
   { 
      // If we have an item with this key, return it. Otherwise, return null  
      var resolvedKey = ResolveCacheKey(regionName, key); 
 
      CacheItem<T> item = null; 
 
      if (this.Cache.KeyExists(resolvedKey)) 
      { 
         RedisValue value = this.Cache.StringGet(resolvedKey); 
         var itemValue = GetFromCacheAndBlobStorage<T>(value, regionName, key); 
         item = new CacheItem<T>(key, (T)itemValue, regionName); 
      } 
 
      return item; 
   } 
 
   public override bool Contains(string key, string regionName) 
   { 
      // Check if the key is in the dictionary  
       
      var resolvedKey = ResolveCacheKey(regionName, key); 
      var exists = this.Cache.KeyExists(resolvedKey); 
      return exists; 
   } 
 
   public override bool UpdateCacheItem<T>(CacheItem<T> item) 
   { 
      // Update the item  
 
      if (item == null) 
         throw new ArgumentNullException("item"); 
 
      var resolvedKey = ResolveCacheKey(item.RegionName, item.Key); 
      var exists = this.Cache.KeyExists(resolvedKey); 
      if (exists) 
      { 
         AddToCacheAndBlobStorage(item, null); 
      } 
 
      return exists; 
   } 
 
   public override T Remove<T>(string key, string regionName) 
   { 
      // Remove if found, return old value  
 
      T existingValue = default(T); 
      var resolvedKey = ResolveCacheKey(regionName, key); 
      if (this.Cache.KeyExists(resolvedKey)) 
      { 
         RedisValue value = this.Cache.StringGet(resolvedKey); 
         existingValue = (T)GetFromCacheAndBlobStorage<T>(value, regionName, key); 
      } 
 
      // Delete it  
      DeleteItem(key, regionName); 
 
      return existingValue; 
   } 
 
   public override void DeleteItem(string key, string regionName) 
   { 
      // Delete if found  
 
      DeleteFromCacheAndBlobStorage(regionName, key); 
   } 
 
   public override void UpdatePolicy(string key, CacheItemPolicy policy, string regionName) 
   { 
      // Redis Cache does not allow us to update the expiration policy of an item.  
   } 
 
   private static string ResolveCacheKey(string regionName, string key) 
   { 
      // Both must be non-empty strings  
      if (string.IsNullOrEmpty(regionName)) throw new InvalidOperationException("Region name must be a non-empty string"); 
      if (string.IsNullOrEmpty(key)) throw new InvalidOperationException("Region key name must be a non-empty string"); 
 
      // regionName might not be unique, key might not be unique, but combine them and we are guaranteed a unique key  
      return regionName + "-" + key; 
   } 
 
   public static string ResolveBlobReference(string regionName, string key) 
   { 
      // Get the blob reference  
      return regionName + "/" + key; 
   } 
 
   private void AddToCacheAndBlobStorage<T>(CacheItem<T> item, CacheItemPolicy policy) 
   { 
      // Get a Redis value from our item data  
      byte[] blob = null; 
      bool hasBlob = false; 
      string json = null; 
      var val = item.Value; 
      var typeOfT = typeof(T); 
 
      if (typeOfT == typeof(RasterImage)) 
      { 
         blob = ImageToBlob(item.Value as RasterImage); 
         hasBlob = true; 
      } 
      else if (typeOfT == typeof(SvgDocument)) 
      { 
         blob = SvgToBlob(item.Value as SvgDocument); 
         hasBlob = true; 
      } 
      else if (typeOfT == typeof(byte[])) 
      { 
         blob = item.Value as byte[]; 
         hasBlob = true; 
      } 
      else 
      { 
         // JSON serialize it  
         json = JsonConvert.SerializeObject(item.Value); 
         hasBlob = false; 
      } 
 
      // If the sliding expiration is used, make it the absolute value  
      TimeSpan? expiry = null; 
 
      if (policy != null) 
      { 
         var expiryDate = policy.AbsoluteExpiration; 
         if (policy.SlidingExpiration > TimeSpan.Zero) 
         { 
            expiryDate = DateTime.UtcNow.Add(policy.SlidingExpiration); 
         } 
 
         // Now, we have a date, convert it to time span from now (all UTC)  
         expiry = expiryDate.Subtract(DateTime.UtcNow); 
      } 
 
      var resolvedKey = ResolveCacheKey(item.RegionName, item.Key); 
 
      // Set the cache item value  
      if (hasBlob) 
      { 
         this.Cache.StringSet(resolvedKey, 1, expiry); 
         if (blob != null) 
         { 
            var blobReference = ResolveBlobReference(item.RegionName, item.Key); 
            BlobContainerClient container = this.BlobServiceClient.GetBlobContainerClient(this.ContainerName); 
            BlobClient blobClient = container.GetBlobClient(blobReference); 
            using (MemoryStream ms = new MemoryStream(blob)) 
            { 
               Task.Run(async () => await blobClient.UploadAsync(ms, new BlobUploadOptions())).Wait(); 
            } 
         } 
      } 
      else 
      { 
         this.Cache.StringSet(resolvedKey, json, expiry); 
      } 
   } 
 
   private object GetFromCacheAndBlobStorage<T>(RedisValue value, string regionName, string key) 
   { 
      var typeOfT = typeof(T); 
      object result = null; 
      BlobProperties properties = new BlobProperties(); 
      BlobDownloadResult blobDownloadResult = null; 
       
      if (typeOfT == typeof(RasterImage) || typeOfT == typeof(SvgDocument) || typeOfT == typeof(byte[])) 
      { 
         // Read the blob   
         BlobContainerClient container = this.BlobServiceClient.GetBlobContainerClient(this.ContainerName); 
         var name = ResolveBlobReference(regionName, key); 
         BlobClient blobClient = container.GetBlobClient(ResolveBlobReference(regionName, key)); 
         bool exists = Task.Run(async () => await blobClient.ExistsAsync()).Result; 
         if (exists) 
            //TODO Find a way to retrieve this attribute value for blob length later 
            Task.Run(async () => properties = await blobClient.GetPropertiesAsync()).Wait(); 
 
         byte[] blob = null; 
         //TODO need the properties value to get the length of the blob 
         //USE BlobProperties.ContentLength this gives the llenght of the byte array 
         var length = properties.ContentLength; 
         if (length != -1) 
         { 
            blob = new byte[length]; 
            if (length > 0) 
            { 
               using (MemoryStream ms = new MemoryStream()) 
               { 
                  Task.Run(async () => await blobClient.DownloadToAsync(ms)).Wait(); 
                  blob = ms.GetBuffer(); 
               } 
            } 
         } 
 
         if (typeOfT == typeof(RasterImage)) 
         { 
            result = ImageFromBlob(blob); 
         } 
         else if (typeOfT == typeof(SvgDocument)) 
         { 
            result = SvgFromBlob(blob); 
         } 
         else 
         { 
            result = (byte[])blob; 
         } 
      } 
      else 
      { 
         // JSON deserialize it   
         result = JsonConvert.DeserializeObject<T>(value); 
      } 
    
      return result; 
   } 
 
   private void DeleteFromCacheAndBlobStorage(string regionName, string key) 
   { 
      var resolvedKey = ResolveCacheKey(regionName, key); 
      if (this.Cache.KeyExists(resolvedKey)) 
         this.Cache.KeyDelete(resolvedKey); 
 
      BlobContainerClient container = this.BlobServiceClient.GetBlobContainerClient(this.ContainerName); 
      BlobClient blobClient = container.GetBlobClient(ResolveBlobReference(regionName, key)); 
      Task.Run(async () => await blobClient.DeleteIfExistsAsync()).Wait(); 
   } 
 
   // Helper methods to convert RasterImage or SvgDocument objects from/to byte[]  
   private static byte[] ImageToBlob(RasterImage image) 
   { 
      if (image == null) 
         return null; 
 
      // Save as PNG into a memory stream, use the byte[] data  
      using (var rasterCodecs = new RasterCodecs()) 
      { 
         using (var ms = new MemoryStream()) 
         { 
            rasterCodecs.Save(image, ms, RasterImageFormat.Png, 0); 
            return ms.GetBuffer(); 
         } 
      } 
   } 
 
   private static RasterImage ImageFromBlob(byte[] blob) 
   { 
      if (blob == null || blob.Length == 0) 
         return null; 
 
      // Load to a raster image, using the byte[] data  
      using (var rasterCodecs = new RasterCodecs()) 
      { 
         using (var ms = new MemoryStream(blob)) 
         { 
            return rasterCodecs.Load(ms, 1); 
         } 
      } 
   } 
 
   private static byte[] SvgToBlob(SvgDocument svg) 
   { 
      if (svg == null) 
         return null; 
 
 
      using (var ms = new MemoryStream()) 
      { 
         svg.SaveToStream(ms, null); 
         return ms.GetBuffer(); 
      } 
   } 
 
   private static SvgDocument SvgFromBlob(byte[] blob) 
   { 
      if (blob == null || blob.Length == 0) 
         return null; 
 
      return SvgDocument.LoadFromMemory(blob, 0, blob.Length, null); 
   } 
} 

Append the code below to the RedisWithBlobsObjectCache class to override functions we don't support for the Document Viewer. Each of these functions will throw a NotSupportedException.

C#
   //  
   // These members must be over-ridden by our class but are never called by the Document toolkit  
   // So just throw a not supported exception  
   //  
 
   // This is for default region support. We do not have that  
   public override object this[string key] 
   { 
      get 
      { 
         throw new NotSupportedException(); 
      } 
      set 
      { 
         throw new NotSupportedException(); 
      } 
   } 
 
   // Delete a region in one shot. We do not support that  
   // Note: This is only called if we have DefaultCacheCapabilities.CacheRegions. Since we do not, the caller is responsible for  
   // calling DeleteAll passing all the items of the region (which in turn will call DeleteItem for each)  
   public override void DeleteRegion(string regionName) 
   { 
      throw new NotSupportedException(); 
   } 
 
   // Begin adding an external resource. We do not support that  
   // Note: This is only called if we have DefaultCacheCapabilities.ExternalResources  
   public override Uri BeginAddExternalResource(string key, string regionName, bool readWrite) 
   { 
      throw new NotSupportedException(); 
   } 
 
   // End adding an external resource. We do not support that  
   // Note: This is only called if we have DefaultCacheCapabilities.ExternalResources  
   public override void EndAddExternalResource<T>(bool commit, string key, T value, CacheItemPolicy policy, string regionName) 
   { 
      throw new NotSupportedException(); 
   } 
 
   // Get the item external resource. We do not support that  
   // Note: This is only called if we have DefaultCacheCapabilities.ExternalResources  
   public override Uri GetItemExternalResource(string key, string regionName, bool readWrite) 
   { 
      throw new NotSupportedException(); 
   } 
 
   // Remove the item external resource. We do not support that  
   // Note: This is only called if we have DefaultCacheCapabilities.ExternalResources  
   public override void RemoveItemExternalResource(string key, string regionName) 
   { 
      throw new NotSupportedException(); 
   } 
 
   // Get the item virtual directory path. We do not support that  
   // Note: This is only called if we have DefaultCacheCapabilities.VirtualDirectory  
   public override Uri GetItemVirtualDirectoryUrl(string key, string regionName) 
   { 
      throw new NotSupportedException(); 
   } 
 
   // Getting number of items in the cache. We do not support that  
   public override long GetCount(string regionName) 
   { 
      throw new NotSupportedException(); 
   } 
 
   // Statistics. We do not support that  
   public override CacheStatistics GetStatistics() 
   { 
      throw new NotSupportedException(); 
   } 
 
   // Statistics. We do not support that  
   public override CacheStatistics GetStatistics(string key, string regionName) 
   { 
      throw new NotSupportedException(); 
   } 
 
   // Getting all the values. We do not support that  
   public override IDictionary<string, object> GetValues(IEnumerable<string> keys, string regionName) 
   { 
      throw new NotSupportedException(); 
   } 
 
   // Enumeration of the items. We do not support that  
   protected override IEnumerator<KeyValuePair<string, object>> GetEnumerator() 
   { 
      throw new NotSupportedException(); 
   } 
 
 
   // Enumeration of the keys. We do not support that  
   public override void EnumerateKeys(string region, EnumerateCacheEntriesCallback callback) 
   { 
      throw new NotSupportedException(); 
   } 
 
   // Enumeration of regions. We do not support that  
   public override void EnumerateRegions(EnumerateCacheEntriesCallback callback) 
   { 
      throw new NotSupportedException(); 
   }     

Create the Cache Manager

In order to use our cache in the Document Service, we need to create a CacheManager object to handle the communication between the service and the cache.

In the Solution Explorer, right-click Class1.cs and select Rename from the context menu. Type RedisWithBlobsCacheManager.cs and press Enter.

Add the following references to the top of the class file:

C#
using Azure.Storage.Blobs; 
using Leadtools.Caching; 
using Leadtools.Document; 
using Leadtools.Services.Tools.Helpers; 
using StackExchange.Redis; 
using System; 
using System.Diagnostics; 
using System.Xml.Linq; 

In the Leadtools.Services.Tools.Cache namespace, define the RedisWithBlobsCacheManger class and add it to the ICacheManager derivation list.

C#
namespace Leadtools.Services.Tools.Cache 
{ 
   public class RedisWithBlobsCacheManager : ICacheManager 
   { 
 
   } 
} 

Update the RedisWithBlobsCacheManager class to match the one shown below.

C#
public class RedisWithBlobsCacheManager : ICacheManager 
{ 
   // Our cache 
   private ObjectCache _objectCache; 
   private CacheItemPolicy _cacheItemPolicy; 
   private bool _isInitialized = false; 
   public string WebRootPath { get; set; } 
   ConnectionMultiplexer connection; 
 
   // Its name 
   public const string CACHE_NAME = "RedisWithBlobObjectCache"; 
 
   public static long RedisCacheSlidingExpiryPolicySeconds { get; set; } = 1 * 60 * 60; //sec, min, hours  //1 hour total default 
 
   public RedisWithBlobsCacheManager(string WebRootPath, XElement cacheManagerElement) 
   { 
      if (string.IsNullOrEmpty(WebRootPath)) 
         throw new ArgumentNullException(nameof(WebRootPath)); 
 
      _isInitialized = false; 
      this.WebRootPath = WebRootPath; 
 
      // Get the values from the XML document 
      ParseXml(cacheManagerElement); 
   } 
   public void ParseXml(XElement cacheManagerElement) 
   { 
      // Nothing to parse 
   } 
 
   private const string CACHE_MANAGER_NAME = "RedisWithBlobsCacheManger"; 
   public string Name 
   { 
      get { return CACHE_MANAGER_NAME; } 
   } 
 
   public string[] GetCacheNames() 
   { 
      return new string[] { CACHE_NAME }; 
   } 
 
   public bool IsInitialized 
   { 
      get { return _isInitialized; } 
   } 
 
   public void Initialize() 
   { 
      Trace.WriteLine("Initializing default cache from configuration"); 
 
      if (_isInitialized) 
         throw new InvalidOperationException("Cache already initialized"); 
       
      _objectCache = InitializeRedisWithBlobsCache(); 
      _objectCache.SetName(CACHE_NAME); 
      if (_objectCache.Name != CACHE_NAME) 
         throw new InvalidOperationException($"ObjectCache implementation {_objectCache.GetType().FullName} does not override SetName"); 
 
      DocumentFactory.LoadDocumentFromCache += LoadDocumentFromCacheHandler; 
      _cacheItemPolicy = InitializePolicy(_objectCache); 
      _isInitialized = true; 
   } 
 
   private void LoadDocumentFromCacheHandler(object sender, ResolveDocumentEventArgs e) 
   { 
      // Get the cache for the document if we have it 
      ObjectCache objectCache = GetCacheForDocument(e.LoadFromCacheOptions.DocumentId); 
      if (objectCache != null) 
         e.LoadFromCacheOptions.Cache = objectCache; 
   } 
 
   public void Cleanup() 
   { 
      if (!_isInitialized) 
         return; 
 
      _objectCache = null; 
      _cacheItemPolicy = null; 
      _isInitialized = false; 
     
      //Deletes all entries from cached 
      var endpoints = connection.GetEndPoints(); 
      foreach (var point in endpoints) 
      { 
         var server = connection.GetServer(point); 
         server.FlushAllDatabases(); 
      } 
   } 
 
   private ObjectCache InitializeRedisWithBlobsCache() 
   { 
      // Called by InitializeService the first time the service is run 
      // Initialize the global Cache object 
 
      string cacheConfigFile = ServiceHelper.GetSettingValue(ServiceHelper.Key_Cache_ConfigFile); 
      cacheConfigFile = ServiceHelper.GetAbsolutePath(cacheConfigFile); 
      if (string.IsNullOrEmpty(cacheConfigFile)) 
         throw new InvalidOperationException($"The cache configuration file location in '{ServiceHelper.Key_Cache_ConfigFile}' in the configuration file is empty"); 
 
      ObjectCache objectCache = null; 
 
      // Set the base directory of the cache (for resolving any relative paths) to this project's path 
      var additional = new Dictionary<string, string>(); 
      additional.Add(ObjectCache.BASE_DIRECTORY_KEY, WebRootPath); 
 
      try 
      { 
         //Connection String 
         var redisConfiguration = "YOUR_CACHE_ADDRESS,password=YOUR_PASSWORD,abortConnect=false"; 
         var configurationOptions = ConfigurationOptions.Parse(configuration); 
         // Increase the sync-timeout since we may be storing large binaries.  
         // Note that this is not required for RedisWithBlobsObjectCache implementation.  
         configurationOptions.SyncTimeout = 5000; 
         connection = ConnectionMultiplexer.Connect(configurationOptions); 
         IDatabase redisDatabase = connection.GetDatabase(); 
         var blobConfiguration = "DefaultEndpointsProtocol=https;AccountName=YOUR_ACCOUNT_NAME;AccountKey=YOUR_ACCOUNT_KEY;EndpointSuffix=core.windows.net"; 
         BlobServiceClient blobServiceClient = new BlobServiceClient(blobConfiguration); 
         blobServiceClient.GetProperties(); 
         // Create the LEADTOOLS ObjectCache wrapper  
         objectCache = new RedisWithBlobsObjectCache(redisDatabase, blobServiceClient); 
      } 
      catch (Exception ex) 
      { 
         throw new InvalidOperationException($"Cannot load cache configuration from '{cacheConfigFile}'", ex); 
      } 
 
      return objectCache; 
   } 
 
   public static CacheItemPolicy InitializePolicy(ObjectCache objectCache) 
   { 
      var policy = new CacheItemPolicy(); 
      //policy.AbsoluteExpiration = ObjectCache.InfiniteAbsoluteExpiration; 
      policy.SlidingExpiration = TimeSpan.FromSeconds(RedisCacheSlidingExpiryPolicySeconds); 
      return policy; 
   } 
 
   private static void VerifyCacheName(string cacheName, bool allowNull) 
   { 
      if (cacheName == null && allowNull) 
         return; 
 
      if (CACHE_NAME != cacheName) 
         throw new ArgumentException($"Invalid cache name: {cacheName}", nameof(cacheName)); 
   } 
 
   public void CheckCacheAccess(string cacheName) 
   { 
      Console.WriteLine("Made it to check!"); 
      CacheItemPolicy policy = null; 
   } 
 
   public CacheItemPolicy CreatePolicy(string cacheName) 
   { 
      VerifyCacheName(cacheName, false); 
      return _cacheItemPolicy.Clone(); 
   } 
 
   public CacheItemPolicy CreatePolicy(ObjectCache objectCache) 
   { 
      // Get the name of this cache 
      string cacheName = GetCacheName(objectCache); 
      if (cacheName == null) 
         throw new InvalidOperationException("Invalid object cache"); 
      return CreatePolicy(cacheName); 
   } 
 
   public CacheStatistics GetCacheStatistics(string cacheName) 
   { 
      return null; 
   } 
 
   public void RemoveExpiredItems(string cacheName) 
   { 
      VerifyCacheName(cacheName, true); 
 
      // Only supported by FileCache 
      FileCache fileCache = _objectCache as FileCache; 
      if (fileCache != null) 
      { 
         fileCache.CheckPolicies(); 
      } 
   } 
 
   public ObjectCache DefaultCache 
   { 
      get { return _objectCache; } 
   } 
 
   public ObjectCache GetCacheByName(string cacheName) 
   { 
      VerifyCacheName(cacheName, false); 
      return _objectCache; 
   } 
 
   public string GetCacheName(ObjectCache objectCache) 
   { 
      if (objectCache == null) 
         throw new ArgumentNullException(nameof(objectCache)); 
 
      if (objectCache == _objectCache) 
         return CACHE_NAME; 
 
      return null; 
   } 
 
   public ObjectCache GetCacheForDocument(string documentId) 
   { 
      if (documentId == null) 
         throw new ArgumentNullException(nameof(documentId)); 
      return _objectCache; 
   } 
 
   public ObjectCache GetCacheForDocumentOrDefault(string documentId) 
   { 
      ObjectCache objectCache = null; 
 
      if (!string.IsNullOrEmpty(documentId)) 
      { 
         objectCache = GetCacheForDocument(documentId); 
      } 
 
      if (objectCache == null) 
         objectCache = DefaultCache; 
 
      return objectCache; 
   } 
 
   public ObjectCache GetCacheForDocument(Uri documentUri) 
   { 
      if (documentUri == null) 
         throw new ArgumentNullException(nameof(documentUri)); 
 
      // Get the document ID from the URI and call the other version of this function 
      if (!DocumentFactory.IsUploadDocumentUri(documentUri)) 
         throw new ArgumentException($"{documentUri.ToString()} is not a valid LEAD document URI", nameof(documentUri)); 
 
      string documentId = DocumentFactory.GetLeadCacheData(documentUri); 
      return GetCacheForDocument(documentId); 
   } 
 
   public ObjectCache GetCacheForDocumentOrDefault(Uri documentUri) 
   { 
      ObjectCache objectCache = null; 
 
      if (documentUri != null) 
      { 
         objectCache = GetCacheForDocument(documentUri); 
      } 
 
      if (objectCache == null) 
         objectCache = DefaultCache; 
 
      return objectCache; 
   } 
 
   public ObjectCache GetCacheForLoadFromUri(Uri uri, LoadDocumentOptions loadDocumentOptions) 
   { 
      return _objectCache; 
   } 
 
   public ObjectCache GetCacheForBeginUpload(UploadDocumentOptions uploadDocumentOptions) 
   { 
      return _objectCache; 
   } 
 
   public ObjectCache GetCacheForCreate(CreateDocumentOptions createDocumentOptions) 
   { 
   return _objectCache; 
   } 
} 

Note

The default policy expiration time is 1 hour, but you can change it by altering the value of RedisWithBlobsCacheSlidingExpiryPolicySeconds in the RedisWithBlobsCacheManager class.

In the InitializeRedisWithBlobsCache method update the redisConfiguration and the blobConfiguration variable to contain the string needed to connect to your Azure Cache for Redis configuration. You can find this information on the Azure Portal.

You can also use the Connection String that is provided by the Azure Portal instead of constructing the string yourself.

C#
var redisConfiguration = "YOUR_CACHE_ADDRESS,password=YOUR_PASSWORD,abortConnect=false"; 
 
var blobConfiguration = "DefaultEndpointsProtocol=https;AccountName=YOUR_ACCOUNT_NAME;AccountKey=YOUR_ACCOUNT_KEY;EndpointSuffix=core.windows.net"; 

Set the Cache in the Document Service

Add the following code snippet to where you set your cache manager in the Document Service.

C#
public static void CreateCache() 
{ 
   // Called by InitializeService the first time the service is run 
   // Initialize the global ICacheManager object 
   ICacheManager cacheManager = null; 
 
 
   //cacheManager = new RedisCacheManager(WebRootPath, null); 
   cacheManager = new RedisWithBlobsCacheManager(WebRootPath, null); 
 
   /* 
   // See if we have a CacheManager configuration file 
   string cacheManagerConfigFile = GetSettingValue(Key_Cache_CacheManagerConfigFile); 
   cacheManagerConfigFile = GetAbsolutePath(cacheManagerConfigFile); 
   if (!string.IsNullOrEmpty(cacheManagerConfigFile)) 
   { 
      using (var stream = File.OpenRead(cacheManagerConfigFile)) 
      cacheManager = CacheManagerFactory.CreateFromConfiguration(stream, WebRootPath); 
   } 
   else 
   { 
      // Try to create the default ICacheManager directly (backward compatibility) 
      cacheManager = new DefaultCacheManager(WebRootPath, null); 
   } 
 
   if (cacheManager == null) 
   throw new InvalidOperationException("Could not find a valid LEADTOOLS cache system configuration."); 
   */ 
   cacheManager.Initialize(); 
   _cacheManager = cacheManager; 
} 

Note

By default the cache variable is set inside the ServiceHelper class in the CreateCache method, so update the CreateCache method to match the one shown below. If you are setting up this cache without a cache manager, just load the RedisObjectCache into the ObjectCache variable in your project.

Run the Project

If you followed all these steps correctly you can start up the JS Document Viewer and load and save documents from the cache.

You can see more information about the caching process by using the console for this Azure Cache for Redis on the Azure Portal. You can browse the directory of blobs by navigating to your container in your Storage Blobs implementation.

For your reference, you can download the RedisWithBlobsCache.cs and RedisWithBlobsCacheManager.cs files here.

Wrap-up

This tutorial showed how to set up a Azure Redis Cache with Storage Blobs for use with the LEADTOOLS Document Library in a .NET Application.

See Also

Help Version 22.0.2024.3.20
Products | Support | Contact Us | Intellectual Property Notices
© 1991-2023 LEAD Technologies, Inc. All Rights Reserved.

Products | Support | Contact Us | Intellectual Property Notices
© 1991-2023 LEAD Technologies, Inc. All Rights Reserved.