Use Memory Cache with the Document Viewer - WinForms C#

This tutorial shows how to use a non-persistent memory cache in a WinForms C# application with the Document Viewer. This cache will hold information on files loaded from the internet.

Overview  
Summary This tutorial covers how to implement a memory cache for the Document Viewer in a C# Windows WinForms Application.
Completion Time 30 minutes
Project Download tutorial project (14 KB)
Platform Windows WinForms C# Application
IDE Visual Studio 2022
Development License Download LEADTOOLS

Required Knowledge

Get familiar with the basic steps of creating a project and loading an image in the Document Viewer by reviewing the Add References and Set a License and Display Files in the Document Viewer tutorials, before working on the Use Memory Cache with the Document Viewer - WinForms C# tutorial.

Note

For more information on the Document Viewer and its caching capabilities, see Document Toolkit and Caching.

Create the Project and Add LEADTOOLS References

Start with a copy of the project created in the Display Files in the Document Viewer tutorial. If you do not have that project, follow the steps in that tutorial to create it.

The references needed depend upon the purpose of the project. References can be added by one or the other of the following two methods (but not both).

If using NuGet references, this tutorial requires the following NuGet package:

If using local DLL references, the following DLLs are needed.

The DLLs are located at <INSTALL_DIR>\LEADTOOLS22\Bin\DotNet4\x64:

For a complete list of which DLL files are required for your application, refer to Files to be Included With Your Application.

Set the License File

The License unlocks the features needed for the project. It must be set before any toolkit function is called. For details, including tutorials for different platforms, refer to Setting a Runtime License.

There are two types of runtime licenses:

Add the MemoryCache Code

With the project created, the references added, and the license set, coding can begin.

In the Solution Explorer, right-click on the project file and select Add -> Class. This will bring up the Add New Item dialog, add MemoryCache.cs for the name. Click Add to add the class to the project.

In the newly added class, add the following using statements to the top

C#
using System; 
using System.Collections.Concurrent; 
using System.Collections.Generic; 
using Leadtools.Caching; 

Add MemoryCache as a derivation of the ObjectCache class. Add the code below to the MemoryCache class:

C#
namespace MemoryCaching 
{ 
    public class MemoryCache : ObjectCache 
    { 
        // The cache. A concurrent dictionary of string|object  
        private ConcurrentDictionary<string, object> _cache = new ConcurrentDictionary<string, object>(); 
        //  
        // These members must be implemented by our class and are called by the Document toolkit  
        //  
        // Our name  
        public override string Name 
        { 
            get 
            { 
                return "Memory Cache"; 
            } 
        } 
        // We only support binary serialization. In reality, we do not support policies nor serialization, but we return Binary  
        // to inform any callers to not bother sending us any JSON data  
        public override CacheSerializationMode PolicySerializationMode 
        { 
            get 
            { 
                return CacheSerializationMode.Binary; 
            } 
            set 
            { 
                throw new NotSupportedException(); 
            } 
        } 
        public override CacheSerializationMode DataSerializationMode 
        { 
            get 
            { 
                return CacheSerializationMode.Binary; 
            } 
            set 
            { 
                throw new NotSupportedException(); 
            } 
        } 
        // We have no special extra support  
        public override DefaultCacheCapabilities DefaultCacheCapabilities 
        { 
            get 
            { 
                return DefaultCacheCapabilities.None; 
            } 
        } 
        // This function handles all the backend for adding the document to the cache 
        // You don't need to call this function manually, it is automatically handled by the  
        // Document Factory 
        public override CacheItem<T> AddOrGetExisting<T>(CacheItem<T> item, CacheItemPolicy policy) 
        { 
            if (item == null) 
                throw new ArgumentNullException("item"); 
 
            Console.WriteLine("Adding to Cache"); 
 
            // Resolve the key, remember, we do not have regions  
            var resolvedKey = ResolveKey(item.RegionName, item.Key); 
 
            CacheItem<T> oldItem = null; 
 
            // Try to get the old value  
            // Yes, save the old value to return it to the user  
            object oldPayload; 
            if (_cache.TryGetValue(resolvedKey, out oldPayload)) 
                oldItem = new CacheItem<T>(item.Key, (T)oldPayload, item.RegionName); 
 
            // Set the new data  
            _cache.TryAdd(resolvedKey, item.Value); 
 
            // Return old item  
            return oldItem; 
        } 
        //Function that handles retrieving the cache item from the cache, based on a certain  
        //region and key 
        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 = ResolveKey(regionName, key); 
 
