Skip to content

Commit 6214647

Browse files
author
tracy.ma
committed
optimization multi tenancy
1 parent 9e15374 commit 6214647

20 files changed

Lines changed: 409 additions & 31 deletions

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
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using BotSharp.Plugin.MultiTenancy.Models;
2+
using System.Threading;
3+
4+
namespace BotSharp.Plugin.MultiTenancy.MultiTenancy;
5+
6+
public class AsyncLocalCurrentTenantAccessor : ICurrentTenantAccessor
7+
{
8+
private readonly AsyncLocal<TenantInfoBasic?> _currentScope;
9+
private AsyncLocalCurrentTenantAccessor()
10+
{
11+
_currentScope = new AsyncLocal<TenantInfoBasic?>();
12+
}
13+
14+
public static AsyncLocalCurrentTenantAccessor Instance { get; } = new();
15+
16+
public TenantInfoBasic? Current
17+
{
18+
get => _currentScope.Value;
19+
set => _currentScope.Value = value;
20+
}
21+
}

0 commit comments

Comments
 (0)