Implement a distributed Redis cache in ASP.Net Core Web API
What is a distributed Cache?
A cache shared by multiple app servers is typically maintained as an external service to the app servers that access it.
A distributed cache can improve the performance and scalability of an ASP.NET Core app, especially when the app is hosted by a cloud service or a server farm (multi-server environment).
A distributed cache has several advantages over other caching scenarios where cached data is stored on individual app servers.
A distributed cache is shared across multiple application servers. The cached data doesn’t reside in the memory of an individual web server. But this data is stored centrally so it is available to all of the application’s servers.
If some of the servers down or stop responding due to technical or network issues, other servers will still be able to retrieve the cached data. And one more advantage of a distributed cache is that the cached data is still available even the server restarts. Since cache works independently will not affect when you can still be able to add or remove a server.
These are the 4 types of distributed caches, and this article covers Redis Distributed Cache in step-by-step implementation.
In ASP.NET Core using Redis distributed cache is that simple to do when you through the entire article. The Distributed caching features implemented in ASP.NET Core using C# program. This article covers ASP.NET Core Web API 2 with Azure Redis Cache implementation in an easy way.
Types of Distributed Cache- Distributed Memory Cache
- Distributed SQL Server Cache
- Distributed Redis Cache
- Distributed NCache Cache
What is Redis Cache?
Redis is an open-source in-memory data store, which is often used as a distributed cache. You can configure an Azure Redis Cache for an Azure-hosted ASP.NET Core app, and use an Azure Redis Cache for local development.
Azure Cache for Redis provides an in-memory data store based on the Redis software. Redis improves the performance and scalability of an application that uses backend data stores heavily. It can process large volumes of application requests by keeping frequently accessed data in the server memory that can be written to and read quickly. Redis brings a critical low-latency and high-throughput data storage solution to modern applications.
How do I create Azure Redis Cache?
To continue the next section I assume you have created Azure Redis Cache Database.
If you are new to Azure Redis How to create an Azure Redis Distributed Cache
* Please note to secure when you copy Azure Primary Access Key
What tools are used to implement?
Create a new project in Visual Studio 2017 or Visual Studio 2019, choose the ASP.NET Core Web API 2 project with an empty template.
If you are new to this how to create ASP.NET Core Web API 2 - here it is
I created a new Web API project- what is next?
You need to know a bit about IDistributedCache Interface
What is IDistributedCache?
The IDistributedCache interface includes synchronous and asynchronous methods. The interface allows items to be added, retrieved, and removed from the distributed cache implementation.
Using this interface is an incorporate a method to implement in your services because this interface is common for any distributed cache you choose.
The IDistributedCache interface includes the following methods:
Get, GetAsync
Takes a string key and retrieves a cached item as a byte[] if found in the cache.
Set, SetAsync
Adds an item (as byte[]) to the cache using a string key. You must save data in byte array content
Refresh, RefreshAsync
Refreshes an item in the cache based on its key, resetting its sliding expiration timeout (if any).
Remove, RemoveAsync
Removes a cache entry based on its key.
IDistributedCache
public interface IDistributedCache
{
byte[] Get(string key);
Task<byte[]> GetAsync(string key, CancellationToken token = default(CancellationToken));
void Refresh(string key);
Task RefreshAsync(string key, CancellationToken token = default(CancellationToken));
void Remove(string key);
Task RemoveAsync(string key, CancellationToken token = default(CancellationToken));
void Set(string key, byte[] value, DistributedCacheEntryOptions options);
Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken));
}
public interface IDistributedCache
{
byte[] Get(string key);
Task<byte[]> GetAsync(string key, CancellationToken token = default(CancellationToken));
void Refresh(string key);
Task RefreshAsync(string key, CancellationToken token = default(CancellationToken));
void Remove(string key);
Task RemoveAsync(string key, CancellationToken token = default(CancellationToken));
void Set(string key, byte[] value, DistributedCacheEntryOptions options);
Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken));
}
Step 1: How to begin with Visual Studio 2017 or 2019?
First Step
Go to your Visual Studio new project solution created, and add dependency supporter for Azure Redis Distributed Cache "Microsoft.Extensions.Caching.Redis"
1. Right-click on the Solution Explorer,
2. Choose "Manage NuGet packages"
3. Search for "Microsoft.Extensions.Caching.Redis" and install
*Note: I installed version 2.1.2 because of Visual Studio 2017 project and a .NET Core 2.1
Step 2: Add Azure Redis Connection String?
Go to your Azure portal http://portal.azure.com/ to copy the Primary Connection String as shown below
Open the project add the following code in the appsettings.json file, the connection string is added under "RedisCache" section
Open the project and click on the "Startup. cs" file,
Go to a method public void ConfigureServices(IServiceCollection services)
Add these lines of code inside
public void ConfigureServices(IServiceCollection services)
{
//Redis DB Connection
services.AddDistributedRedisCache(option =>
{
option.Configuration = Configuration.GetSection("RedisCache").GetSection("ConnectionString").Value;
});
}
public void ConfigureServices(IServiceCollection services)
{
//Redis DB Connection
services.AddDistributedRedisCache(option =>
{
option.Configuration = Configuration.GetSection("RedisCache").GetSection("ConnectionString").Value;
});
}
Step 4: How do I save and retrieve data from Azure Redis Cache?
The data is stored in Azure as a key value-based and content is stored as a byte [ ] array with a time interval of expiry.
For example SaveData(key, byte []) Key param is a string type
Let's make use of the Dependency interface IDistributedCache in a class object. If you have read the above section of Step1 interface methods are explicitly displaced.
This is a simple class that explains as below,
Create an interface "IRedisCache" that implements three methods
IRedisCache.cs
public interface IRedisService
{
T GetRedisData<T>(string key);
string GetRedisData(string key);
void SetRedisData(string key, byte[] value, int expiry);
}
public interface IRedisService
{
T GetRedisData<T>(string key);
string GetRedisData(string key);
void SetRedisData(string key, byte[] value, int expiry);
}
Method 1 : T GetRedisData<T>(string key);
This is to retrieve by key and return a class object stored in Azure
Method 2 : string GetRedisData(string key);
This is to retrieve by key and return a string stored in Azure
Method 3 : void SetRedisData(string key, byte[] value, int expiry);
This is to store data or class object convert into an array byte by key and set with an expiry of a resource.
Now, create a class "RedisCache" that implements "IRedisCache".
If you notice the below class that implements ILoggerService East way to implement
This is the class we implement IDistributedCache in constructor DI for accessing Redis Azure base
Note
Consider RedisCache as a base class because when the main class
DemoService.cs
(in the next section) will make use of the IRedisCache methods implemented in RedisCache. This is to hide method implementation and to make it available as method declaration.RedisCache.cs [base class]
public class RedisService : IRedisService
{
private readonly ILoggerService _loggerService;
private readonly IDistributedCache _cache;
public RedisService(IDistributedCache cache, ILoggerService loggerService)
{
_cache = cache;
_loggerService = loggerService;
}
public void SetRedisData(string key, byte[] value, int expiry)
{
try
{
if (key == null) throw new Exception($"Unable to define key={key}.");
_cache.Set(key, value, new DistributedCacheEntryOptions()
{
AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(expiry)
});
}
catch (Exception ex)
{
_loggerService.LogMessage(ex);
throw ex;
}
}
public T GetRedisData<T>(string key)
{
try
{
var decodedStr = GetRedisData(key);
return JsonConvert.DeserializeObject<T>(decodedStr);
}
catch (Exception ex)
{
_loggerService.LogMessage(ex);
throw ex;
}
}
public string GetRedisData(string key)
{
try
{
var data = _cache.Get(key);
if (data == null) throw new Exception($"Unable to find data for key={key}.");
var decodedStr = Encoding.UTF8.GetString(data);
if (string.IsNullOrEmpty(decodedStr)) throw new Exception($"Decoded data is empty for key={key}");
return decodedStr;
}
catch (Exception ex)
{
_loggerService.LogMessage(ex);
throw ex;
}
}
}
public class RedisService : IRedisService { private readonly ILoggerService _loggerService; private readonly IDistributedCache _cache; public RedisService(IDistributedCache cache, ILoggerService loggerService) { _cache = cache; _loggerService = loggerService; } public void SetRedisData(string key, byte[] value, int expiry) { try { if (key == null) throw new Exception($"Unable to define key={key}."); _cache.Set(key, value, new DistributedCacheEntryOptions() { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(expiry) }); } catch (Exception ex) { _loggerService.LogMessage(ex); throw ex; } } public T GetRedisData<T>(string key) { try { var decodedStr = GetRedisData(key); return JsonConvert.DeserializeObject<T>(decodedStr); } catch (Exception ex) { _loggerService.LogMessage(ex); throw ex; } } public string GetRedisData(string key) { try { var data = _cache.Get(key); if (data == null) throw new Exception($"Unable to find data for key={key}."); var decodedStr = Encoding.UTF8.GetString(data); if (string.IsNullOrEmpty(decodedStr)) throw new Exception($"Decoded data is empty for key={key}"); return decodedStr; } catch (Exception ex) { _loggerService.LogMessage(ex); throw ex; } } }
So far we have defined our base class interface and class.
Next is to create a model
Product.cs
that represents a class object or entry to be stored in AzureProduct. cs
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
}
Now let's create an interface
IDemoService.cs
for method declaration.IDemoService.cs
public interface IDemoService
{
IActionResult FetchData(string userId);
IActionResult FetchObjectData(string userId);
IActionResult SaveData(string userId,Product product);
}
public interface IDemoService
{
IActionResult FetchData(string userId);
IActionResult FetchObjectData(string userId);
IActionResult SaveData(string userId,Product product);
}
On following create a service class
DemoService.cs
that implements the IDemoService interfaceDemoService. cs
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Demo.Services
{
public class DemoService : IDemoService
{
/// <summary>
/// Declare Interface as private readonly for secure
/// </summary>
private readonly IRedisService _redisService;
/// <summary>
/// Construction interface object Dependency Injection
/// </summary>
/// <param name="redisService"></param>
public DemoService(IRedisService redisService)
{
_redisService = redisService;
}
/// <summary>
/// Save a product entry to Azure
/// </summary>
/// <param name="userId"></param>
/// <returns>Ok Success</returns>
public IActionResult SaveData(string userId, Product product)
{
//Save in Azure Redis Cache
//1.Convert to json object
//2.Convert to Bytes[]
var _productdata = this.ToBytes(this.ToJson(product));
//3.Save with unique key,data and expiry time - ** Note to remeber Key to retrive
_redisService.SetRedisData(userId, _productdata, 30);
return JsonOk("Successfully Saved");
}
/// <summary>
/// Fetch class object from Azure Redis
/// </summary>
/// <param name="userId"></param>
/// <returns>Product entry</returns>
public IActionResult FetchObjectData(string userId)
{
var result = _redisService.GetRedisData<Product>(userId);
return JsonOk(result);
}
/// <summary>
/// Fetch string value from Azure Redis
/// </summary>
/// <param name="userId"></param>
/// <returns>string</returns>
public IActionResult FetchData(string userId)
{
var result = _redisService.GetRedisData(userId);
return JsonOk(result);
}
private string ToJson(object obj)
{
return JsonConvert.SerializeObject(obj);
}
private byte[] ToBytes(string jsonText)
{
if (string.IsNullOrEmpty(jsonText)) throw new Exception($"The json text is empty = {jsonText}");
return Encoding.UTF8.GetBytes(jsonText);
}
private OkObjectResult JsonOk(object obj)
{
return new OkObjectResult(JsonConvert.SerializeObject(obj));
}
}
}
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Demo.Services
{
public class DemoService : IDemoService
{
/// <summary>
/// Declare Interface as private readonly for secure
/// </summary>
private readonly IRedisService _redisService;
/// <summary>
/// Construction interface object Dependency Injection
/// </summary>
/// <param name="redisService"></param>
public DemoService(IRedisService redisService)
{
_redisService = redisService;
}
/// <summary>
/// Save a product entry to Azure
/// </summary>
/// <param name="userId"></param>
/// <returns>Ok Success</returns>
public IActionResult SaveData(string userId, Product product)
{
//Save in Azure Redis Cache
//1.Convert to json object
//2.Convert to Bytes[]
var _productdata = this.ToBytes(this.ToJson(product));
//3.Save with unique key,data and expiry time - ** Note to remeber Key to retrive
_redisService.SetRedisData(userId, _productdata, 30);
return JsonOk("Successfully Saved");
}
/// <summary>
/// Fetch class object from Azure Redis
/// </summary>
/// <param name="userId"></param>
/// <returns>Product entry</returns>
public IActionResult FetchObjectData(string userId)
{
var result = _redisService.GetRedisData<Product>(userId);
return JsonOk(result);
}
/// <summary>
/// Fetch string value from Azure Redis
/// </summary>
/// <param name="userId"></param>
/// <returns>string</returns>
public IActionResult FetchData(string userId)
{
var result = _redisService.GetRedisData(userId);
return JsonOk(result);
}
private string ToJson(object obj)
{
return JsonConvert.SerializeObject(obj);
}
private byte[] ToBytes(string jsonText)
{
if (string.IsNullOrEmpty(jsonText)) throw new Exception($"The json text is empty = {jsonText}");
return Encoding.UTF8.GetBytes(jsonText);
}
private OkObjectResult JsonOk(object obj)
{
return new OkObjectResult(JsonConvert.SerializeObject(obj));
}
}
}
So now you have successfully created a Demo service class implemented the base class.
Create a Web API Controller
The next final action is to create a Controller
DemoController.cs
implement the demo services, this controller created to execute by the below functions.
1. Save Product Data [POST]
2. Retrieve Product Data as class object entry [GET]
3. Retrieve Product property as a string value [GET]
I used the controller action method parameter as [Frombody] to get input from user request
DemoController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using IRAS_API_POC.Interfaces;
using IRAS_API_POC.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace IRAS_API_POC.Controllers.abc
{
public class DemoController : ControllerBase
{
private readonly IDemoService _demoService;
public DemoController(IDemoService demoService)
{
_demoService = demoService;
}
[HttpPost]
public IActionResult SaveProduct([FromBody] string userId, Product product)
{
if (product == null)
{
new Product
{
Id = 1,
Category = "A",
Name = "Dell",
Price = 34
};
}
return _demoService.SaveData(userId, product);
}
[HttpGet]
public IActionResult GetProduct([FromBody] string userId)
{
//returns product object data
return _demoService.FetchObjectData(userId);
}
[HttpGet]
public IActionResult GetProductData([FromBody] string userId)
{
//returns string value
return _demoService.FetchData(userId);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using IRAS_API_POC.Interfaces;
using IRAS_API_POC.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace IRAS_API_POC.Controllers.abc
{
public class DemoController : ControllerBase
{
private readonly IDemoService _demoService;
public DemoController(IDemoService demoService)
{
_demoService = demoService;
}
[HttpPost]
public IActionResult SaveProduct([FromBody] string userId, Product product)
{
if (product == null)
{
new Product
{
Id = 1,
Category = "A",
Name = "Dell",
Price = 34
};
}
return _demoService.SaveData(userId, product);
}
[HttpGet]
public IActionResult GetProduct([FromBody] string userId)
{
//returns product object data
return _demoService.FetchObjectData(userId);
}
[HttpGet]
public IActionResult GetProductData([FromBody] string userId)
{
//returns string value
return _demoService.FetchData(userId);
}
}
}
The code ends with this web API controller. When you execute each method in the Postman tool would get a result.
Summary
Until now, we have learned basic concepts of Distributed Cache and types, in this article covered Azure Redis Distributed cache and its implementation in Web API step-by-step.
What are the tools used?
Development Tool: Visual Studio 2017
Project Temple Framework: ASP.NET Core Web API 2
Database: Azure Redis Distributed Cache
We identified the IDistrubuted Cache interface that has saving, retrieval methods for azure cache implementation.
Overall What we implemented?
1. Created interface IRedisCache, which is inherited in RedisCache base class.
2. Created a middle class or layer interface IDemoService, which is inherited in DemoService class
3. Created a Controller which is DemoController and used IDemoService interface as Constructor Dependency Injection
What actually we did with the Azure Redis Distributed Cache?
1. Created an Azure Redis distributed cache in Azure Portal
2. Initiated Redis Connection String in appSettings.json
3. Created a Product Model class with properties
4. Created base class interfaces and classes
5. Created middle layer service that will Save Data, Retrieve Data string, Retrieve class object.
6. Created a Controller which executes all three actions methods based on user request send.
0 Comments