            CacheItem<T> item = null; 
 
            object payload; 
            if (_cache.TryGetValue(resolvedKey, out payload)) 
                item = new CacheItem<T>(key, (T)payload, regionName); 
 
            return item; 
        } 
        public override bool Contains(string key, string regionName) 
        { 
            // Check if the key is in the dictionary  
 
            var resolvedKey = ResolveKey(regionName, key); 
            var exists = _cache.ContainsKey(resolvedKey); 
 
            return exists; 
        } 
        public override bool UpdateCacheItem<T>(CacheItem<T> item) 
        { 
            // Update the item   
 
            if (item == null) 
                throw new ArgumentNullException("item"); 
 
            var resolvedKey = ResolveKey(item.RegionName, item.Key); 
            var exists = _cache.ContainsKey(resolvedKey); 
            if (exists) 
                _cache[resolvedKey] = item.Value; 
 
            return exists; 
        } 
        public override T Remove<T>(string key, string regionName) 
        { 
            // Removed if exists, return old value  
 
            var resolvedKey = ResolveKey(regionName, key); 
 
            object payload; 
            var removed = _cache.TryRemove(resolvedKey, out payload); 
            return removed ? (T)payload : default(T); 
        } 
        public override void DeleteItem(string key, string regionName) 
        { 
            // Remove if exists  
 
            var resolvedKey = ResolveKey(regionName, key); 
            object payload; 
            _cache.TryRemove(resolvedKey, out payload); 
        } 
        private static string ResolveKey(string regionName, string key) 
        { 
            // Both must me non-empty strings  
            if (string.IsNullOrEmpty(regionName)) throw new InvalidOperationException("Region name must be a none empty string"); 
            if (string.IsNullOrEmpty(key)) throw new InvalidOperationException("Region key name must be a none empty string"); 
 
            // We are a simple dictionary with no grouping. regionName might not be unique, key might not be unique, but combine them  
            // and we are guaranteed a unique key  
            return regionName + "-" + key; 
        } 
        public override void UpdatePolicy(string key, CacheItemPolicy policy, string regionName) 
        { 
            // Nothing to do  
        } 
    } 
} 

Add the below code to the bottom of the MemoryCache class to override functions LEADTOOLS does not support for the Document Viewer. If not added each of these functions will throw a NotSupportedException.

C#
// These members must be over implemented by our class but are never called by the Documents 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(); 
} 

Set the MemoryCache as the Active Cache in the Document Viewer

Go to the Form1.cs class and ensure that the using statements below are included.

C#
using System; 
using System.IO; 
using System.Drawing; 
using System.Windows.Forms; 
 
using Leadtools; 
using Leadtools.Document; 
using Leadtools.Caching; 
using Leadtools.Document.Viewer; 
using Leadtools.Controls; 
using System.Collections.Generic; 
using NUnit.Framework; 

Remove the instances of FileCache and add the global variable below:

C#
// Remove 
//private FileCache _cache; 
 
// Add 
private MemoryCache _cache; 

Adjust the InitDocumentViewer() method to the code below:

