Browse Source

Revised multitenancy resolve and store system.

pull/81/head
Halil İbrahim Kalkan 9 years ago
parent
commit
5837a3ef45
  1. 21
      src/AbpDesk/AbpDesk.ConsoleDemo/appsettings.json
  2. 2
      src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/CookieTenantResolver.cs
  3. 2
      src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/HeaderTenantResolver.cs
  4. 8
      src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/HttpTenantResolverBase.cs
  5. 2
      src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/QueryStringTenantResolver.cs
  6. 2
      src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/RouteTenantResolver.cs
  7. 13
      src/Volo.Abp.MultiTenancy/Volo/Abp/Data/MultiTenancy/ITenantConnectionStringStore.cs
  8. 11
      src/Volo.Abp.MultiTenancy/Volo/Abp/Data/MultiTenancy/MultiTenantConnectionStringResolver.cs
  9. 19
      src/Volo.Abp.MultiTenancy/Volo/Abp/Data/MultiTenancy/NullTenantConnectionStringStore.cs
  10. 2
      src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/AsyncLocalTenantScopeProvider.cs
  11. 28
      src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ConfigurationStore/ConfigurationTenantStore.cs
  12. 12
      src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ConfigurationStore/ConfigurationTenantStoreOptions.cs
  13. 8
      src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/IMultiTenancyManager.cs
  14. 4
      src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ITenantResolveContext.cs
  15. 5
      src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ITenantScopeProvider.cs
  16. 14
      src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ITenantStore.cs
  17. 90
      src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenancyManager.cs
  18. 22
      src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantInfo.cs
  19. 28
      src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantInformation.cs
  20. 8
      src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantResolveContext.cs
  21. 4
      src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantScope.cs
  22. 1
      src/Volo.Abp/Volo/Abp/AbpKernelModule.cs
  23. 3
      src/Volo.Abp/Volo/Abp/Data/ConnectionStrings.cs
  24. 1
      src/Volo.Abp/Volo/Abp/Domain/Repositories/IRepository.cs
  25. 1
      src/Volo.Abp/project.json
  26. 2
      test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/App/AppModule.cs
  27. 40
      test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/MultiTenancy/AspNetCoreMultiTenancy_Tests.cs
  28. 52
      test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/Data/MultiTenancy/MultiTenantConnectionStringResolver_Tests.cs
  29. 49
      test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/MultiTenantManager_Tests.cs

21
src/AbpDesk/AbpDesk.ConsoleDemo/appsettings.json

@ -2,5 +2,24 @@
"ConnectionStrings": {
"Default": "Server=localhost;Database=AbpDesk;Trusted_Connection=True;",
"AbpDeskMongoBlog": "mongodb://127.0.0.1:27017|AbpDeskBlog"
}
},
//Example tenant configuration, not used!
"Tenants": [
{
"Id": "2D8BF07D-50F4-4A70-805E-C7618F008043",
"Name": "Acme",
"ConnectionStrings": {
"Default": "",
"AbpDeskMongoBlog": ""
}
},
{
"Id": "33A01BA1-4106-41DA-AF34-28028FB9BD1D",
"Name": "Vlsft",
"ConnectionStrings": {
"Default": "",
"AbpDeskMongoBlog": ""
}
}
]
}

2
src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/CookieTenantResolver.cs

