Skip to content

Commit ed3f6bc

Browse files
claudiamurialdoclaudiamurialdo
andauthored
Add support for tenant ID in Redis-based caching (#1175)
* Add support for tenant ID in Redis-based database caching * Always enable TenantMiddleware to support scenarios such as using database caching in Redis or storing sessions in Redis * Fix merge error --------- Co-authored-by: claudiamurialdo <c.murialdo@globant.com>
1 parent fdf9388 commit ed3f6bc

File tree

9 files changed

+88
-19
lines changed

9 files changed

+88
-19
lines changed

dotnet/src/dotnetcore/GxClasses/Properties/AssemblyInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@
2121
[assembly: InternalsVisibleTo("GeneXus.OpenTelemetry.Diagnostics")]
2222
[assembly: InternalsVisibleTo("ConsoleApp2")]
2323
[assembly: InternalsVisibleTo("GxAI")]
24+
[assembly: InternalsVisibleTo("GxRedis")]
2425

dotnet/src/dotnetcore/GxClasses/Services/Session/GXSessionFactory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Threading;
33
using System.Threading.Tasks;
44
using GeneXus.Application;
5+
using GeneXus.Cache;
56
using GeneXus.Configuration;
67
using GeneXus.Data;
78
using GeneXus.Data.ADO;
@@ -70,7 +71,6 @@ public class GxRedisSession : ISessionService
7071
internal static string SESSION_INSTANCE = "SESSION_PROVIDER_INSTANCE_NAME";
7172
internal static string SESSION_PASSWORD = "SESSION_PROVIDER_PASSWORD";
7273
static string SESSION_TIMEOUT = "SESSION_PROVIDER_SESSION_TIMEOUT";
73-
const string SUBDOMAIN = "%SUBDOMAIN%";
7474
public GxRedisSession(GXService serviceProvider)
7575
{
7676
string password = serviceProvider.Properties.Get(SESSION_PASSWORD);
@@ -110,7 +110,7 @@ public GxRedisSession(string host, string password, string instanceName, int ses
110110
}
111111
internal bool IsMultitenant
112112
{
113-
get { return InstanceName == SUBDOMAIN; }
113+
get { return InstanceName == CacheFactory.SUBDOMAIN; }
114114
}
115115
public string ConnectionString { get; }
116116
public string InstanceName { get; }

dotnet/src/dotnetcore/GxNetCoreStartup/SessionHelper.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public TenantRedisCache(IHttpContextAccessor httpContextAccessor, IServiceProvid
2424

2525
private IDistributedCache GetTenantCache()
2626
{
27-
string tenantId = _httpContextAccessor.HttpContext?.Items[TenantMiddleware.TENANT_ID]?.ToString() ?? "default";
27+
string tenantId = _httpContextAccessor.HttpContext?.Items[AppContext.TENANT_ID]?.ToString() ?? "default";
2828

2929
return _redisCaches.GetOrAdd(tenantId, id =>
3030
{
@@ -51,7 +51,6 @@ private IDistributedCache GetTenantCache()
5151

5252
public class TenantMiddleware
5353
{
54-
internal const string TENANT_ID = "TenantId";
5554
private readonly RequestDelegate _next;
5655

5756
public TenantMiddleware(RequestDelegate next)
@@ -61,9 +60,15 @@ public TenantMiddleware(RequestDelegate next)
6160

6261
public async Task Invoke(HttpContext context)
6362
{
64-
string host = context.Request.Host.Host;
65-
string subdomain = host.Split('.').FirstOrDefault();
66-
context.Items[TENANT_ID] = subdomain;
63+
string host = context?.Request?.Host.Host ?? string.Empty;
64+
string subdomain;
65+
66+
if (!string.IsNullOrEmpty(host) && host.Contains('.'))
67+
{
68+
subdomain = host.Split('.').FirstOrDefault();
69+
if (!string.IsNullOrEmpty(subdomain))
70+
context.Items[AppContext.TENANT_ID] = subdomain;
71+
}
6772

6873
await _next(context);
6974
}

dotnet/src/dotnetcore/GxNetCoreStartup/Startup.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -543,11 +543,7 @@ public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IHos
543543
app.UseStaticFiles();
544544

545545
ISessionService sessionService = GXSessionServiceFactory.GetProvider();
546-
GxRedisSession gxRedisSession = sessionService as GxRedisSession;
547-
if (gxRedisSession != null && gxRedisSession.IsMultitenant)
548-
{
549-
app.UseMiddleware<TenantMiddleware>();
550-
}
546+
app.UseMiddleware<TenantMiddleware>();
551547

552548
ConfigureCors(app);
553549
ConfigureSwaggerUI(app, baseVirtualPath);

dotnet/src/dotnetcore/Providers/Cache/GxRedis/GxRedis.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
<Project Sdk="Microsoft.NET.Sdk">
2-
<PropertyGroup>
2+
<PropertyGroup>
33
<TargetFrameworks>net8.0</TargetFrameworks>
44
<PackageTags>Redis</PackageTags>
55
<PackageId>GeneXus.Redis.Core</PackageId>
6+
<DefineConstants>NETCORE</DefineConstants>
67
</PropertyGroup>
78
<PropertyGroup>
89
<AppDesignerFolder>Properties</AppDesignerFolder>

dotnet/src/dotnetframework/GxClasses/Core/GXApplication.cs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,12 @@ public interface IGxContext
285285
#if NETCORE
286286
internal static class AppContext
287287
{
288+
internal const string TENANT_ID = "TenantId";
289+
internal static string TenantId
290+
{
291+
get => Current?.Items[TENANT_ID]?.ToString() ?? "default";
292+
}
293+
288294
static IHttpContextAccessor _httpContextAccessor { get; set; }
289295
internal static HttpContext Current => _httpContextAccessor != null ? new GxHttpContextAccesor(_httpContextAccessor) : null;
290296
internal static void Configure(IHttpContextAccessor accessor)
@@ -365,6 +371,9 @@ public class GxContext : IGxContext
365371
internal const string GXLanguage = "GXLanguage";
366372
internal const string GXTheme = "GXTheme";
367373
internal const string SERVER_VAR_HTTP_HOST = "HTTP_HOST";
374+
#if NETCORE
375+
string _tenantId;
376+
#endif
368377
internal const string CURRENT_GX_CONTEXT = "CURRENT_GX_CONTEXT";
369378
[NonSerialized]
370379
HttpContext _HttpContext;
@@ -1193,10 +1202,8 @@ private void LocalInitialize()
11931202
Config.LoadConfiguration();
11941203
}
11951204

1196-
11971205
public static bool isReorganization { get; set; }
11981206

1199-
12001207
public bool IsMultipartRequest
12011208
{
12021209
get
@@ -1212,6 +1219,23 @@ public bool IsMultipartRequest
12121219
return false;
12131220
}
12141221
}
1222+
#if NETCORE
1223+
internal string TenantId
1224+
{
1225+
get
1226+
{
1227+
if (string.IsNullOrEmpty(_tenantId) && HttpContext != null)
1228+
{
1229+
_tenantId = AppContext.TenantId;
1230+
}
1231+
return _tenantId;
1232+
}
1233+
set
1234+
{
1235+
_tenantId = value;
1236+
}
1237+
}
1238+
#endif
12151239
public IGxContext UtlClone()
12161240
{
12171241
//Context for new LUW
@@ -4053,6 +4077,9 @@ public void SetSubmitInitialConfig(IGxContext context)
40534077
this.SetPhysicalPath(context.GetPhysicalPath());
40544078
this.SetLanguageWithoutSession(context.GetLanguage());
40554079
this.ClientID = context.ClientID;
4080+
#if NETCORE
4081+
this.TenantId = (context as GxContext)?.TenantId;
4082+
#endif
40564083
InitializeSubmitSession(context, this);
40574084
}
40584085

@@ -4235,4 +4262,4 @@ public override string GetScriptPath()
42354262
}
42364263
}
42374264

4238-
}
4265+
}

dotnet/src/dotnetframework/GxClasses/Services/Caching/GxCache.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOp
9595
public class CacheFactory
9696
{
9797
private static readonly IGXLogger log = GXLoggerFactory.GetLogger<CacheFactory>();
98-
98+
internal const string SUBDOMAIN = "%SUBDOMAIN%";
9999
public static string CACHE_SD = "SD";
100100
public static string CACHE_DB = "DB";
101101
public static string CACHE_FILES = "FL";

dotnet/src/dotnetframework/Providers/Cache/GxRedis/GxRedis.cs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33
using System.Linq;
44
using System.Text.Json;
55
using System.Threading.Tasks;
6+
#if NETCORE
7+
using GeneXus.Application;
8+
#endif
69
using GeneXus.Encryption;
710
using GeneXus.Services;
811
using GeneXus.Utils;
912
using StackExchange.Redis;
13+
using StackExchange.Redis.KeyspaceIsolation;
1014

1115
namespace GeneXus.Cache
1216
{
@@ -16,9 +20,14 @@ public sealed class Redis : ICacheService2
1620

1721
ConnectionMultiplexer _redisConnection;
1822
IDatabase _redisDatabase;
23+
#if NETCORE
24+
bool _multitenant;
25+
#endif
1926
ConfigurationOptions _redisConnectionOptions;
2027
private const int REDIS_DEFAULT_PORT = 6379;
2128
public int redisSessionTimeout;
29+
private string _instanceName;
30+
2231
public Redis(string connectionString)
2332
{
2433
_redisConnectionOptions = ConfigurationOptions.Parse(connectionString);
@@ -39,6 +48,8 @@ public Redis()
3948
string address, password;
4049
address = providerService.Properties.Get("CACHE_PROVIDER_ADDRESS");
4150
password = providerService.Properties.Get("CACHE_PROVIDER_PASSWORD");
51+
_instanceName = providerService.Properties.Get("CACHE_PROVIDER_INSTANCE_NAME");
52+
4253
if (!string.IsNullOrEmpty(password))
4354
{
4455
string ret = string.Empty;
@@ -74,7 +85,29 @@ IDatabase RedisDatabase
7485
if (_redisDatabase == null)
7586
{
7687
_redisConnection = ConnectionMultiplexer.Connect(_redisConnectionOptions);
77-
_redisDatabase = _redisConnection.GetDatabase();
88+
IDatabase db = _redisConnection.GetDatabase();
89+
90+
if (!string.IsNullOrEmpty(_instanceName))
91+
{
92+
#if NETCORE
93+
if (_instanceName == CacheFactory.SUBDOMAIN)
94+
{
95+
_multitenant = true;
96+
GXLogging.Debug(log, "Using Redis multitenant (key prefix):" + CacheFactory.SUBDOMAIN);
97+
_redisDatabase = db;
98+
}
99+
else
100+
#endif
101+
{
102+
string prefixKey = _instanceName.EndsWith(":") ? _instanceName : _instanceName + ":";
103+
_redisDatabase = db.WithKeyPrefix(_instanceName);
104+
GXLogging.Debug(log, "Using Redis instance name (key prefix): " + prefixKey);
105+
}
106+
}
107+
else
108+
{
109+
_redisDatabase = db;
110+
}
78111
}
79112
return _redisDatabase;
80113
}
@@ -221,6 +254,12 @@ private IEnumerable<RedisKey> Key(string cacheid, IEnumerable<string> key)
221254
}
222255
private string FormatKey(string cacheid, string key, Nullable<long> prefix)
223256
{
257+
#if NETCORE
258+
if (_multitenant)
259+
{
260+
return String.Format("{0}:{1}_{2}_{3}", GxContext.Current.TenantId, cacheid, prefix, GXUtil.GetHash(key));
261+
}
262+
#endif
224263
return String.Format("{0}_{1}_{2}", cacheid, prefix, GXUtil.GetHash(key));
225264
}
226265
private Nullable<long> KeyPrefix(string cacheid)

dotnet/src/dotnetframework/Providers/Cache/GxRedis/GxRedis.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<PackageId>GeneXus.Redis</PackageId>
99
</PropertyGroup>
1010
<ItemGroup>
11-
<PackageReference Include="StackExchange.Redis" Version="2.2.4" />
11+
<PackageReference Include="StackExchange.Redis" Version="2.6.122" />
1212
<PackageReference Include="System.Text.Json" Version="8.0.5" />
1313
</ItemGroup>
1414
<ItemGroup>

0 commit comments

Comments
 (0)