Working With A Distributed Cache In ASP.NET Core

Introduction

In general terms, caching takes place where the frequently-used data is stored, so that the application can quickly access the data rather than accessing the data from the source. Caching can improve the performance and scalability of the application dramatically and can help us to remove the unnecessary requests from the external data sources for the data that changes infrequently.

ASP.NET Core has a rich support for caching and it supports different kinds of caching. In my past article, I explained about the In-memory caching. In this article, we will talk about distributed cache. It can help us to improve the performance and scalability of the application, when the application is hosted on the web farm or cloud environment.

In distributed caching, cache is not stored in to an individual web server’s memory. Cache data is centrally managed and the same data is available to all the app servers. The distributed caching has several advantages, as shown below.

  • The cache is stored centrally, so all the users get the same data and data is not dependent on which web server handles its request.
  • The cache data is not impacted if any problem happens with the web server; i.e.,  restart, new server is added, a server is removed.

The distributed cache can be configured with either Redis or SQL Server. The implementation of the caching is not dependent on the configuration; the application interacts with the cache, using IDistributedCache interface.

IDistributedCache Interface

This interface has methods, which allow us to add, remove, and retrieve the distributed cache. This interface contains synchronous and asynchronous methods.

  • Get, GetAsync
    It retrieves the data from the cache, using key. It returns byte[], if the key is not found in to cache.

  • Set, SetAsync
    It adds the item to cache as byte[].

  • Refresh, RefreshAsync
    It refreshes the item in the cache and also resets its sliding expiration timeout, if any.

  • Remove, RemoveAsync
    It removes the entry from the cache, using key.

We need to perform the three simple steps given below to configure distributed cache in ASP.NET Core.

  1. Define cache dependencies into project.json file.
  2. Configure cache Service ConfigureServices method of Startup class.
  3. Dependency is automatically injected to the application's middleware or MVC controller constructor. Using this instance of cache dependency object, we can perform the operation related to distributed cache

Distributed Cache with SQL Server