@ -5,7 +5,7 @@ namespace Volo.Abp.AspNetCore.MultiTenancy
{
public class CookieTenantResolver : HttpTenantResolverBase
{
protected override string GetTenantIdFromHttpContextOrNull(ITenantResolveContext context, HttpContext httpContext)
protected override string GetTenantIdOrNameFromHttpContextOrNull(ITenantResolveContext context, HttpContext httpContext)
{
return httpContext.Request.Cookies[context.GetAspNetCoreMultiTenancyOptions().TenantIdKey];
}

2
src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/HeaderTenantResolver.cs

@ -7,7 +7,7 @@ namespace Volo.Abp.AspNetCore.MultiTenancy
{
public class HeaderTenantResolver : HttpTenantResolverBase
{
protected override string GetTenantIdFromHttpContextOrNull(ITenantResolveContext context, HttpContext httpContext)
protected override string GetTenantIdOrNameFromHttpContextOrNull(ITenantResolveContext context, HttpContext httpContext)
{
//TODO: Get first one if provided multiple values and write a log
if (httpContext.Request.Headers.IsNullOrEmpty())

8
src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/HttpTenantResolverBase.cs

@ -19,13 +19,13 @@ namespace Volo.Abp.AspNetCore.MultiTenancy
private void ResolveFromHttpContext(ITenantResolveContext context, HttpContext httpContext)
{
var tenantId = GetTenantIdFromHttpContextOrNull(context, httpContext);
if (!tenantId.IsNullOrEmpty())
var tenantIdOrName = GetTenantIdOrNameFromHttpContextOrNull(context, httpContext);
if (!tenantIdOrName.IsNullOrEmpty())
{
context.Tenant = new TenantInfo(tenantId);
context.TenantIdOrName = tenantIdOrName;
}
}
protected abstract string GetTenantIdFromHttpContextOrNull(ITenantResolveContext context,HttpContext httpContext);
protected abstract string GetTenantIdOrNameFromHttpContextOrNull(ITenantResolveContext context,HttpContext httpContext);
}
}

2
src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/QueryStringTenantResolver.cs

@ -5,7 +5,7 @@ namespace Volo.Abp.AspNetCore.MultiTenancy
{
public class QueryStringTenantResolver : HttpTenantResolverBase
{
protected override string GetTenantIdFromHttpContextOrNull(ITenantResolveContext context, HttpContext httpContext)
protected override string GetTenantIdOrNameFromHttpContextOrNull(ITenantResolveContext context, HttpContext httpContext)
{
if (!httpContext.Request.QueryString.HasValue)
{

2
src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/RouteTenantResolver.cs

@ -7,7 +7,7 @@ namespace Volo.Abp.AspNetCore.MultiTenancy
{
public class RouteTenantResolver : HttpTenantResolverBase
{
protected override string GetTenantIdFromHttpContextOrNull(ITenantResolveContext context, HttpContext httpContext)
protected override string GetTenantIdOrNameFromHttpContextOrNull(ITenantResolveContext context, HttpContext httpContext)
{
var tenantId = httpContext.GetRouteValue(context.GetAspNetCoreMultiTenancyOptions().TenantIdKey);
if (tenantId == null)

13
src/Volo.Abp.MultiTenancy/Volo/Abp/Data/MultiTenancy/ITenantConnectionStringStore.cs

@ -1,13 +0,0 @@
using JetBrains.Annotations;
namespace Volo.Abp.Data.MultiTenancy
{
public interface ITenantConnectionStringStore
{
[CanBeNull]
string GetDefaultConnectionStringOrNull([NotNull] string tenantId);
[CanBeNull]
string GetConnectionStringOrNull([NotNull] string tenantId, [NotNull] string connStringName);
}
}

11
src/Volo.Abp.MultiTenancy/Volo/Abp/Data/MultiTenancy/MultiTenantConnectionStringResolver.cs

@ -9,16 +9,13 @@ namespace Volo.Abp.Data.MultiTenancy
public class MultiTenantConnectionStringResolver : DefaultConnectionStringResolver
{
private readonly IMultiTenancyManager _multiTenancyManager;
private readonly ITenantConnectionStringStore _tenantConnectionStringStore;
public MultiTenantConnectionStringResolver(
IOptionsSnapshot<DbConnectionOptions> options,
IMultiTenancyManager multiTenancyManager,
ITenantConnectionStringStore tenantConnectionStringStore)
IMultiTenancyManager multiTenancyManager)
: base(options)
{
_multiTenancyManager = multiTenancyManager;
_tenantConnectionStringStore = tenantConnectionStringStore;
}
public override string Resolve(string connectionStringName = null)
@ -34,12 +31,12 @@ namespace Volo.Abp.Data.MultiTenancy
//Requesting default connection string
if (connectionStringName == null)
{
return _tenantConnectionStringStore.GetDefaultConnectionStringOrNull(tenant.Id) ??
return tenant.ConnectionStrings.Default ??
Options.ConnectionStrings.Default;
}
//Requesting specific connection string
var connString = _tenantConnectionStringStore.GetConnectionStringOrNull(tenant.Id, connectionStringName);
var connString = tenant.ConnectionStrings.GetOrDefault(connectionStringName);
if (connString != null)
{
return connString;
@ -56,7 +53,7 @@ namespace Volo.Abp.Data.MultiTenancy
return connStringInOptions;
}
return _tenantConnectionStringStore.GetDefaultConnectionStringOrNull(tenant.Id) ??
return tenant.ConnectionStrings.Default ??
Options.ConnectionStrings.Default;
}
}

19
src/Volo.Abp.MultiTenancy/Volo/Abp/Data/MultiTenancy/NullTenantConnectionStringStore.cs

@ -1,19 +0,0 @@
using Volo.DependencyInjection;
namespace Volo.Abp.Data.MultiTenancy
{
public sealed class NullTenantConnectionStringStore : ITenantConnectionStringStore, ISingletonDependency
{
public string GetDefaultConnectionStringOrNull(string tenantId)
{
//No tenant specific connection string by default
return null;
}
public string GetConnectionStringOrNull(string tenantId, string connStringName)
{
//No tenant specific connection string by default
return null;
}
}
}

2
src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/AsyncLocalTenantScopeProvider.cs

@ -19,7 +19,7 @@ namespace Volo.Abp.MultiTenancy
_currentScope = new AsyncLocal<TenantScope>();
}
public IDisposable EnterScope(TenantInfo tenant)
public IDisposable EnterScope(TenantInformation tenant)
{
var parentScope = CurrentScope;
CurrentScope = new TenantScope(tenant);

28
src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ConfigurationStore/ConfigurationTenantStore.cs

@ -0,0 +1,28 @@
using System;
using System.Linq;
using Microsoft.Extensions.Options;
using Volo.DependencyInjection;
namespace Volo.Abp.MultiTenancy.ConfigurationStore
{
[Dependency(TryRegister = true)]
public class ConfigurationTenantStore : ITenantStore, ITransientDependency
{
private readonly ConfigurationTenantStoreOptions _options;
public ConfigurationTenantStore(IOptionsSnapshot<ConfigurationTenantStoreOptions> options)
{
_options = options.Value;
}
public TenantInformation Find(string name)
{
return _options.Tenants.FirstOrDefault(t => t.Name == name);
}
public TenantInformation Find(Guid id)
{
return _options.Tenants.FirstOrDefault(t => t.Id == id);
}
}
}

12
src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ConfigurationStore/ConfigurationTenantStoreOptions.cs

@ -0,0 +1,12 @@
namespace Volo.Abp.MultiTenancy.ConfigurationStore
{
public class ConfigurationTenantStoreOptions
{
public TenantInformation[] Tenants { get; set; }
public ConfigurationTenantStoreOptions()
{
Tenants = new TenantInformation[0];
}
}
}

8
src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/IMultiTenancyManager.cs

@ -6,8 +6,12 @@ namespace Volo.Abp.MultiTenancy
public interface IMultiTenancyManager
{
[CanBeNull]
TenantInfo CurrentTenant { get; }
TenantInformation CurrentTenant { get; }
IDisposable ChangeTenant([CanBeNull] TenantInfo tenant);
IDisposable ChangeTenant(Guid? tenantId);
IDisposable ChangeTenant([CanBeNull] string name);
IDisposable ChangeToHost();
}
}

4
src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ITenantResolveContext.cs

@ -6,8 +6,8 @@ namespace Volo.Abp.MultiTenancy
public interface ITenantResolveContext : IServiceProviderAccessor
{
[CanBeNull]
TenantInfo Tenant { get; set; }
string TenantIdOrName { get; set; }
bool? Handled { get; set; }
bool Handled { get; set; }
}
}

5
src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ITenantScopeProvider.cs

@ -5,9 +5,12 @@ namespace Volo.Abp.MultiTenancy
{
public interface ITenantScopeProvider
{
/// <summary>
/// Can be null for host.
/// </summary>
[CanBeNull]
TenantScope CurrentScope { get; }
IDisposable EnterScope([CanBeNull] TenantInfo tenant);
IDisposable EnterScope([CanBeNull] TenantInformation tenant);
}
}

14
src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ITenantStore.cs

@ -0,0 +1,14 @@
using System;
using JetBrains.Annotations;
namespace Volo.Abp.MultiTenancy
{
public interface ITenantStore
{
[CanBeNull]
TenantInformation Find(string name);
[CanBeNull]
TenantInformation Find(Guid id);
}
}

90
src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenancyManager.cs

@ -1,6 +1,8 @@
using System;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.DependencyInjection;
@ -8,23 +10,66 @@ namespace Volo.Abp.MultiTenancy
{
public class MultiTenancyManager : IMultiTenancyManager, ITransientDependency
{
public TenantInfo CurrentTenant => GetCurrentTenant();
public TenantInformation CurrentTenant => GetCurrentTenant();
private readonly IServiceProvider _serviceProvider;
private readonly ITenantScopeProvider _tenantScopeProvider;
private readonly ITenantStore _tenantStore;
private readonly MultiTenancyOptions _options;
private readonly ILogger<MultiTenancyManager> _logger;
public MultiTenancyManager(
IServiceProvider serviceProvider,
ITenantScopeProvider tenantScopeProvider,
IOptions<MultiTenancyOptions> options)
IOptions<MultiTenancyOptions> options,
ITenantStore tenantStore,
ILogger<MultiTenancyManager> logger)
{
_serviceProvider = serviceProvider;
_tenantScopeProvider = tenantScopeProvider;
_tenantStore = tenantStore;
_logger = logger;
_options = options.Value;
}
protected virtual TenantInfo GetCurrentTenant()
public IDisposable ChangeTenant(Guid? tenantId)
{
if (tenantId == null)
{
return ChangeToHost();
}
var tenant = _tenantStore.Find(tenantId.Value);
if (tenant == null)
{
throw new AbpException("There is no tenant with given tenant id: " + tenantId.Value);
}
return _tenantScopeProvider.EnterScope(tenant);
}
public IDisposable ChangeTenant(string name)
{
if (name == null)
{
return ChangeToHost();
}
var tenant = _tenantStore.Find(name);
if (tenant == null)
{
throw new AbpException("There is no tenant with given tenant name: " + name);
}
return _tenantScopeProvider.EnterScope(tenant);
}
public IDisposable ChangeToHost()
{
return _tenantScopeProvider.EnterScope(null);
}
protected virtual TenantInformation GetCurrentTenant()
{
if (_tenantScopeProvider.CurrentScope != null)
{
@ -34,8 +79,10 @@ namespace Volo.Abp.MultiTenancy
return GetCurrentTenantFromResolvers();
}
protected virtual TenantInfo GetCurrentTenantFromResolvers()
protected virtual TenantInformation GetCurrentTenantFromResolvers()
{
//TODO: Can be optimized by some caching mechanism?
if (!_options.TenantResolvers.Any())
{
return null;
@ -49,23 +96,42 @@ namespace Volo.Abp.MultiTenancy
{
tenantResolver.Resolve(context);
//TODO: Validate TenantId by a TenantStore (TenantId can be TenancyName, so also normalize it by tenant store)
if (context.IsHandled())
if (context.ResolvedTenantOrHost())
{
break;
if (context.TenantIdOrName == null)
{
//Resolved host!
return null;
}
var tenant = GetValidatedTenantOrNull(context.TenantIdOrName);
if (tenant != null)
{
return tenant;
}
_logger.LogWarning($"Resolved tenancy name '{context.TenantIdOrName}' by '{tenantResolver.GetType().FullName}' but could not find in current tenant store.");
context.TenantIdOrName = null;
}
context.Handled = null;
context.Handled = false;
}
return context.Tenant;
//Could not find a tenant
return null;
}
}
public IDisposable ChangeTenant(TenantInfo tenant)
[CanBeNull]
private TenantInformation GetValidatedTenantOrNull([NotNull] string tenantIdOrName)
{
return _tenantScopeProvider.EnterScope(tenant);
Guid tenantId;
if (Guid.TryParse(tenantIdOrName, out tenantId))
{
return _tenantStore.Find(tenantId);
}
return _tenantStore.Find(tenantIdOrName);
}
}
}

22
src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantInfo.cs

@ -1,22 +0,0 @@
using JetBrains.Annotations;
namespace Volo.Abp.MultiTenancy
{
public class TenantInfo
{
[NotNull]
public string Id { get; }
private TenantInfo()
{
}
public TenantInfo([NotNull] string id)
{
Check.NotNull(id, nameof(id));
Id = id;
}
}
}

28
src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantInformation.cs

@ -0,0 +1,28 @@
using System;
using JetBrains.Annotations;
using Volo.Abp.Data;
namespace Volo.Abp.MultiTenancy
{
public class TenantInformation
{
public Guid Id { get; protected set; }
public string Name { get; protected set; }
public ConnectionStrings ConnectionStrings { get; protected set; }
protected TenantInformation()
{
}
public TenantInformation(Guid id, [NotNull] string name)
{
Id = id;
Name = name;
ConnectionStrings = new ConnectionStrings();
}
}
}

8
src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantResolveContext.cs

@ -6,13 +6,13 @@ namespace Volo.Abp.MultiTenancy
{
public IServiceProvider ServiceProvider { get; }
public TenantInfo Tenant { get; set; }
public string TenantIdOrName { get; set; }
public bool? Handled { get; set; }
public bool Handled { get; set; }
internal bool IsHandled()
internal bool ResolvedTenantOrHost()
{
return Handled == true || (Handled == null && Tenant != null);
return Handled || TenantIdOrName != null;
}
public TenantResolveContext(IServiceProvider serviceProvider)

4
src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantScope.cs

@ -9,9 +9,9 @@ namespace Volo.Abp.MultiTenancy
/// Not null value for a tenant.
/// </summary>
[CanBeNull]
public TenantInfo Tenant { get; }
public TenantInformation Tenant { get; }
public TenantScope([CanBeNull] TenantInfo tenant)
public TenantScope([CanBeNull] TenantInformation tenant)
{
Tenant = tenant;
}

1
src/Volo.Abp/Volo/Abp/AbpKernelModule.cs

@ -8,6 +8,7 @@ namespace Volo.Abp
public override void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.AddLogging();
services.AddAssemblyOf<AbpKernelModule>();
}

3
src/Volo.Abp/Volo/Abp/Data/ConnectionStrings.cs

@ -1,4 +1,5 @@
using System.Collections.Generic;
using Volo.ExtensionMethods.Collections.Generic;
namespace Volo.Abp.Data
{
@ -8,7 +9,7 @@ namespace Volo.Abp.Data
public string Default
{
get { return this[DefaultConnectionStringName]; }
get { return this.GetOrDefault(DefaultConnectionStringName); }
set { this[DefaultConnectionStringName] = value; }
}
}

1
src/Volo.Abp/Volo/Abp/Domain/Repositories/IRepository.cs

@ -67,7 +67,6 @@ namespace Volo.Abp.Domain.Repositories
[NotNull]
TEntity Insert([NotNull] TEntity entity, bool autoSave = false);
//TODO: Consider to add a new overload that takes cancellationToken as second argument?
/// <summary>
/// Inserts a new entity.
/// </summary>

1
src/Volo.Abp/project.json

@ -5,6 +5,7 @@
"NETStandard.Library": "1.6.1",
"Microsoft.Extensions.DependencyInjection": "1.1.0",
"Microsoft.Extensions.Options": "1.1.0",
"Microsoft.Extensions.Logging": "1.1.0",
"System.Collections.Immutable": "1.3.0",
"System.Runtime.Loader": "4.3.0",
"System.Linq.Queryable": "4.3.0",

2
test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/App/AppModule.cs

@ -28,7 +28,7 @@ namespace Volo.Abp.AspNetCore.App
var dictionary = new Dictionary<string, string>
{
["TenantId"] = manager.CurrentTenant?.Id ?? ""
["TenantId"] = manager.CurrentTenant == null ? "" : manager.CurrentTenant.Id.ToString()
};
var result = jsonSerializer.Serialize(dictionary);

40
test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/MultiTenancy/AspNetCoreMultiTenancy_Tests.cs

@ -1,16 +1,23 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using Shouldly;
using Volo.Abp.AspNetCore.App;
using Volo.Abp.MultiTenancy;
using Volo.Abp.MultiTenancy.ConfigurationStore;
using Xunit;
namespace Volo.Abp.AspNetCore.MultiTenancy
{
public class AspNetCoreMultiTenancy_Tests : AppTestBase
{
private readonly Guid _testTenantId = Guid.NewGuid();
private readonly string _testTenantName = "acme";
private readonly AspNetCoreMultiTenancyOptions _options;
public AspNetCoreMultiTenancy_Tests()
@ -18,6 +25,20 @@ namespace Volo.Abp.AspNetCore.MultiTenancy
_options = ServiceProvider.GetRequiredService<IOptions<AspNetCoreMultiTenancyOptions>>().Value;
}
protected override IWebHostBuilder CreateWebHostBuilder()
{
return base.CreateWebHostBuilder().ConfigureServices(services =>
{
services.Configure<ConfigurationTenantStoreOptions>(options =>
{
options.Tenants = new[]
{
new TenantInformation(_testTenantId, _testTenantName)
};
});
});
}
[Fact]
public async Task Should_Use_Host_If_Tenant_Is_Not_Specified()
{
@ -28,32 +49,27 @@ namespace Volo.Abp.AspNetCore.MultiTenancy
[Fact]
public async Task Should_Use_QueryString_Tenant_Id_If_Specified()
{
const string testTenantId = "42";
var result = await GetResponseAsObjectAsync<Dictionary<string, string>>($"http://abp.io?{_options.TenantIdKey}={testTenantId}");
result["TenantId"].ShouldBe(testTenantId);
var result = await GetResponseAsObjectAsync<Dictionary<string, string>>($"http://abp.io?{_options.TenantIdKey}={_testTenantName}");
result["TenantId"].ShouldBe(_testTenantId.ToString());
}
[Fact]
public async Task Should_Use_Header_Tenant_Id_If_Specified()
{
const string testTenantId = "42";
Client.DefaultRequestHeaders.Add(_options.TenantIdKey, testTenantId);
Client.DefaultRequestHeaders.Add(_options.TenantIdKey, _testTenantId.ToString());
var result = await GetResponseAsObjectAsync<Dictionary<string, string>>("http://abp.io");
result["TenantId"].ShouldBe(testTenantId);
result["TenantId"].ShouldBe(_testTenantId.ToString());
}
[Fact]
public async Task Should_Use_Cookie_Tenant_Id_If_Specified()
{
const string testTenantId = "42";
Client.DefaultRequestHeaders.Add("Cookie", new CookieHeaderValue(_options.TenantIdKey, testTenantId).ToString());
Client.DefaultRequestHeaders.Add("Cookie", new CookieHeaderValue(_options.TenantIdKey, _testTenantId.ToString()).ToString());
var result = await GetResponseAsObjectAsync<Dictionary<string, string>>("http://abp.io");
result["TenantId"].ShouldBe(testTenantId);
result["TenantId"].ShouldBe(_testTenantId.ToString());
}
}
}

52
test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/Data/MultiTenancy/MultiTenantConnectionStringResolver_Tests.cs

@ -1,8 +1,13 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Shouldly;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.MultiTenancy.ConfigurationStore;
using Volo.ExtensionMethods.Collections.Generic;
using Xunit;
@ -27,14 +32,23 @@ namespace Volo.Abp.Data.MultiTenancy
{
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<ITenantConnectionStringStore, MyTenantConnectionStringStore>());
services.Configure<ConfigurationTenantStoreOptions>(options =>
{
options.Tenants = new[]
{
new TenantInformation(Guid.NewGuid(), "tenant1")
{
ConnectionStrings =
{
{ ConnectionStrings.DefaultConnectionStringName, "tenant1-default-value" },
{ "db1", "tenant1-db1-value" }
}
},
new TenantInformation(Guid.NewGuid(), "tenant2")
};
});
}
[Fact]
@ -45,7 +59,7 @@ namespace Volo.Abp.Data.MultiTenancy
_connectionResolver.Resolve("db1").ShouldBe("db1-default-value");
//Overrided connection strings for tenant1
using (_multiTenancyManager.ChangeTenant(new TenantInfo("tenant1")))
using (_multiTenancyManager.ChangeTenant("tenant1"))
{
_connectionResolver.Resolve().ShouldBe("tenant1-default-value");
_connectionResolver.Resolve("db1").ShouldBe("tenant1-db1-value");
@ -56,31 +70,11 @@ namespace Volo.Abp.Data.MultiTenancy
_connectionResolver.Resolve("db1").ShouldBe("db1-default-value");
//Undefined connection strings for tenant2
using (_multiTenancyManager.ChangeTenant(new TenantInfo("tenant2")))
using (_multiTenancyManager.ChangeTenant("tenant2"))
{
_connectionResolver.Resolve().ShouldBe("default-value");
_connectionResolver.Resolve("db1").ShouldBe("db1-default-value");
}
}
public class MyTenantConnectionStringStore : ITenantConnectionStringStore
{
private readonly IOptions<DbConnectionOptions> _options;
public MyTenantConnectionStringStore(IOptions<DbConnectionOptions> options)
{
_options = options;
}
public string GetConnectionStringOrNull(string tenantId, string connStringName)
{
return _options.Value.ConnectionStrings.GetOrDefault(tenantId + "#" + connStringName);
}
public string GetDefaultConnectionStringOrNull(string tenantId)
{
return _options.Value.ConnectionStrings.GetOrDefault(tenantId + "#Default");
}
}
}
}

49
test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/MultiTenantManager_Tests.cs

@ -1,5 +1,8 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.Data;
using Volo.Abp.MultiTenancy.ConfigurationStore;
using Xunit;
namespace Volo.Abp.MultiTenancy
@ -8,9 +11,9 @@ namespace Volo.Abp.MultiTenancy
{
private readonly IMultiTenancyManager _multiTenancyManager;
private readonly TenantInfo _tenantA = new TenantInfo("A");
private readonly TenantInfo _tenantB = new TenantInfo("B");
private TenantInfo _tenantToBeResolved;
private readonly string _tenantA = "A";
private readonly string _tenantB = "B";
private string _tenantToBeResolved;
public MultiTenantManager_Tests()
{
@ -25,6 +28,18 @@ namespace Volo.Abp.MultiTenancy
_multiTenancyManager.CurrentTenant.ShouldBeNull();
}
protected override void BeforeAddApplication(IServiceCollection services)
{
services.Configure<ConfigurationTenantStoreOptions>(options =>
{
options.Tenants = new[]
{
new TenantInformation(Guid.NewGuid(), _tenantA),
new TenantInformation(Guid.NewGuid(), _tenantB)
};
});
}
protected override void AfterAddApplication(IServiceCollection services)
{
services.Configure<MultiTenancyOptions>(options =>
@ -33,7 +48,7 @@ namespace Volo.Abp.MultiTenancy
{
if (_tenantToBeResolved == _tenantA)
{
context.Tenant = _tenantA;
context.TenantIdOrName = _tenantA;
}
}));
@ -41,7 +56,7 @@ namespace Volo.Abp.MultiTenancy
{
if (_tenantToBeResolved == _tenantB)
{
context.Tenant = _tenantB;
context.TenantIdOrName = _tenantB;
}
}));
});
@ -56,10 +71,10 @@ namespace Volo.Abp.MultiTenancy
//Assert
_multiTenancyManager.CurrentTenant.ShouldBe(_tenantA);
Assert.NotNull(_multiTenancyManager.CurrentTenant);
_multiTenancyManager.CurrentTenant.Name.ShouldBe(_tenantA);
}
[Fact]
public void Should_Get_Current_Tenant_From_Multiple_Resolvers()
{
@ -69,7 +84,8 @@ namespace Volo.Abp.MultiTenancy
//Assert
_multiTenancyManager.CurrentTenant.ShouldBe(_tenantB);
Assert.NotNull(_multiTenancyManager.CurrentTenant);
_multiTenancyManager.CurrentTenant.Name.ShouldBe(_tenantB);
}
[Fact]
@ -79,21 +95,26 @@ namespace Volo.Abp.MultiTenancy
_tenantToBeResolved = _tenantB;
_multiTenancyManager.CurrentTenant.ShouldBe(_tenantB);
Assert.NotNull(_multiTenancyManager.CurrentTenant);
_multiTenancyManager.CurrentTenant.Name.ShouldBe(_tenantB);
using (_multiTenancyManager.ChangeTenant(_tenantA))
{
_multiTenancyManager.CurrentTenant.ShouldBe(_tenantA);
Assert.NotNull(_multiTenancyManager.CurrentTenant);
_multiTenancyManager.CurrentTenant.Name.ShouldBe(_tenantA);
using (_multiTenancyManager.ChangeTenant(_tenantB))
{
_multiTenancyManager.CurrentTenant.ShouldBe(_tenantB);
Assert.NotNull(_multiTenancyManager.CurrentTenant);
_multiTenancyManager.CurrentTenant.Name.ShouldBe(_tenantB);
}
_multiTenancyManager.CurrentTenant.ShouldBe(_tenantA);
Assert.NotNull(_multiTenancyManager.CurrentTenant);
_multiTenancyManager.CurrentTenant.Name.ShouldBe(_tenantA);
}
_multiTenancyManager.CurrentTenant.ShouldBe(_tenantB);
Assert.NotNull(_multiTenancyManager.CurrentTenant);
_multiTenancyManager.CurrentTenant.Name.ShouldBe(_tenantB);
}
}
}

Loading…
Cancel
Save