Key Takeaways
- NCache is an open-source distributed in-memory cache developed natively in .NET and .NET Core that helps such applications to process fast and scale out with ease.
- Caching is a proven technique used to hold and readily use frequently accessed data.
- This article presents a discussion and implementation of how we can work with NCache IDistributedCache provider using .NET 6.
Introduction
In this post, we'll look at how to implement distributed caching in ASP.NET Core using NCache as the cache provider. A cache is a type of high-speed memory that applications use to store frequently accessed data. It reduces the unnecessary database hits since the data being requested is readily available in the cache. Caching is a well-known approach for boosting application performance.
What is distributed caching
A distributed cache is a cache that is shared by several app servers and is frequently managed as a separate service from the app servers that use it.
A distributed cache can go beyond the memory restrictions of a single computer by connecting multiple computers, which is known as a distributed architecture or a distributed cluster, for increased capacity and processing power.
An ASP.NET Core project's efficiency and scalability can be improved by using a distributed cache, especially if the application is hosted by a cloud service or a server farm. In high-data-volume and high-load applications, distributed caches are extremely useful. The distributed design allows for gradual expansion and scalability by adding more computers to the cluster, allowing the cache to expand in sync with the data growth.
The advantages of distributed cache are:
- Cached data is consistent across all web servers. The user gets the same results regardless of which web server serves their request.
- Web server restarts and deployments do not affect cached data. Individual web servers can be added or removed with no effect on the cache.
- Doesn't use local memory.
What is NCache?
NCache is a free, open-source distributed in-memory cache written in.NET and.NET Core. NCache is a distributed cache that saves application data while avoiding costly database trips. It's lightning-fast and linearly scalable. NCache can be utilized to reduce performance constraints caused by data storage and databases, as well as to enable your.NET Core applications to manage high-volume transaction processing (XTP). It can be used both locally and as a distributed cache cluster for ASP.NET Core apps hosted on Azure or other platforms.
Distributed Caching in ASP.NET Core
Using the IDistributedCache interface provided by dotnetcore, we can connect our ASP.NET Core apps to any distributed cache cluster and utilize it for caching as needed. Almost all major cache providers provide implementations for IDistributedCache, which we can register in the IoC container using IServiceCollection.
IDistributedCache Interface
This is the interface required to access the distributed cache objects, which includes synchronous and asynchronous methods. Items can be added, retrieved, and removed from the distributed cache implementation using this interface.
To perform actions on the actual cache, the IDistributedCache Interface provides the following methods.
- Get, GetAsync: Takes a string key and retrieves the cached item from the cache server if found.
- Set, SetAsync: Add items to the cache using a string key.
- Refresh, RefreshAsync: Resets the sliding expiry timeout of an item in the cache based on its key (if any).
- Remove, RemoveAsync: Deletes the cache data based on the key.
Connecting to an NCache cache
First, we need to install NCache on our machine. You can find a step-by-step tutorial on how to set up NCache on your windows machine here.
Once, the setup is complete, we are ready to use the NCache server in our .NET 6 app. Let's create a .NET 6 project to learn the caching operations with NCache.
First, let's install the following NuGet package:
Install-Package NCache.Microsoft.Extensions.Caching.
This package uses NCache to implement the IDistributedCache interface.
We need to add the cache to the ASP.NET dependencies container in the Program.cs file after installing the NuGet package.
// Program.cs
using Alachisoft.NCache.Caching.Distributed;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// Add NCache implementation here...
builder.Services.AddNCacheDistributedCache(configuration => {
configuration.CacheName = "myClusteredCache";
configuration.EnableLogs = true;
configuration.ExceptionsEnabled = true;
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment()) {
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
We can also configure the cache settings in the appsettings.json file. To achieve this, we can use another overload of the same method.
builder.Services.AddNCacheDistributedCache(Configuration.GetSection("NCacheSettings"));
// appsettings.josn
{
"NCacheSettings": {
"CacheName": "myClusteredCache",
"EnableLogs": true,
"EnableDetailLogs": false,
"ExceptionsEnabled": true,
"OperationRetry": 0,
"operationRetryInterval": 0
}
}
So far, we've finished the connectivity part. We have our NCache server running on our machine with a cluster cache named myClusterCache. A cluster cache with the name demoCache is created automatically when you install NCache on your machine. Learn about how to create a cluster cache manually here.
Let's create a new extension generic method that will make our life a bit easier to get or set records from the cache server. It contains two methods: SetRecordAsync
, which stores the data in the cache, and GetRecordAsync
, which retrieves the data. In the DistributedCacheEntryOptions, we can pass configuration values. This determines how long the items will be stored in the cache.
public static class DistributedCacheExtensions {
public static async Task SetRecordAsync < T > (this IDistributedCache cache, string recordId, T data, TimeSpan ? absoluteExpireTime = null, TimeSpan ? unusedExpireTime = null) {
var options = new DistributedCacheEntryOptions {
AbsoluteExpirationRelativeToNow = absoluteExpireTime ?? TimeSpan.FromSeconds(60),
SlidingExpiration = unusedExpireTime
};
var jsonData = JsonSerializer.Serialize(data);
await cache.SetStringAsync(recordId, jsonData, options);
}
public static async Task < T > GetRecordAsync < T > (this IDistributedCache cache, string recordId) {
var jsonData = await cache.GetStringAsync(recordId);
if (jsonData is null) {
return default (T);
}
return JsonSerializer.Deserialize < T > (jsonData);
}
}
AbsoluteExpirationRelativeToNow
This method gets or sets an absolute expiry time relative to the current date and time.
SetSlidingExpiration
This works in the same way as Absolute Expiration. If it is not requested for a specified duration of time, the cache will expire. It is important to note that Sliding Expiration should always be set lower than absolute expiration.
Now, let's inject IDistributedCache into the StudentController to access the configured cache.
using DistributedCache_NCache.Extensions;
using DistributedCache_NCache.Models;
using DistributedCache_NCache.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Distributed;
namespace DistributedCache_NCache.Controllers {
[Route("api/[controller]")]
[ApiController]
public class StudentController: ControllerBase {
private readonly IStudent < Student > _studentService;
private readonly IDistributedCache _cache;
public StudentController(IStudent < Student > studentService, IDistributedCache cache) {
_studentService = studentService;
_cache = cache;
}
[HttpGet]
public async Task < IActionResult > Get() {
string cacheKey = "StudentList_" + DateTime.Now.ToString("yyyyMMdd_hhmm");
var student = await _cache.GetRecordAsync < List < Student >> (cacheKey);
if (student is null) {
student = (List < Student > ) await _studentService.ListAllAsync();
await _cache.SetRecordAsync(cacheKey, student);
}
return Ok(student);
}
}
}
In the above snippet, we set the cache key first, so that when we call the method, it checks if the data is present with that cache key in the cache server. If the data is present, then it returns the data from the cache otherwise, it fetches the data from the database, set the data to the cache, and then returns the data.
The second time when we call this method again, the data will be returned from the cache without hitting the database and the response will be much faster.
Let's test our implementation via postman now. Run the application and hit the endpoint "/api/student".
The below screenshot shows that the request took 2.78 seconds for the response.
When I hit the endpoint for the second time, the data is returned from the cache, as it was added to the cache server on the first call. This is a cache hit and the response time is much faster compared to the first call.
The cache hit can also be seen on the NCache Web Manager portal, which allows us to keep track of the cache cluster's health and traffic in real-time.
Summary
Caching is a proven technique used to hold and readily use frequently accessed data. This has excellent benefits, as we can save the expensive database trips and increase the response time significantly and save a lot of resources and money.
While in-memory caching provides a decent start, distributed caching takes things a step further and is best suited to distributed systems and cloud-native solutions.
Along with Redis, Memcached, and other popular caching solutions, NCache is one of the most popular. It includes features like real-time cluster management, Pub/Sub design pattern, and a large library for ASP.NET Core integration.
Check out my DistributedCache-NCache repository on GitHub to follow along with the code we wrote in this post.