This post describes how you can use Redis Cache as IDistributedCache in ASP.NET Core. Redis Cache is an in-memory data structure store, used as a database, cache and message broker. Redis Cache is used in multi-instance applications to handle session data and cached data.
As an alternative to Redis, you can use an sql database or a no-sql database to handle session data in a multi-instance application. Redis Cache has built-in replication, supports a lot of data structures, can provide high availability and it can be used on Azure.
Install Redis Server on Windows
You need to download and install Redis on your development machine to be able to test your implementation in ASP.NET Core. You can download an msi-file or a zip-file, the msi-file will install Redis and create a windows service that automatically starts Redis server on startup.
When you have installed Redis, make sure that the service has started and that you can ping the server. Open a command prompt as an administrator and browse to the folder where Redis is installed (cd “C:\Program Files\Redis”), start redis-cli.exe and type “ping”.
Microsoft Windows [Version 10.0.18362.239]
(c) 2019 Microsoft Corporation. Med ensamrätt.
C:\WINDOWS\system32>cd..
C:\Windows>cd..
C:\>cd program files
C:\Program Files>cd redis
C:\Program Files\Redis>redis-cli.exe
127.0.0.1:6379> ping
PONG
127.0.0.1:6379>
Application settings
We first need to install a NuGet-package (Microsoft.Extensions.Caching.Redis
) to our project in Visual Studio, it is a distributed cache implementation of IDistributedCache
using Redis. We store a connection string to Redis as application settings in secrets.json
and appsettings.json
. The default port for Redis is 6379, you might have used another port.
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Information"
}
},
"RedisCacheOptions": {
"ConnectionString": "localhost:6379"
}
}
Models
namespace Doxservr.Common.Options
{
public class CacheOptions
{
#region Variables
public double ExpirationInMinutes { get; set; }
#endregion
#region Constructors
public CacheOptions()
{
// Set values for instance variables
this.ExpirationInMinutes = 240;
} // End of the constructor
#endregion
} // End of the class
} // End of the namespace
namespace Doxservr.Common.Options
{
public class RedisCacheOptions
{
#region Variables
public string ConnectionString { get; set; }
#endregion
#region Constructors
public RedisCacheOptions()
{
// Set values for instance variables
this.ConnectionString = "";
} // End of the constructor
#endregion
} // End of the class
} // End of the namespace
using System;
using System.Collections.Generic;
namespace Doxservr.Common.Models
{
public class KeyStringList
{
#region Variables
public IDictionary<string, string> dictionary;
#endregion
#region Constructors
public KeyStringList()
{
// Set values for instance variables
this.dictionary = new Dictionary<string, string>(10);
} // End of the constructor
public KeyStringList(Int32 capacity)
{
// Set values for instance variables
this.dictionary = new Dictionary<string, string>(capacity);
} // End of the constructor
public KeyStringList(IDictionary<string, string> dictionary)
{
// Set values for instance variables
this.dictionary = dictionary;
} // End of the constructor
#endregion
#region Insert methods
public void Add(string key, string value)
{
// Add the value to the dictionary
dictionary.Add(key, value);
} // End of the Add method
#endregion
#region Update methods
public void Update(string key, string value)
{
// Update the value
dictionary[key] = value;
} // End of the Update method
#endregion
#region Get methods
public string Get(string key)
{
// Create the string to return
string value = key;
// Check if the dictionary contains the key
if (this.dictionary.ContainsKey(key))
{
value = this.dictionary[key];
}
// Return the value
return value;
} // End of the Get method
#endregion
} // End of the class
} // End of the namespace
Services
We register services for memory cache, redis cache and sessions in the ConfigureServices
method in the StartUp
class. Memory cache is used as a backup if we do not want to use Redis. Our session service will use Redis Cache if it is registered or memory cache if we set the connection string to an empty string.
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public void ConfigureServices(IServiceCollection services)
{
// Add the mvc framework
services.AddRazorPages();
// Create options
services.Configure<CacheOptions>(options => { options.ExpirationInMinutes = 240d; });
// Add memory cache
services.AddDistributedMemoryCache();
// Add redis distributed cache
if (configuration.GetSection("RedisCacheOptions")["ConnectionString"] != "")
{
services.AddDistributedRedisCache(options =>
{
options.Configuration = configuration.GetSection("RedisCacheOptions")["ConnectionString"];
options.InstanceName = "Mysite:";
});
}
// Add the session service
services.AddSession(options =>
{
// Set session options
options.IdleTimeout = TimeSpan.FromMinutes(30d);
options.Cookie.Name = ".Mysite";
options.Cookie.Path = "/";
options.Cookie.HttpOnly = true;
options.Cookie.SameSite = SameSiteMode.Lax;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
});
} // End of the ConfigureServices method
We also have to use sessions in the Configure method in the StartUp
class to automatically enable session state for the application.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Use sessions
app.UseSession();
} // End of the Configure method
Work with distributed cache
We have a class in which we have injected IDistributedCache, we can now use this interface to get data from redis cache and to save data to redis cache.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Azure.Documents;
using Newtonsoft.Json;
using Doxservr.Common.Options;
using Doxservr.Common.Models;
namespace Doxservr.Common.Repositories
{
public class StaticTextRepository : IStaticTextRepository
{
#region Variables
private readonly ICosmosDatabaseRepository cosmos_database_repository;
private readonly IDistributedCache distributed_cache;
private readonly CacheOptions cache_options;
private readonly ILanguageRepository language_repository;
#endregion
#region Constructors
public StaticTextRepository(ICosmosDatabaseRepository cosmos_database_repository, IDistributedCache distributed_cache, IOptions<CacheOptions> cache_options,
ILanguageRepository language_repository)
{
// Set values for instance variables
this.cosmos_database_repository = cosmos_database_repository;
this.distributed_cache = distributed_cache;
this.cache_options = cache_options.Value;
this.language_repository = language_repository;
} // End of the constructor
#endregion
#region Add methods
public async Task<bool> Add(StaticTextsDocument item)
{
// Create a document
return await this.cosmos_database_repository.Add<StaticTextsDocument>(item);
} // End of the Add method
#endregion
#region Update methods
public async Task<bool> Upsert(StaticTextsDocument item)
{
// Upsert a document
return await this.cosmos_database_repository.Upsert<StaticTextsDocument>(item);
} // End of the Upsert method
public async Task<bool> Update(StaticTextsDocument item)
{
// Replace a document
return await this.cosmos_database_repository.Update<StaticTextsDocument>(item.id, item);
} // End of the Update method
#endregion
#region Get methods
public async Task<ModelItem<StaticTextsDocument>> GetById(string id)
{
// Return the post
return await this.cosmos_database_repository.GetById<StaticTextsDocument>(id, id);
} // End of the GetById method
public async Task<ModelItem<StaticTextsDocument>> GetByLanguageCode(string language_code)
{
// Create the sql string
string sql = "SELECT VALUE s FROM s WHERE s.language_code = @language_code AND s.model_type = @model_type";
// Create parameters
SqlParameterCollection parameters = new SqlParameterCollection()
{
new SqlParameter("@language_code", language_code),
new SqlParameter("@model_type", "static_text")
};
// Return the post
return await this.cosmos_database_repository.GetByQuery<StaticTextsDocument>(sql, parameters);
} // End of the GetByLanguageCode method
public async Task<KeyStringList> GetFromCache(string language_code)
{
// Create the cacheId
string cacheId = "StaticTexts_" + language_code.ToString();
// Get the cached settings
string data = this.distributed_cache.GetString(cacheId);
// Create the list to return
KeyStringList posts = new KeyStringList();
if (data == null)
{
// Get the post
ModelItem<StaticTextsDocument> static_texts_model = await GetByLanguageCode(language_code);
// Make sure that something was found in the database
if (static_texts_model.item == null)
{
return posts;
}
else
{
posts = new KeyStringList(static_texts_model.item.dictionary);
}
// Create cache options
DistributedCacheEntryOptions cacheEntryOptions = new DistributedCacheEntryOptions();
cacheEntryOptions.SetSlidingExpiration(TimeSpan.FromMinutes(this.cache_options.ExpirationInMinutes));
cacheEntryOptions.SetAbsoluteExpiration(TimeSpan.FromMinutes(this.cache_options.ExpirationInMinutes));
// Save data in cache
this.distributed_cache.SetString(cacheId, JsonConvert.SerializeObject(posts), cacheEntryOptions);
}
else
{
posts = JsonConvert.DeserializeObject<KeyStringList>(data);
}
// Return the post
return posts;
} // End of the GetFromCache method
#endregion
#region Delete methods
public async Task<bool> DeleteOnId(string id)
{
// Delete a document
return await this.cosmos_database_repository.DeleteOnId(id, id);
} // End of the DeleteOnId method
#endregion
#region Helper methods
public async Task RemoveFromCache()
{
// Get all languages
ModelItem<LanguagesDocument> languages_model = await this.language_repository.GetByType();
// Loop languages
foreach (KeyValuePair<string, string> entry in languages_model.item.dictionary)
{
// Get the data
string data = this.distributed_cache.GetString("StaticTexts_" + entry.Key);
// Only remove the cache if it exists
if (data != null)
{
// Remove data from cache
this.distributed_cache.Remove("StaticTexts_" + entry.Key);
}
}
} // End of the RemoveFromCache method
#endregion
} // End of the class
} // End of the namespace
Work with sessions
We have added a session extension class to set and get serializable objects as session variables.
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
namespace Doxservr.Common.Helpers
{
public static class SessionExtensions
{
public static void Set<T>(this ISession session, string key, T value)
{
session.SetString(key, JsonConvert.SerializeObject(value));
} // End of the Set method
public static T Get<T>(this ISession session, string key)
{
var value = session.GetString(key);
return value == null ? default(T) : JsonConvert.DeserializeObject<T>(value);
}
} // End of the class
} // End of the namespace
Below are some examples on how you can set and get session variables in ASP.NET Core.
HttpContext.Session.SetString("SessionKey1", "VALUE 1");
HttpContext.Session.SetInt32("SessionKey2", 2);
string value1 = HttpContext.Session.GetString("SessionKey1");
Int32 value2 = HttpContext.Session.GetInt32("SessionKey2");
HttpContext.Session.Set<DateTime>("SessionKey3", DateTime.Now);
DateTime dt = HttpContext.Session.Get<DateTime>("SessionKey3");
can you please add the packages needs to be installed?
Hi,
thank you for your comment. I have added using statements.
can i get whole code if you have ?