Skip to content

Commit 705a4dd

Browse files
authored
Merge pull request #1293 from tracyma-05/feature/optimization_multitenancy
optimization multi tenancy
2 parents 04f023c + 2936d45 commit 705a4dd

21 files changed

Lines changed: 438 additions & 36 deletions

src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheAttribute.cs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using BotSharp.Abstraction.Infrastructures;
2+
using BotSharp.Abstraction.MultiTenancy;
23
using Microsoft.AspNetCore.Http;
34
using Microsoft.Extensions.DependencyInjection;
45
using Rougamo;
@@ -76,7 +77,7 @@ public virtual Task<bool> IsOutOfDate(MethodContext context, object value)
7677
private string GetCacheKey(MethodContext context)
7778
{
7879
var prefixKey = GetPrefixKey(context.Method.Name);
79-
var argsKey = string.Join("_", context.Arguments.Select(arg => GetCacheKeyByArg(arg)));
80+
var argsKey = string.Join("_", context.Arguments.Select(arg => GetCacheKeyByArg(arg)));
8081

8182
if (_perInstanceCache && context.Target != null)
8283
{
@@ -85,22 +86,41 @@ private string GetCacheKey(MethodContext context)
8586
else
8687
{
8788
return $"{prefixKey}_{argsKey}";
88-
}
89+
}
8990
}
9091

9192
private string GetPrefixKey(string name)
9293
{
94+
var tenantId = GetTenantId();
95+
if (!string.IsNullOrWhiteSpace(tenantId))
96+
{
97+
return $"{_settings.Prefix}:{tenantId}:{name}";
98+
}
99+
93100
return _settings.Prefix + ":" + name;
94101
}
95102

103+
private string? GetTenantId()
104+
{
105+
try
106+
{
107+
var tenant = Services.GetService<ICurrentTenant>();
108+
return tenant?.TenantId;
109+
}
110+
catch
111+
{
112+
return null;
113+
}
114+
}
115+
96116
private string GetCacheKeyByArg(object? arg)
97117
{
98118
if (arg is null)
99119
{
100120
return NullMarker.GetHashCode().ToString();
101121
}
102122
else if (arg is ICacheKey withCacheKey)
103-
{
123+
{
104124
return withCacheKey.GetCacheKey();
105125
}
106126
else
@@ -110,7 +130,10 @@ private string GetCacheKeyByArg(object? arg)
110130
}
111131

112132
public async Task ClearCacheAsync()
113-
{
114-
await _cache.ClearCacheAsync(_settings.Prefix);
133+
{
134+
var tenantId = GetTenantId();
135+
var prefix = string.IsNullOrWhiteSpace(tenantId) ? _settings.Prefix : $"{_settings.Prefix}:{tenantId}";
136+
await _cache.ClearCacheAsync(prefix);
115137
}
138+
116139
}

src/Infrastructure/BotSharp.Abstraction/MultiTenancy/ConnectionStringNameAttribute.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Reflection;
2+
13
namespace BotSharp.Abstraction.MultiTenancy;
24

35
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
@@ -10,7 +12,11 @@ public ConnectionStringNameAttribute(string name)
1012
Name = name;
1113
}
1214

13-
public static string GetConnStringName(Type type) => type.FullName ?? string.Empty;
15+
public static string GetConnStringName(Type type)
16+
{
17+
var customAttribute = type.GetTypeInfo().GetCustomAttribute<ConnectionStringNameAttribute>();
18+
return customAttribute == null ? type.FullName : customAttribute.Name;
19+
}
1420

1521
public static string GetConnStringName<T>() => GetConnStringName(typeof(T));
1622
}

src/Infrastructure/BotSharp.Abstraction/MultiTenancy/IConnectionStringResolver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ public interface IConnectionStringResolver
55
string? GetConnectionString(string connectionStringName);
66

