From 30614929731f1e49865b9afd0409205f4ebbaf92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 19 Dec 2016 23:50:26 +0300 Subject: [PATCH] Determine connection string by current tenant. Resolved #12. --- .../ITenantConnectionStringStore.cs | 10 ++ .../MultiTenantConnectionStringResolver.cs | 43 +++++++++ .../NullTenantConnectionStringStore.cs | 13 +++ .../Abp/MultiTenancy/AbpMultiTenancyModule.cs | 4 + src/Volo.Abp.TestBase/AbpIntegratedTest.cs | 4 +- .../Data/DefaultConnectionStringResolver.cs | 2 +- ...ltiTenantConnectionStringResolver_Tests.cs | 95 +++++++++++++++++++ 7 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 src/Volo.Abp.MultiTenancy/Volo/Abp/Data/MultiTenancy/ITenantConnectionStringStore.cs create mode 100644 src/Volo.Abp.MultiTenancy/Volo/Abp/Data/MultiTenancy/MultiTenantConnectionStringResolver.cs create mode 100644 src/Volo.Abp.MultiTenancy/Volo/Abp/Data/MultiTenancy/NullTenantConnectionStringStore.cs create mode 100644 test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/Data/MultiTenancy/MultiTenantConnectionStringResolver_Tests.cs diff --git a/src/Volo.Abp.MultiTenancy/Volo/Abp/Data/MultiTenancy/ITenantConnectionStringStore.cs b/src/Volo.Abp.MultiTenancy/Volo/Abp/Data/MultiTenancy/ITenantConnectionStringStore.cs new file mode 100644 index 0000000000..c9a6359562 --- /dev/null +++ b/src/Volo.Abp.MultiTenancy/Volo/Abp/Data/MultiTenancy/ITenantConnectionStringStore.cs @@ -0,0 +1,10 @@ +using JetBrains.Annotations; + +namespace Volo.Abp.Data.MultiTenancy +{ + public interface ITenantConnectionStringStore + { + [CanBeNull] + string GetConnectionStringOrNull([NotNull] string tenantId, [CanBeNull] string databaseName); + } +} \ No newline at end of file diff --git a/src/Volo.Abp.MultiTenancy/Volo/Abp/Data/MultiTenancy/MultiTenantConnectionStringResolver.cs b/src/Volo.Abp.MultiTenancy/Volo/Abp/Data/MultiTenancy/MultiTenantConnectionStringResolver.cs new file mode 100644 index 0000000000..eb8eb21f85 --- /dev/null +++ b/src/Volo.Abp.MultiTenancy/Volo/Abp/Data/MultiTenancy/MultiTenantConnectionStringResolver.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.Options; +using Volo.Abp.MultiTenancy; +using Volo.DependencyInjection; + +namespace Volo.Abp.Data.MultiTenancy +{ + //TODO: It would be better to use composition over inheritance on connection string resolve progress! + //TODO: Create a replace service registration convention, instead of custom registration in AbpMultiTenancyModule? + + [DisableConventionalRegistration] + public class MultiTenantConnectionStringResolver : DefaultConnectionStringResolver + { + private readonly IMultiTenancyManager _multiTenancyManager; + private readonly ITenantConnectionStringStore _tenantConnectionStringStore; + + public MultiTenantConnectionStringResolver( + IOptions options, + IMultiTenancyManager multiTenancyManager, + ITenantConnectionStringStore tenantConnectionStringStore) + : base(options) + { + _multiTenancyManager = multiTenancyManager; + _tenantConnectionStringStore = tenantConnectionStringStore; + } + + public override string Resolve(string databaseName = null) + { + var tenant = _multiTenancyManager.CurrentTenant; + if (tenant == null) + { + return base.Resolve(databaseName); + } + + var connectionString = _tenantConnectionStringStore.GetConnectionStringOrNull(tenant.Id, databaseName); + if (connectionString == null) + { + return base.Resolve(databaseName); + } + + return connectionString; + } + } +} diff --git a/src/Volo.Abp.MultiTenancy/Volo/Abp/Data/MultiTenancy/NullTenantConnectionStringStore.cs b/src/Volo.Abp.MultiTenancy/Volo/Abp/Data/MultiTenancy/NullTenantConnectionStringStore.cs new file mode 100644 index 0000000000..a53f6c0b48 --- /dev/null +++ b/src/Volo.Abp.MultiTenancy/Volo/Abp/Data/MultiTenancy/NullTenantConnectionStringStore.cs @@ -0,0 +1,13 @@ +using Volo.DependencyInjection; + +namespace Volo.Abp.Data.MultiTenancy +{ + public sealed class NullTenantConnectionStringStore : ITenantConnectionStringStore, ISingletonDependency + { + public string GetConnectionStringOrNull(string tenantId, string databaseName) + { + //No tenant specific connection string by default + return null; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/AbpMultiTenancyModule.cs b/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/AbpMultiTenancyModule.cs index d92f07bcf6..a84833c8ad 100644 --- a/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/AbpMultiTenancyModule.cs +++ b/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/AbpMultiTenancyModule.cs @@ -1,4 +1,7 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Volo.Abp.Data; +using Volo.Abp.Data.MultiTenancy; using Volo.Abp.Modularity; namespace Volo.Abp.MultiTenancy @@ -7,6 +10,7 @@ namespace Volo.Abp.MultiTenancy { public override void ConfigureServices(IServiceCollection services) { + services.Replace(ServiceDescriptor.Transient()); services.AddAssemblyOf(); } } diff --git a/src/Volo.Abp.TestBase/AbpIntegratedTest.cs b/src/Volo.Abp.TestBase/AbpIntegratedTest.cs index b1c1e22502..3721ae1ba7 100644 --- a/src/Volo.Abp.TestBase/AbpIntegratedTest.cs +++ b/src/Volo.Abp.TestBase/AbpIntegratedTest.cs @@ -17,7 +17,7 @@ namespace Volo.Abp.TestBase { var services = CreateServiceCollection(); - BeforeAppApplication(services); + BeforeAddApplication(services); Application = services.AddApplication(); @@ -33,7 +33,7 @@ namespace Volo.Abp.TestBase return new ServiceCollection(); } - protected virtual void BeforeAppApplication(IServiceCollection services) + protected virtual void BeforeAddApplication(IServiceCollection services) { } diff --git a/src/Volo.Abp/Volo/Abp/Data/DefaultConnectionStringResolver.cs b/src/Volo.Abp/Volo/Abp/Data/DefaultConnectionStringResolver.cs index 9e94496f94..d4545352cc 100644 --- a/src/Volo.Abp/Volo/Abp/Data/DefaultConnectionStringResolver.cs +++ b/src/Volo.Abp/Volo/Abp/Data/DefaultConnectionStringResolver.cs @@ -14,7 +14,7 @@ namespace Volo.Abp.Data _options = options.Value; } - public string Resolve(string databaseName = null) + public virtual string Resolve(string databaseName = null) { //TODO: Override by tenant conn string //TODO: Override by tenant module specific conn string diff --git a/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/Data/MultiTenancy/MultiTenantConnectionStringResolver_Tests.cs b/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/Data/MultiTenancy/MultiTenantConnectionStringResolver_Tests.cs new file mode 100644 index 0000000000..ab7646695f --- /dev/null +++ b/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/Data/MultiTenancy/MultiTenantConnectionStringResolver_Tests.cs @@ -0,0 +1,95 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using Shouldly; +using Volo.Abp.MultiTenancy; +using Xunit; + +namespace Volo.Abp.Data.MultiTenancy +{ + public class MultiTenantConnectionStringResolver_Tests : MultiTenancyTestBase + { + private readonly IMultiTenancyManager _multiTenancyManager; + private readonly IConnectionStringResolver _connectionResolver; + + public MultiTenantConnectionStringResolver_Tests() + { + _multiTenancyManager = ServiceProvider.GetRequiredService(); + + _connectionResolver = ServiceProvider.GetRequiredService(); + _connectionResolver.ShouldBeOfType(); + } + + protected override void BeforeAddApplication(IServiceCollection services) + { + services.Configure(options => + { + options.ConnectionStrings.Default = "default-value"; + options.ConnectionStrings["db1"] = "db1-default-value"; + options.ConnectionStrings["tenant1#Default"] = "tenant1-default-value"; + options.ConnectionStrings["tenant1#db1"] = "tenant1-db1-value"; + }); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + services.Replace(ServiceDescriptor.Transient()); + } + + [Fact] + public void All_Tests() + { + //No tenant in current context + _connectionResolver.Resolve().ShouldBe("default-value"); + _connectionResolver.Resolve("db1").ShouldBe("db1-default-value"); + + //Overrided connection strings for tenant1 + using (_multiTenancyManager.ChangeTenant(new TenantInfo("tenant1"))) + { + _connectionResolver.Resolve().ShouldBe("tenant1-default-value"); + _connectionResolver.Resolve("db1").ShouldBe("tenant1-db1-value"); + } + + //No tenant in current context + _connectionResolver.Resolve().ShouldBe("default-value"); + _connectionResolver.Resolve("db1").ShouldBe("db1-default-value"); + + //Undefined connection strings for tenant2 + using (_multiTenancyManager.ChangeTenant(new TenantInfo("tenant2"))) + { + _connectionResolver.Resolve().ShouldBe("default-value"); + _connectionResolver.Resolve("db1").ShouldBe("db1-default-value"); + } + } + + public class MyTenantConnectionStringStore : ITenantConnectionStringStore + { + private readonly IOptions _options; + + public MyTenantConnectionStringStore(IOptions options) + { + _options = options; + } + + public string GetConnectionStringOrNull(string tenantId, string databaseName) + { + if (databaseName != null) + { + if (_options.Value.ConnectionStrings.ContainsKey(tenantId + "#" + databaseName)) + { + return _options.Value.ConnectionStrings[tenantId + "#" + databaseName]; + } + } + else + { + if (_options.Value.ConnectionStrings.ContainsKey(tenantId + "#Default")) + { + return _options.Value.ConnectionStrings[tenantId + "#Default"]; + } + } + + return null; + } + } + } +}