| 
 | 1 | +using System;  | 
 | 2 | +using System.Collections.Concurrent;  | 
 | 3 | +using System.Linq;  | 
 | 4 | +using System.Threading;  | 
 | 5 | +using System.Threading.Tasks;  | 
 | 6 | +using GeneXus.Services;  | 
 | 7 | +using Microsoft.AspNetCore.Http;  | 
 | 8 | +using Microsoft.Extensions.Caching.Distributed;  | 
 | 9 | +using StackExchange.Redis;  | 
 | 10 | + | 
 | 11 | +namespace GeneXus.Application  | 
 | 12 | +{  | 
 | 13 | + | 
 | 14 | +	public class CustomRedisSessionStore : IDistributedCache  | 
 | 15 | +	{  | 
 | 16 | +		private readonly IDatabase _db;  | 
 | 17 | +		private readonly TimeSpan _idleTimeout;  | 
 | 18 | +		private readonly TimeSpan _refreshThreshold;  | 
 | 19 | +		private readonly string _instanceName;  | 
 | 20 | + | 
 | 21 | +		public CustomRedisSessionStore(string connectionString, TimeSpan idleTimeout, string instanceName)  | 
 | 22 | +		{  | 
 | 23 | +			var mux = ConnectionMultiplexer.Connect(connectionString);  | 
 | 24 | +			_db = mux.GetDatabase();  | 
 | 25 | +			_idleTimeout = idleTimeout;  | 
 | 26 | +			_refreshThreshold = TimeSpan.FromTicks((long)(idleTimeout.Ticks * 0.2));  | 
 | 27 | +			_instanceName = instanceName ?? string.Empty;  | 
 | 28 | +		}  | 
 | 29 | + | 
 | 30 | +		private string FormatKey(string key) => string.IsNullOrEmpty(_instanceName) ? key : $"{_instanceName}:{key}";  | 
 | 31 | + | 
 | 32 | +		public byte[] Get(string key) => _db.StringGet(FormatKey(key));  | 
 | 33 | + | 
 | 34 | +		public async Task<byte[]> GetAsync(string key, CancellationToken token = default)  | 
 | 35 | +		{  | 
 | 36 | +			string redisKey = FormatKey(key);  | 
 | 37 | +			var value = await _db.StringGetAsync(redisKey);  | 
 | 38 | + | 
 | 39 | +			await RefreshKeyIfNeededAsync(redisKey);  | 
 | 40 | + | 
 | 41 | +			return value;  | 
 | 42 | +		}  | 
 | 43 | + | 
 | 44 | +		public void Refresh(string key)  | 
 | 45 | +		{  | 
 | 46 | +			string redisKey = FormatKey(key);  | 
 | 47 | + | 
 | 48 | +			var ttl = _db.KeyTimeToLive(redisKey);  | 
 | 49 | + | 
 | 50 | +			if (ShouldRefreshKey(ttl))  | 
 | 51 | +			{  | 
 | 52 | +				_db.KeyExpire(redisKey, _idleTimeout);  | 
 | 53 | +			}  | 
 | 54 | +		}  | 
 | 55 | +		private bool ShouldRefreshKey(TimeSpan? ttl)  | 
 | 56 | +		{  | 
 | 57 | +			return ttl.HasValue && ttl.Value < _refreshThreshold;  | 
 | 58 | +		}  | 
 | 59 | +		public async Task RefreshAsync(string key, CancellationToken token = default)  | 
 | 60 | +		{  | 
 | 61 | +			string redisKey = FormatKey(key);  | 
 | 62 | +			await RefreshKeyIfNeededAsync(redisKey);  | 
 | 63 | +		}  | 
 | 64 | +		private async Task RefreshKeyIfNeededAsync(string redisKey)  | 
 | 65 | +		{  | 
 | 66 | +			var ttl = await _db.KeyTimeToLiveAsync(redisKey);  | 
 | 67 | + | 
 | 68 | +			if (ShouldRefreshKey(ttl))  | 
 | 69 | +			{  | 
 | 70 | +				_ = _db.KeyExpireAsync(redisKey, _idleTimeout);  | 
 | 71 | +			}  | 
 | 72 | +		}  | 
 | 73 | +		public void Remove(string key) => _db.KeyDelete(FormatKey(key));  | 
 | 74 | + | 
 | 75 | +		public Task RemoveAsync(string key, CancellationToken token = default)  | 
 | 76 | +			=> _db.KeyDeleteAsync(FormatKey(key));  | 
 | 77 | + | 
 | 78 | +		public void Set(string key, byte[] value, DistributedCacheEntryOptions options)  | 
 | 79 | +		{  | 
 | 80 | +			_db.StringSet(FormatKey(key), value, _idleTimeout);  | 
 | 81 | +		}  | 
 | 82 | + | 
 | 83 | +		public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default)  | 
 | 84 | +		{  | 
 | 85 | +			return _db.StringSetAsync(FormatKey(key), value, _idleTimeout);  | 
 | 86 | +		}  | 
 | 87 | +	}  | 
 | 88 | + | 
 | 89 | +}  | 
0 commit comments