C#
private void InitDocumentViewer() 
{ 
    InitUI(); 
 
    var createOptions = new DocumentViewerCreateOptions(); 
 
    // Set the UI part where the Document Viewer is displayed  
    createOptions.ViewContainer = this.Controls.Find("docViewerPanel", false)[0]; 
 
    // Set the UI part where the Thumbnails are displayed  
    createOptions.ThumbnailsContainer = this.Controls.Find("thumbPanel", false)[0]; 
 
    // Not using annotations for now   
    createOptions.UseAnnotations = false; 
 
    // Now create the viewer  
    _documentViewer = DocumentViewerFactory.CreateDocumentViewer(createOptions); 
    _documentViewer.View.ImageViewer.Zoom(ControlSizeMode.FitAlways, 1.0, _documentViewer.View.ImageViewer.DefaultZoomOrigin); 
    _cache = new MemoryCache(); 
    _virtualDocument = DocumentFactory.Create(new CreateDocumentOptions() { Cache = _cache, UseCache = true }); 
} 

Create Dialog Box for Loading From URLs

In the Solution Explorer, right-click on the project file and select Add -> Form(Windows Forms). This will bring up the Add New Item dialog, put OpenDocumentUrlDialog.cs for the name and click Add.

Open the OpenDocumentUrlDialog.Desginer.cs file. Add the below code to the InitializeComponent() function.

C#
private void InitializeComponent() 
{ 
    this._txtBox_Url = new System.Windows.Forms.TextBox(); 
    this._Load = new System.Windows.Forms.Button(); 
    this._UrlLabel = new System.Windows.Forms.Label(); 
    this.SuspendLayout(); 
    //  
    // _txtBox_Url 
    //  
    this._txtBox_Url.Location = new System.Drawing.Point(12, 38); 
    this._txtBox_Url.Name = "_txtBox_Url"; 
    this._txtBox_Url.Size = new System.Drawing.Size(776, 20); 
    this._txtBox_Url.TabIndex = 0; 
    this._txtBox_Url.Text = "http://demo.leadtools.com/images/pdf/leadtools.pdf"; 
    //  
    // _Load 
    //  
    this._Load.Location = new System.Drawing.Point(713, 64); 
    this._Load.Name = "_Load"; 
    this._Load.Size = new System.Drawing.Size(75, 28); 
    this._Load.TabIndex = 1; 
    this._Load.Text = "&Load"; 
    this._Load.UseVisualStyleBackColor = true; 
    this._Load.Click += (sender, e) => LoadBtn_Click(); 
    //  
    // _UrlLabel 
    //  
    this._UrlLabel.AutoSize = true; 
    this._UrlLabel.Location = new System.Drawing.Point(12, 22); 
    this._UrlLabel.Name = "_UrlLabel"; 
    this._UrlLabel.Size = new System.Drawing.Size(20, 13); 
    this._UrlLabel.TabIndex = 2; 
    this._UrlLabel.Text = "Url"; 
    //  
    // OpenDocumentUrlDialog 
    //  
    this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 
    this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 
    this.ClientSize = new System.Drawing.Size(800, 116); 
    this.Controls.Add(this._UrlLabel); 
    this.Controls.Add(this._Load); 
    this.Controls.Add(this._txtBox_Url); 
    this.Name = "OpenDocumentUrlDialog"; 
    this.Text = "OpenDocumentFromUrlDialog"; 
    this.ResumeLayout(false); 
    this.PerformLayout(); 
} 

Add the following variables below #endregion.

C#
private System.Windows.Forms.TextBox _txtBox_Url; 
private System.Windows.Forms.Button _Load; 
private System.Windows.Forms.Label _UrlLabel; 

Right-click on OpenDocumentUrlDialog.cs and select View Code to bring up the code behind the form. Add the following variable to the top of the class.

C#
public string url { get { return _txtBox_Url.Text; } } 

Inside the OpenDocuemntUrlDialog class, add the below function.

C#
private void LoadBtn_Click() 
{ 
    this.Close(); 
} 

Add the Document Cache Info Code

Right-click on Form1.cs and select View Code to bring up the code behind the form. Add and new method to the Form1 class named ShowDocumentCacheInfo(ObjectCache cache, string documentId). This method will be called inside the LoadDocumentFromUri function created in the section below, right before the BeginUpdate() call. Add the code below to the new method to show the cache information.

C#
private static void ShowDocumentCacheInfo(ObjectCache cache, string documentId)  
{  
    // We know that the document ID is the cache region ID  
    // We also know that a document may contain more than 1 cache item  
    // We also know that all the possible items that belong to a document can be obtained with LEADDocument.GetCacheKeys  
  
    // First load the document from the cache  
    var loadFromCacheOptions = new LoadFromCacheOptions();  
    loadFromCacheOptions.Cache = cache;  
    loadFromCacheOptions.DocumentId = documentId;  
    using (var document = DocumentFactory.LoadFromCache(loadFromCacheOptions))  
    {  
        // This is to demonstrate that it is not necessary to keep the global "cache" object around, GetCache  
        // returns it for the document  
        ObjectCache documentCache = document.GetCache();  
        Assert.IsNotNull(documentCache);  
  
        // Get all the possible cache keys  
        // These keys may or may not exist in the cache, depending on which part of the document was cached.  
        // For instance, if DocumentCacheOptions.PageImage was set in the document, and the image was cached, then there is an item for it  
        Console.WriteLine($"document {documentId} cache policies:");  
        ISet<string> cacheKeys = document.GetCacheKeys();  
        foreach (string cacheKey in cacheKeys)  
        {  
            // Does it exist?  
            if (documentCache.Contains(cacheKey, document.DocumentId))  
            {  
                // Get the policy  
                CacheItemPolicy itemPolicy = documentCache.GetPolicy(cacheKey, document.DocumentId);  
                // This demo sets an absolute expiration, but this is generic code  
                // than can figure it out by examining the values:  
                DateTime absoluteExpiration = itemPolicy.AbsoluteExpiration;  
                TimeSpan slidingExpiration = itemPolicy.SlidingExpiration;  
                DateTime localTimeExpiration;  
  
                if (slidingExpiration != TimeSpan.Zero)  
                {  
                    // Has sliding expiration, therefore the expiration will be NOW + sliding  
                    localTimeExpiration = DateTime.Now.Add(slidingExpiration);  
                }  
                else  
                {  
                    // Absolute expiration is stored in UTC, convert to local  
                    localTimeExpiration = absoluteExpiration.ToLocalTime();  
                }  
  
                // Show the expiration  
                Console.WriteLine($" key:{cacheKey} expiry at {localTimeExpiration}");  
            }  
        }  
    }  
} 

Add the Load from URI Code

In the Form1 class add a new function named public void LoadDocumentFromUri(Button loadButton). Add the the code below to load from a URL.

C#
public void LoadDocumentFromUri(Button loadButton) 
{ 
    string url; 
    using (var dlg = new OpenDocumentUrlDialog()) 
    { 
        dlg.ShowDialog(this); 
        url = dlg.url; 
 
        LEADDocument document = DocumentFactory.LoadFromUri(new Uri(url), new LoadDocumentOptions { UseCache = true }); 
        _virtualDocument.Pages.Clear(); 
        for (int i = 0; i < document.Pages.Count; i++) 
        { 
            _virtualDocument.Pages.Add(document.Pages[i]); 
        } 
 
        ShowDocumentCacheInfo(_cache, _virtualDocument.DocumentId); 
        _documentViewer.BeginUpdate(); 
        _documentViewer.SetDocument(_virtualDocument); 
        _documentViewer.View.Invalidate(); 
        if (_documentViewer.Thumbnails != null) 
            _documentViewer.Thumbnails.Invalidate(); 
        _documentViewer.EndUpdate(); 
    } 
} 

Run the Project

Run the project by pressing F5, or by selecting Debug -> Start Debugging.

If the steps were followed correctly, the application runs and allows you to load a document into the viewer. When you load the document in the viewer, you can see the cache information in the Output window in Visual Studio.

Displayed Saved Cache Keys

Wrap-up

This tutorial showed how to implement a Memory Cache for the DocumentViewer object.

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.