SqlServerCache allows the distributed cache to use SQL Server as cache storing purpose. Prior to using SQL Server as a cache, we must create a table with the schema given below.

  1. CREATE TABLE[dbo].[SQLCache](    
  2.        [Id][nvarchar](449) NOT NULL,   
  3.     [Value][varbinary](maxNOT NULL,   
  4.     [ExpiresAtTime][datetimeoffset](7) NOT NULL,   
  5.     [SlidingExpirationInSeconds][bigintNULL,   
  6.     [AbsoluteExpiration][datetimeoffset](7) NULL,   
  7.     CONSTRAINT[pk_Id] PRIMARY KEY CLUSTERED([Id] ASCWITH(PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)   
  8. ON[PRIMARY]) ON[PRIMARY] TEXTIMAGE_ON[PRIMARY]   
The next step is to add dependencies into the project.json file.To use SQL server as cache, we need to add "Microsoft.Extensions.Caching.SqlServer" dependency to the project. 
 
Project.json 
  1. {  
  2.     "version""1.0.0-*",  
  3.     "buildOptions": {  
  4.         "preserveCompilationContext"true,  
  5.         "debugType""portable",  
  6.         "emitEntryPoint"true  
  7.     },  
  8.     "tool": {  
  9.         "Microsoft.Extensions.Caching.SqlConfig.Tools""1.0.0-preview2-final"  
  10.     },  
  11.     "dependencies": {},  
  12.     "frameworks": {  
  13.         "netcoreapp1.0": {  
  14.             "dependencies": {  
  15.                 "Microsoft.NETCore.App": {  
  16.                     "type""platform",  
  17.                     "version""1.0.1"  
  18.                 },  
  19.                 "Microsoft.AspNetCore.Server.Kestrel""1.0.0",  
  20.                 "Microsoft.AspNetCore.Mvc""1.0.0",  
  21.                 "Microsoft.Extensions.Caching.Memory""1.0.0",  
  22.                 "Microsoft.Extensions.Caching.SqlServer""1.0.0"  
  23.             },  
  24.             "imports""dnxcore50"  
  25.         }  
  26.     }  
  27. }  
Now, we need to have some configuration in configureServices method of the startup class. Using method "AddDistributedSqlServerCache", we can configure SQL Server as cache. In this method, we need to pass the connection string, schema name and cache table name.

Startup.cs
  1. public void ConfigureServices(IServiceCollection services) {  
  2.     services.AddMvc();  
  3.     services.AddDistributedSqlServerCache(opt => {  
  4.         opt.ConnectionString = @ "server=DESKTOP-HP\SQL;Database=CachingTest;Trusted_Connection=True;";  
  5.         opt.SchemaName = "dbo";  
  6.         opt.TableName = "SQLCache";  
  7.     });  
  8. }  
Using methods explained above (get, set, and remove), we can access SQL cache. SQL cache is stored in binary format, so we need to cast to byte[] before storing it in to the cache and when retrieving the data from cache,  the system will return it in byte[]. To use data, we need to cast it to the required format.

In the following example, I have created methods for creating cache, retrieving cache and removing cache in controller. ASP.NET Core MVC Controller is able to request their dependencies explicitly via their constructors. We utilize the caching in our application by requesting an instance of IDistributedCache in our Controller (or middleware) constructor. In the code snippet given below, I have created controller class with three methods SetCacheData, GetCacheData and RemoveCacheData.

HomeController.cs
  1. using System;  
  2. using System.Text;  
  3. using Microsoft.AspNetCore.Mvc;  
  4. using Microsoft.Extensions.Caching.Distributed;  
  5. public class HomeController: Controller {  
  6.     IDistributedCache _memoryCache;  
  7.     public HomeController(IDistributedCache memoryCache) {  
  8.             _memoryCache = memoryCache;  
  9.         }  
  10.         [Route("home/SetCacheData")]  
  11.     public IActionResult SetCacheData() {  
  12.             var Time = DateTime.Now.ToLocalTime().ToString();  
  13.             var cacheOptions = new DistributedCacheEntryOptions {  
  14.                 AbsoluteExpiration = DateTime.Now.AddYears(1)  
  15.             };  
  16.             _memoryCache.Set("Time", Encoding.UTF8.GetBytes(Time), cacheOptions);  
  17.             return View();  
  18.         }  
  19.         [Route("home/GetCacheData")]  
  20.     public IActionResult GetCacheData() {  
  21.             string Time = string.Empty;  
  22.             Time = Encoding.UTF8.GetString(_memoryCache.Get("Time"));  
  23.             ViewBag.data = Time;  
  24.             return View();  
  25.         }  
  26.         [Route("home/RemoveCacheData")]  
  27.     public bool RemoveCacheData() {  
  28.         _memoryCache.Remove("Time");  
  29.         return true;  
  30.     }  
  31. }  
Output of SQL table

When we call the setCacheData of the controller, it stores the data into SQL table, which is specified in the configuration. The snippet is given below, which shows how SQL Server stores the data.

 SQL Server

Distributed Cache with Redis

Redis is an open source and in-memory data store, which is used as a distributed cache. We can install it locally and configure it. Also, we can configure an Azure Redis Cache. The easiest way to install Redis on a Windows machine is chocolatey. To install chocolatey in a local machine, run the command given below from PowerShell (with administrative mode).

PS C:\>iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))

 SQL Server

This command downloads the chocalatey installer for Windows and using the command given below, we can install Redis on the local machine.

PS C:\>choco install redis-64

Once Redis Server is installed, use the command given below, where we can start Redis Server.

PS C:\>redis-server

 SQL Server

To check whether Redis Server starts working properly, we can ping this Server, using Redis client.
If the server is working correctly, it returns “PONG” as the response.

 SQL Server

Now, Redis Server is ready to be used as a distributed cache. We need to add highlighted dependency given below in to project.json file. Here, Redis dependency only works with .NET framework 4.5.1 or 4.5.2, so I am using .NET framework 4.5.1.

Project.json
  1. {  
  2.     "buildOptions": {  
  3.         "preserveCompilationContext"true,  
  4.         "debugType""portable",  
  5.         "emitEntryPoint"true  
  6.     },  
  7.     "dependencies": {  
  8.         "Microsoft.AspNetCore.Server.Kestrel""1.0.0",  
  9.         "Microsoft.AspNetCore.Mvc""1.0.0",  
  10.         "Microsoft.Extensions.Caching.Redis""1.0.0"  
  11.     },  
  12.     "frameworks": {  
  13.         "net451": {},  
  14.     }  
  15. }  
Using the code given below, we can configure Redis Server as a cache in our project. Here, we need to pass the Server's address as configuration and the Server instance name as an instance name property. I am running the Server locally, so I have to pass “localhost:6379” as a configuration.

Statup.cs
  1. public void ConfigureServices(IServiceCollection services) {  
  2.     services.AddMvc();  
  3.     services.AddDistributedRedisCache(options => {  
  4.         options.Configuration = "localhost:6379";  
  5.         options.InstanceName = "";  
  6.     });  
  7. }  
Application code is same as described in SQL distributed cache. We can also verify how many keys are active on Redis Server by running the command given below on Redis client.

127.0.0.1:6379> keys *

 SQL Server

Summary

If we compare in-memory cache and distributed cache then in-memory cache is much faster than the distributed cache but it has some disadvantages. If we compare the above two described options for distributed cache, Redis Server is faster than SQL server. To improve the performance for SQL server distributed cache, we can use memory-optimized tables for caching but varbinary(max) and datetimeoffset data types are not supported in memory-optimized tables. If we want to use SQL Server for caching, we can use "dbcc pintable" to ensure the table is kept in the memory.