77
string? GetConnectionString<TContext>();
8-
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace BotSharp.Abstraction.MultiTenancy;
2+
public interface IMultiTenant
3+
{
4+
/// <summary>
5+
/// Id of the related tenant.
6+
/// </summary>
7+
string? TenantId { get; }
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using BotSharp.Abstraction.MultiTenancy.Options;
2+
3+
namespace BotSharp.Abstraction.MultiTenancy;
4+
5+
public interface ITenantRepository
6+
{
7+
List<TenantConfiguration> GetTenants();
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using BotSharp.Abstraction.MultiTenancy.Options;
2+
3+
namespace BotSharp.Abstraction.MultiTenancy;
4+
5+
public interface ITenantStore
6+
{
7+
List<TenantConfiguration> GetTenants();
8+
}

src/Infrastructure/BotSharp.Abstraction/MultiTenancy/Options/TenantConfiguration.cs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,19 @@ public TenantConfiguration()
1919
IsActive = true;
2020
}
2121

22-
public TenantConfiguration(Guid id, [NotNull] string name)
23-
: this()
22+
public TenantConfiguration(Guid id, [NotNull] string name) : this()
2423
{
25-
if (string.IsNullOrWhiteSpace(name))
26-
{
27-
throw new ArgumentException("Name cannot be null or whitespace.");
28-
}
29-
24+
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Name cannot be null or whitespace.");
3025
Id = id;
3126
Name = name;
27+
3228
ConnectionStrings = new ConnectionStrings();
3329
}
3430

3531
public TenantConfiguration(Guid id, [NotNull] string name, [NotNull] string normalizedName)
3632
: this(id, name)
3733
{
38-
if (string.IsNullOrWhiteSpace(normalizedName))
39-
{
40-
throw new ArgumentException("NormalizedName cannot be null or whitespace.");
41-
}
42-
34+
if (string.IsNullOrWhiteSpace(normalizedName)) throw new ArgumentException("NormalizedName cannot be null or whitespace.");
4335
NormalizedName = normalizedName;
4436
}
4537
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using BotSharp.Abstraction.MultiTenancy.Options;
2+
using Microsoft.Extensions.Options;
3+
4+
namespace BotSharp.Abstraction.MultiTenancy;
5+
6+
public static class TenantStoreExtensions
7+
{
8+
public static bool IsEnabled(this IOptionsMonitor<TenantStoreOptions> options)
9+
=> options.CurrentValue.Enabled;
10+
11+
public static bool HasConfiguredTenants(this IOptionsMonitor<TenantStoreOptions> options)
12+
=> options.CurrentValue.Tenants is { Length: > 0 };
13+
14+
public static IReadOnlyList<TenantConfiguration> ConfiguredTenants(this IOptionsMonitor<TenantStoreOptions> options)
15+
=> options.CurrentValue.Tenants;
16+
}
Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,40 @@
11
using BotSharp.Abstraction.MultiTenancy;
2+
using BotSharp.Abstraction.MultiTenancy.Options;
23
using Microsoft.AspNetCore.Authorization;
34
using Microsoft.AspNetCore.Mvc;
5+
using Microsoft.Extensions.Options;
46
using System.Linq;
5-
using System.Threading.Tasks;
67

78
namespace BotSharp.Plugin.MultiTenancy.Controllers;
89

910
[ApiController]
1011
public class TenantsController : ControllerBase
1112
{
12-
private readonly ITenantOptionProvider _tenantOptionProvider;
13+
private readonly ITenantStore _tenantStore;
14+
private readonly IOptionsMonitor<TenantStoreOptions> _options;
1315

14-
public TenantsController(ITenantOptionProvider tenantOptionProvider)
16+
public TenantsController(ITenantStore tenantStore, IOptionsMonitor<TenantStoreOptions> options)
1517
{
16-
_tenantOptionProvider = tenantOptionProvider;
18+
_tenantStore = tenantStore;
19+
_options = options;
1720
}
1821

1922
[AllowAnonymous]
2023
[HttpGet]
2124
[Route("/tenants/options")]
22-
public async Task<IActionResult> Options()
25+
public IActionResult Options()
2326
{
24-
var tenants = await _tenantOptionProvider.GetOptionsAsync();
25-
var payload = tenants.Select(t => new { tenantId = t.TenantId, name = t.Name }).ToArray();
27+
if (!_options.CurrentValue.Enabled)
28+
{
29+
return Ok(System.Array.Empty<object>());
30+
}
31+
32+
var tenants = _tenantStore.GetTenants();
33+
var payload = tenants
34+
.Select(t => new { tenantId = t.Id, name = t.Name })
35+
.Distinct()
36+
.ToArray();
37+
2638
return Ok(payload);
2739
}
2840
}

src/Plugins/BotSharp.Plugin.MultiTenancy/Extensions/MultiTenancyServiceCollectionExtensions.cs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,58 @@
11
using BotSharp.Abstraction.MultiTenancy;
22
using BotSharp.Abstraction.MultiTenancy.Options;
3-
using BotSharp.Plugin.MultiTenancy.Interfaces;
43
using BotSharp.Plugin.MultiTenancy.Models;
54
using BotSharp.Plugin.MultiTenancy.MultiTenancy;
6-
using BotSharp.Plugin.MultiTenancy.MultiTenancy.Providers;
75
using BotSharp.Plugin.MultiTenancy.MultiTenancy.Resolvers;
8-
using BotSharp.Plugin.MultiTenancy.MultiTenancy.Tenant;
96
using Microsoft.Extensions.Configuration;
107
using Microsoft.Extensions.DependencyInjection;
118
using Microsoft.Extensions.DependencyInjection.Extensions;
9+
using Microsoft.Extensions.Options;
10+
using System.Collections.Generic;
1211

1312
namespace BotSharp.Plugin.MultiTenancy.Extensions;
1413

1514
public static class MultiTenancyServiceCollectionExtensions
1615
{
1716
public static IServiceCollection AddMultiTenancy(this IServiceCollection services, IConfiguration configuration, string sectionName = "TenantStore")
1817
{
18+
var section = configuration.GetSection(sectionName);
19+
if (section.Exists())
20+
{
21+
services.Configure<TenantStoreOptions>(section);
22+
}
23+
else
24+
{
25+
services.Configure<TenantStoreOptions>(_ => { });
26+
}
27+
1928
services.Configure<TenantResolveOptions>(options =>
2029
{
2130
options.TenantResolvers.Add(new ClaimsTenantResolveContributor());
2231
options.TenantResolvers.Add(new HeaderTenantResolveContributor());
2332
options.TenantResolvers.Add(new QueryStringTenantResolveContributor());
2433
});
2534

26-
services.Configure<TenantStoreOptions>(configuration.GetSection(sectionName));
27-
services.AddScoped<ICurrentTenant, CurrentTenant>();
28-
services.AddSingleton<ICurrentTenantAccessor>(AsyncLocalCurrentTenantAccessor.Instance);
2935
services.AddScoped<ITenantResolver, TenantResolver>();
30-
services.AddScoped<IConnectionStringResolver, DefaultConnectionStringResolver>();
31-
services.AddScoped<ITenantConnectionProvider, TenantConnectionProvider>();
32-
services.AddSingleton<ITenantFeature, TenantFeature>();
3336
services.AddScoped<MultiTenancyMiddleware>();
37+
services.AddScoped<ICurrentTenant, CurrentTenant>();
38+
services.AddSingleton<ICurrentTenantAccessor>(AsyncLocalCurrentTenantAccessor.Instance);
39+
40+
// tenant store infrastructure
41+
services.AddMemoryCache();
42+
services.TryAddScoped<ITenantRepository, NullTenantRepository>();
43+
services.TryAddScoped<ConfigTenantStore>();
44+
services.TryAddScoped<DbTenantStore>();
45+
services.TryAddScoped<ITenantStore>(sp => new CompositeTenantStore(
46+
sp.GetRequiredService<IOptionsMonitor<TenantStoreOptions>>(),
47+
new List<ITenantStore>
48+
{
49+
sp.GetRequiredService<ConfigTenantStore>(),
50+
sp.GetRequiredService<DbTenantStore>()
51+
}));
3452

35-
services.TryAddScoped<ITenantOptionProvider, ConfigTenantOptionProvider>();
53+
services.TryAddScoped<IConnectionStringResolver, DefaultConnectionStringResolver>();
54+
services.TryAddScoped<ITenantFeature, TenantFeature>();
55+
services.TryAddScoped<ITenantConnectionProvider, TenantConnectionProvider>();
3656

3757
return services;
3858
}

0 commit comments

Comments
 (0)