Skip to content

Instantly share code, notes, and snippets.

@poojarsn
Last active December 18, 2020 05:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save poojarsn/0c5aef92428f96e154eda06822c374b7 to your computer and use it in GitHub Desktop.
Save poojarsn/0c5aef92428f96e154eda06822c374b7 to your computer and use it in GitHub Desktop.
Azure Redis cache asp.net provider
public interface ICacheProvider
{
T Get<T>(string key, Func<T> fetch) where T : class;
IEnumerable<T> GetAll<T>(IReadOnlyCollection<string> keys, Func<IEnumerable<T>> fetch, Func<T, string, string> keyGen, string keyPrefix) where T : class;
void Clear(string key);
}
//Encryt and decrypt
//Logging service
//Key for different env same shared redis cache
public class RedisCacheProvider : ICacheProvider
{
private readonly Func<IDatabase> _connectionResolver;
private const double CacheTimeMaxDeviationFraction = 0.2;
private readonly ICryptoService _cyptoService;
private readonly ILoggerService _loggerService;
private readonly string _environment;
private readonly string _encryptionKey = ConfigurationManager.AppSettings[AppSettingKeys.Common.RedisEncryptionKey];
private static readonly Random Random = new Random(Guid.NewGuid().GetHashCode());
public RedisCacheProvider(bool enableCacheEncryption, int cacheExpiration, string environment, Func<IDatabase> connectionResolver, ICryptoService cyptoService,
ILoggerService loggerService)
{
_connectionResolver = connectionResolver;
CacheExpiration = cacheExpiration;
EnableCacheEncryption = enableCacheEncryption;
_cyptoService = cyptoService;
_loggerService = loggerService;
_environment = environment;
}
private int CacheExpiration { get; set; }
private bool EnableCacheEncryption { get; set; }
public T Get<T>(string key, Func<T> fetch) where T : class
{
return Get(key, fetch, CacheExpiration, EnableCacheEncryption);
}
public IEnumerable<T> GetAll<T>(IReadOnlyCollection<string> keys, Func<IEnumerable<T>> fetch,
Func<T, string, string> keyGen, string keyPrefix) where T : class
{
return GetAll(keys, fetch, keyGen, CacheExpiration, EnableCacheEncryption, keyPrefix);
}
private IEnumerable<T> GetAll<T>(IReadOnlyCollection<string> keys, Func<IEnumerable<T>> fetch,
Func<T, string, string> keyGen, int minutesToExpiry, bool enableCacheEncryption, string keyPrefix) where T : class
{
var redisKeys = keys.Select(key => (RedisKey)GetKey(key)).ToArray();
try
{
var redisValue = _connectionResolver().StringGet(redisKeys)?
.Where(r => r.HasValue)
.Select(r => JsonConvert.DeserializeObject<T>(enableCacheEncryption ? _cyptoService.Decrypt(r, _encryptionKey) : r.ToString()));
if (redisValue == null) return null;
var redisValueArr = redisValue as T[] ?? redisValue.ToArray();
if (redisValueArr.Count() == keys.Count)
{
_loggerService.Info($"Cache Hit {keyPrefix}: keys - {keys.Count}");
return redisValueArr;
}
_loggerService.Info($"Cache Miss {keyPrefix}: Search keys - {keys.Count} Redis Keys -{redisValueArr.Count()}");
}
catch (Exception ex)
{
_loggerService.Error($"Error while getting cache from redis. Key: {keyPrefix}, Message: {ex.Message} : {ex}");
return null;
}
var values = fetch();
if (values == null) return null;
var valuesFeatched = values as T[] ?? values.ToArray();
foreach (var value in valuesFeatched)
{
var featchedValue = (enableCacheEncryption) ? _cyptoService.Encrypt(JsonConvert.SerializeObject(value), _encryptionKey) : JsonConvert.SerializeObject(value);
_connectionResolver().StringSet(GetKey(keyGen(value, keyPrefix)), featchedValue, GetCacheAbsoluteExpiration(minutesToExpiry));
}
_loggerService.Info($"Cache Added {keyPrefix}: Search keys - {keys.Count} Featched Keys -{valuesFeatched.Count()}");
return valuesFeatched;
}
}
public void Clear(string key)
{
throw new NotImplementedException();
}
private T Get<T>(string key, Func<T> fetch, int minutesToExpiry, bool enableCacheEncryption) where T : class
{
var redisValue = _connectionResolver().StringGet(GetKey(key));
if (redisValue.HasValue)
{
redisValue = enableCacheEncryption ? _cyptoService.Decrypt(redisValue, _encryptionKey) : redisValue.ToString();
return JsonConvert.DeserializeObject<T>(redisValue);
}
var value = fetch();
if (value == null) return null;
_connectionResolver().StringSet(GetKey(key), enableCacheEncryption ? _cyptoService.Encrypt(JsonConvert.SerializeObject(value), _encryptionKey) : JsonConvert.SerializeObject(value), GetCacheAbsoluteExpiration(minutesToExpiry));
return value;
}
private static TimeSpan? GetCacheAbsoluteExpiration(int requestedMinutes)
{
var maxDeviation = requestedMinutes * CacheTimeMaxDeviationFraction;
var deviation = (Random.NextDouble() - 0.5) * 2 * maxDeviation;
return TimeSpan.FromMinutes(requestedMinutes + deviation);
}
private string GetKey(string key)
{
return string.IsNullOrWhiteSpace(_environment) ? $"{key}" : $"{_environment}:{key}";
}
}
public static class RedisReferenceDataConnnection
{
/// <summary>
/// To use a different App Setting you will need to write your own connection resolver and register it
/// via AutoFac (an example can be seen where RedisCache is registered in ServiceQuotesModule).
/// </summary>
private static readonly string ConnectionString = ConfigurationManager.ConnectionStrings["redis"].ConnectionString;
/// <summary>
/// Static property initialization and lazy loading to simulate connection pooling.
/// This has the constraint of forcing us into a static App Settings key ie 'redisCache'.
/// </summary>
private static readonly Lazy<ConnectionMultiplexer> Connection =
new Lazy<ConnectionMultiplexer>(
() => ConnectionMultiplexer.Connect(ConnectionString));
public static IDatabase GetConnection(int databaseId)
{
return Connection.Value.GetDatabase(databaseId);
}
}
public class AutofacServiceProviderBuilder
: BaseServiceProviderBuilder
{
protected override IServiceProvider BuildServiceProvider(IServiceCollection serviceCollection)
{
var builder = new ContainerBuilder();
/*------------------------------------------------------------------------------------------
* Register Sitecore services in Autofac.
* It goes through all existing services (registered so far by Sitecore) and
* registers them in Autofac, which makes them available in your DI container. If
* Sitecore registers more services later on, they will be registered in Autofac through
* the IServiceProvider adaptor we returned from BuildServiceProvider().
*------------------------------------------------------------------------------------------
*/
builder.Populate(serviceCollection);
// Register our custom services via a module.
builder.RegisterModule<ServicesModule>();
// Register all MVC controllers in the current assembly.
builder.RegisterControllers(Assembly.GetExecutingAssembly());
RegisterServices(builder);
//builder.RegisterFilterProvider();
IContainer container = builder.Build();
Platform.ServiceLocator.SetLocator(container);
return container.Resolve<IServiceProvider>();
}
private static void RegisterServices(ContainerBuilder builder)
{
var cryptoService = new CryptoService();
builder.RegisterInstance<ICryptoService>(cryptoService);
builder.Register(ctx =>
{
var cacheExpirationintvalue = 0;
var enableCacheEncryption = false;
cacheExpirationintvalue = (cacheExpirationintvalue == 0) ? 2000 : cacheExpirationintvalue;
var databaseId = int.Parse(System.Configuration.ConfigurationManager.AppSettings["refDataId"]);
var environment = System.Configuration.ConfigurationManager.AppSettings["Env"];
return new RedisCacheProvider(enableCacheEncryption, cacheExpirationintvalue, environment,
() => RedisReferenceDataConnnection.GetConnection(databaseId), new CryptoService(),
new LoggerService("abc"));
}).As<ICacheProvider>();
;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment