diff --git a/docs/Multi-Tenancy.md b/docs/Multi-Tenancy.md
index 6561b50bc3..ee807a3821 100644
--- a/docs/Multi-Tenancy.md
+++ b/docs/Multi-Tenancy.md
@@ -86,6 +86,10 @@ namespace MyCompany.MyProject
}
````
+#### Change Current Tenant
+
+TODO: ...
+
### Volo.Abp.MultiTenancy Package
Volo.Abp.MultiTenancy is the actual package that makes your application multi-tenant. Install it into your project using PMC:
@@ -310,6 +314,16 @@ namespace MyCompany.MyProject
}
````
+#### Multi-Tenancy Middleware
+
+Volo.Abp.AspNetCore.MultiTenancy package includes the multi-tenancy middleware...
+
+````C#
+app.UseMultiTenancy();
+````
+
+TODO:...
+
#### Determining Current Tenant From Web Request
Volo.Abp.AspNetCore.MultiTenancy package adds following tenant resolvers to determine current tenant from current web request (ordered by priority). These resolvers are added and work out of the box:
diff --git a/src/AbpDesk/AbpDesk.Web.Mvc/AbpDesk.Web.Mvc.csproj b/src/AbpDesk/AbpDesk.Web.Mvc/AbpDesk.Web.Mvc.csproj
index 94469818fd..b98d7ce2f5 100644
--- a/src/AbpDesk/AbpDesk.Web.Mvc/AbpDesk.Web.Mvc.csproj
+++ b/src/AbpDesk/AbpDesk.Web.Mvc/AbpDesk.Web.Mvc.csproj
@@ -27,7 +27,6 @@
-
diff --git a/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs b/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs
index 7cfbce5bb5..d34ce6b15d 100644
--- a/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs
+++ b/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs
@@ -27,8 +27,6 @@ using Volo.Abp.Modularity;
using Volo.Abp.Ui.Navigation;
using Volo.Abp.VirtualFileSystem;
using Volo.Abp.IdentityServer.Jwt;
-using Volo.Abp.MultiTenancy;
-using Volo.Abp.MultiTenancy.ConfigurationStore;
namespace AbpDesk.Web.Mvc
{
@@ -68,22 +66,6 @@ namespace AbpDesk.Web.Mvc
AbpDeskDbConfigurer.Configure(services, configuration);
- //TODO: Getting from appsettings.json didn't worked somehow.
- services.Configure(options =>
- {
- options.Tenants = new[]
- {
- new TenantInfo(
- Guid.Parse("446a5211-3d72-4339-9adc-845151f8ada0"),
- "acme"
- ),
- new TenantInfo(
- Guid.Parse("25388015-ef1c-4355-9c18-f6b6ddbaf89d"),
- "volosoft"
- )
- };
- });
-
services.Configure(options =>
{
options.MenuContributors.Add(new MainMenuContributor());
@@ -154,6 +136,8 @@ namespace AbpDesk.Web.Mvc
app.UseStaticFiles();
app.UseVirtualFiles();
+ app.UseMultiTenancy();
+
app.UseIdentityServer();
app.UseAuthentication();
diff --git a/src/Volo.Abp.AspNetCore.MultiTenancy/Microsoft/AspNetCore/Builder/AbpAspNetCoreMultiTenancyApplicationBuilderExtensions.cs b/src/Volo.Abp.AspNetCore.MultiTenancy/Microsoft/AspNetCore/Builder/AbpAspNetCoreMultiTenancyApplicationBuilderExtensions.cs
new file mode 100644
index 0000000000..afc05f9233
--- /dev/null
+++ b/src/Volo.Abp.AspNetCore.MultiTenancy/Microsoft/AspNetCore/Builder/AbpAspNetCoreMultiTenancyApplicationBuilderExtensions.cs
@@ -0,0 +1,13 @@
+using Volo.Abp.AspNetCore.MultiTenancy;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ public static class AbpAspNetCoreMultiTenancyApplicationBuilderExtensions
+ {
+ public static IApplicationBuilder UseMultiTenancy(this IApplicationBuilder app)
+ {
+ return app
+ .UseMiddleware();
+ }
+ }
+}
diff --git a/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs b/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs
index 84497abceb..05e955f803 100644
--- a/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs
+++ b/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs
@@ -4,7 +4,10 @@ using Volo.Abp.MultiTenancy;
namespace Volo.Abp.AspNetCore.MultiTenancy
{
- [DependsOn(typeof(AbpMultiTenancyAbstractionsModule), typeof(AbpAspNetCoreModule))]
+ [DependsOn(
+ typeof(AbpMultiTenancyAbstractionsModule),
+ typeof(AbpAspNetCoreModule)
+ )]
public class AbpAspNetCoreMultiTenancyModule : AbpModule
{
public override void ConfigureServices(IServiceCollection services)
diff --git a/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs b/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs
new file mode 100644
index 0000000000..6cbc541003
--- /dev/null
+++ b/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Volo.Abp.MultiTenancy;
+
+namespace Volo.Abp.AspNetCore.MultiTenancy
+{
+ public class MultiTenancyMiddleware
+ {
+ private readonly RequestDelegate _next;
+
+ private readonly ITenantResolver _tenantResolver;
+ private readonly ITenantStore _tenantStore;
+ private readonly ICurrentTenantIdAccessor _currentTenantIdAccessor;
+
+ public MultiTenancyMiddleware(
+ RequestDelegate next,
+ ITenantResolver tenantResolver,
+ ITenantStore tenantStore,
+ ICurrentTenantIdAccessor currentTenantIdAccessor)
+ {
+ _next = next;
+ _tenantResolver = tenantResolver;
+ _tenantStore = tenantStore;
+ _currentTenantIdAccessor = currentTenantIdAccessor;
+ }
+
+ public async Task Invoke(HttpContext httpContext)
+ {
+ //TODO: Try-catch and return "unknown tenant" if found tenant is not in the store..?
+
+ var tenantIdOrName = _tenantResolver.ResolveTenantIdOrName();
+ if (tenantIdOrName == null)
+ {
+ await _next(httpContext);
+ return;
+ }
+
+ var tenant = await FindTenantAsync(tenantIdOrName);
+ if (tenant == null)
+ {
+ throw new AbpException("There is no tenant with given tenant id or name: " + tenantIdOrName);
+ }
+
+ using (SetCurrent(tenant))
+ {
+ await _next(httpContext);
+ }
+ }
+
+ private async Task FindTenantAsync(string tenantIdOrName)
+ {
+ if (Guid.TryParse(tenantIdOrName, out var parsedTenantId))
+ {
+ return await _tenantStore.FindAsync(parsedTenantId);
+ }
+ else
+ {
+ return await _tenantStore.FindAsync(tenantIdOrName);
+ }
+ }
+
+ private IDisposable SetCurrent(TenantInfo tenant)
+ {
+ var parentScope = _currentTenantIdAccessor.Current;
+ _currentTenantIdAccessor.Current = new TenantIdWrapper(tenant?.Id);
+ return new DisposeAction(() =>
+ {
+ _currentTenantIdAccessor.Current = parentScope;
+ });
+ }
+ }
+}
diff --git a/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/Views/Shared/Components/AbpMenu/Default.cshtml b/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/Views/Shared/Components/AbpMenu/Default.cshtml
index c3826aca41..23d3244516 100644
--- a/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/Views/Shared/Components/AbpMenu/Default.cshtml
+++ b/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/Views/Shared/Components/AbpMenu/Default.cshtml
@@ -37,7 +37,7 @@
@if (CurrentTenant.IsAvailable)
{
- @CurrentTenant.Name?.ToString()
+ @CurrentTenant.Id?.ToString()
}
else
{
diff --git a/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Builder/AbpAspNetCoreMvcApplicationBuilderExtensions.cs b/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Builder/AbpAspNetCoreMvcApplicationBuilderExtensions.cs
index 91d0df29bb..1bb1095bd5 100644
--- a/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Builder/AbpAspNetCoreMvcApplicationBuilderExtensions.cs
+++ b/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Builder/AbpAspNetCoreMvcApplicationBuilderExtensions.cs
@@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Builder
.UseMiddleware();
}
- public static IApplicationBuilder UseAbpExceptionHandling(this IApplicationBuilder app)
+ public static IApplicationBuilder UseAbpExceptionHandling(this IApplicationBuilder app) //TODO: Should this go to
{
//Prevent multiple add
if (app.Properties.ContainsKey("_AbpExceptionHandlingMiddleware_Added")) //TODO: Constant
diff --git a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AsyncLocalCurrentTenantIdAccessor.cs b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AsyncLocalCurrentTenantIdAccessor.cs
new file mode 100644
index 0000000000..be2175df9a
--- /dev/null
+++ b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AsyncLocalCurrentTenantIdAccessor.cs
@@ -0,0 +1,21 @@
+using System.Threading;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.MultiTenancy
+{
+ public class AsyncLocalCurrentTenantIdAccessor : ICurrentTenantIdAccessor, ISingletonDependency
+ {
+ public TenantIdWrapper Current
+ {
+ get => _currentScope.Value;
+ set => _currentScope.Value = value;
+ }
+
+ private readonly AsyncLocal _currentScope;
+
+ public AsyncLocalCurrentTenantIdAccessor()
+ {
+ _currentScope = new AsyncLocal();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ConfigurationStore/ConfigurationTenantStore.cs b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ConfigurationStore/ConfigurationTenantStore.cs
index c9ef920a79..fe1bb03534 100644
--- a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ConfigurationStore/ConfigurationTenantStore.cs
+++ b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ConfigurationStore/ConfigurationTenantStore.cs
@@ -1,5 +1,6 @@
using System;
using System.Linq;
+using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
@@ -16,14 +17,14 @@ namespace Volo.Abp.MultiTenancy.ConfigurationStore
_options = options.Value;
}
- public TenantInfo Find(string name)
+ public Task FindAsync(string name)
{
- return _options.Tenants.FirstOrDefault(t => t.Name == name);
+ return Task.FromResult(_options.Tenants.FirstOrDefault(t => t.Name == name));
}
- public TenantInfo Find(Guid id)
+ public Task FindAsync(Guid id)
{
- return _options.Tenants.FirstOrDefault(t => t.Id == id);
+ return Task.FromResult(_options.Tenants.FirstOrDefault(t => t.Id == id));
}
}
}
\ No newline at end of file
diff --git a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/CurrentTenant.cs b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/CurrentTenant.cs
index 1456e3a12b..1cee215eea 100644
--- a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/CurrentTenant.cs
+++ b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/CurrentTenant.cs
@@ -1,114 +1,39 @@
using System;
-using JetBrains.Annotations;
-using Microsoft.Extensions.Logging;
-using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.MultiTenancy
{
public class CurrentTenant : ICurrentTenant, ITransientDependency
{
- public bool IsAvailable => Id.HasValue;
+ public virtual bool IsAvailable => Id.HasValue;
- public Guid? Id => GetCurrentTenant()?.Id;
+ public virtual Guid? Id => _currentTenantIdAccessor.Current?.TenantId;
- public string Name => GetCurrentTenant()?.Name;
+ private readonly ICurrentTenantIdAccessor _currentTenantIdAccessor;
- public ConnectionStrings ConnectionStrings => GetCurrentTenant()?.ConnectionStrings;
-
- private readonly TenantScopeProvider _tenantScopeProvider;
- private readonly ITenantStore _tenantStore;
- private readonly ILogger _logger;
- private readonly ITenantResolver _tenantResolver;
-
- public CurrentTenant(
- TenantScopeProvider tenantScopeProvider,
- ITenantStore tenantStore,
- ILogger logger,
- ITenantResolver tenantResolver)
+ public CurrentTenant(ICurrentTenantIdAccessor currentTenantIdAccessor)
{
- _tenantScopeProvider = tenantScopeProvider;
- _tenantStore = tenantStore;
- _logger = logger;
- _tenantResolver = tenantResolver;
+ _currentTenantIdAccessor = currentTenantIdAccessor;
}
public IDisposable Change(Guid? id)
{
- if (id == null)
- {
- return _tenantScopeProvider.EnterScope(null);
- }
-
- var tenant = _tenantStore.Find(id.Value);
- if (tenant == null)
- {
- throw new AbpException("There is no tenant with given tenant id: " + id.Value);
- }
-
- return _tenantScopeProvider.EnterScope(tenant);
- }
-
- public IDisposable Change(string name)
- {
- if (name == null)
- {
- return _tenantScopeProvider.EnterScope(null);
- }
-
- var tenant = _tenantStore.Find(name);
- if (tenant == null)
- {
- throw new AbpException("There is no tenant with given tenant name: " + name);
- }
-
- return _tenantScopeProvider.EnterScope(tenant);
+ return SetCurrent(id);
}
- [CanBeNull]
- protected virtual TenantInfo GetCurrentTenant()
+ public IDisposable Clear()
{
- if (_tenantScopeProvider.CurrentScope != null)
- {
- return _tenantScopeProvider.CurrentScope.Tenant;
- }
-
- //TODO: Get from ICurrentUser before resolvers and fail if resolvers find a different tenant!
-
- return ResolveTenant();
+ return Change(null);
}
- [CanBeNull]
- protected virtual TenantInfo ResolveTenant()
+ private IDisposable SetCurrent(Guid? tenantId)
{
- var tenantIdOrName = _tenantResolver.ResolveTenantIdOrName();
- if (tenantIdOrName == null)
+ var parentScope = _currentTenantIdAccessor.Current;
+ _currentTenantIdAccessor.Current = new TenantIdWrapper(tenantId);
+ return new DisposeAction(() =>
{
- return null;
- }
-
- TenantInfo tenant;
-
- //Try to find by id
- if (Guid.TryParse(tenantIdOrName, out var tenantId))
- {
- tenant = _tenantStore.Find(tenantId);
- if (tenant != null)
- {
- return tenant;
- }
- }
-
- //Try to find by name
- tenant = _tenantStore.Find(tenantIdOrName);
- if (tenant != null)
- {
- return tenant;
- }
-
- //Could not found!
- _logger.LogWarning($"Resolved tenancy id or name '{tenantIdOrName}' but could not find in the tenant store.");
- return null;
+ _currentTenantIdAccessor.Current = parentScope;
+ });
}
}
}
diff --git a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ICurrentTenant.cs b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ICurrentTenant.cs
index 0889bc692a..1c5cd0b352 100644
--- a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ICurrentTenant.cs
+++ b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ICurrentTenant.cs
@@ -1,6 +1,5 @@
using System;
using JetBrains.Annotations;
-using Volo.Abp.Data;
namespace Volo.Abp.MultiTenancy
{
@@ -11,16 +10,8 @@ namespace Volo.Abp.MultiTenancy
[CanBeNull]
Guid? Id { get; }
- [CanBeNull]
- string Name { get; }
-
- [CanBeNull]
- ConnectionStrings ConnectionStrings { get; }
-
- [NotNull]
IDisposable Change(Guid? id);
- [NotNull]
- IDisposable Change([CanBeNull] string name);
+ IDisposable Clear();
}
}
diff --git a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ICurrentTenantIdAccessor.cs b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ICurrentTenantIdAccessor.cs
new file mode 100644
index 0000000000..49be15cd10
--- /dev/null
+++ b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ICurrentTenantIdAccessor.cs
@@ -0,0 +1,13 @@
+namespace Volo.Abp.MultiTenancy
+{
+ /* Uses TenantScopeTenantInfoWrapper instead of TenantInfo because being null of Current is different that being null of Current.Tenant.
+ * A null Current indicates that we haven't set it explicitly.
+ * A null Current.Tenant indicates that we have set null tenant value explicitly.
+ * A non-null Current.Tenant indicates that we have set a tenant value explicitly.
+ */
+
+ public interface ICurrentTenantIdAccessor
+ {
+ TenantIdWrapper Current { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ITenantStore.cs b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ITenantStore.cs
index 8ca9b181c0..7c45adde4c 100644
--- a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ITenantStore.cs
+++ b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ITenantStore.cs
@@ -1,14 +1,12 @@
using System;
-using JetBrains.Annotations;
+using System.Threading.Tasks;
namespace Volo.Abp.MultiTenancy
{
public interface ITenantStore
{
- [CanBeNull]
- TenantInfo Find(string name);
+ Task FindAsync(string name);
- [CanBeNull]
- TenantInfo Find(Guid id);
+ Task FindAsync(Guid id);
}
}
\ No newline at end of file
diff --git a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/MultiTenantConnectionStringResolver.cs b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/MultiTenantConnectionStringResolver.cs
index fa82fd6388..aad2150b8a 100644
--- a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/MultiTenantConnectionStringResolver.cs
+++ b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/MultiTenantConnectionStringResolver.cs
@@ -1,7 +1,10 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
+using Volo.Abp.Threading;
namespace Volo.Abp.MultiTenancy
{
@@ -9,52 +12,72 @@ namespace Volo.Abp.MultiTenancy
public class MultiTenantConnectionStringResolver : DefaultConnectionStringResolver
{
private readonly ICurrentTenant _currentTenant;
+ private readonly IServiceProvider _serviceProvider;
public MultiTenantConnectionStringResolver(
IOptionsSnapshot options,
- ICurrentTenant currentTenant)
+ ICurrentTenant currentTenant,
+ IServiceProvider serviceProvider)
: base(options)
{
_currentTenant = currentTenant;
+ _serviceProvider = serviceProvider;
}
public override string Resolve(string connectionStringName = null)
{
- var tenantConnectionStrings = _currentTenant.ConnectionStrings;
-
//No current tenant, fallback to default logic
- if (tenantConnectionStrings == null)
+ if (_currentTenant.Id == null)
{
return base.Resolve(connectionStringName);
}
- //Requesting default connection string
- if (connectionStringName == null)
+ using (var serviceScope = _serviceProvider.CreateScope())
{
- return tenantConnectionStrings.Default ??
- Options.ConnectionStrings.Default;
- }
+ var tenantStore = serviceScope
+ .ServiceProvider
+ .GetRequiredService();
- //Requesting specific connection string
- var connString = tenantConnectionStrings.GetOrDefault(connectionStringName);
- if (connString != null)
- {
- return connString;
- }
+ var tenant = AsyncHelper.RunSync(() => tenantStore.FindAsync(_currentTenant.Id.Value)); //TODO: Can we avoid from RunSync?
- /* Requested a specific connection string, but it's not specified for the tenant.
- * - If it's specified in options, use it.
- * - If not, use tenant's default conn string.
- */
-
- var connStringInOptions = Options.ConnectionStrings.GetOrDefault(connectionStringName);
- if (connStringInOptions != null)
- {
- return connStringInOptions;
- }
+ if (tenant == null)
+ {
+ return base.Resolve(connectionStringName);
+ }
- return tenantConnectionStrings.Default ??
- Options.ConnectionStrings.Default;
+ if (tenant.ConnectionStrings == null)
+ {
+ return base.Resolve(connectionStringName);
+ }
+
+ //Requesting default connection string
+ if (connectionStringName == null)
+ {
+ return tenant.ConnectionStrings.Default ??
+ Options.ConnectionStrings.Default;
+ }
+
+ //Requesting specific connection string
+ var connString = tenant.ConnectionStrings.GetOrDefault(connectionStringName);
+ if (connString != null)
+ {
+ return connString;
+ }
+
+ /* Requested a specific connection string, but it's not specified for the tenant.
+ * - If it's specified in options, use it.
+ * - If not, use tenant's default conn string.
+ */
+
+ var connStringInOptions = Options.ConnectionStrings.GetOrDefault(connectionStringName);
+ if (connStringInOptions != null)
+ {
+ return connStringInOptions;
+ }
+
+ return tenant.ConnectionStrings.Default ??
+ Options.ConnectionStrings.Default;
+ }
}
}
}
diff --git a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantIdWrapper.cs b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantIdWrapper.cs
new file mode 100644
index 0000000000..92f8c641d2
--- /dev/null
+++ b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantIdWrapper.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace Volo.Abp.MultiTenancy
+{
+ public class TenantIdWrapper
+ {
+ ///
+ /// Null indicates the host.
+ /// Not null value for a tenant.
+ ///
+ public Guid? TenantId { get; }
+
+ public TenantIdWrapper(Guid? tenantId)
+ {
+ TenantId = tenantId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantScope.cs b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantScope.cs
deleted file mode 100644
index 7476ac0a1f..0000000000
--- a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantScope.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using JetBrains.Annotations;
-
-namespace Volo.Abp.MultiTenancy
-{
- public class TenantScope
- {
- ///
- /// Null indicates the host.
- /// Not null value for a tenant.
- ///
- [CanBeNull]
- public TenantInfo Tenant { get; }
-
- public TenantScope([CanBeNull] TenantInfo tenant)
- {
- Tenant = tenant;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantScopeProvider.cs b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantScopeProvider.cs
deleted file mode 100644
index 8d3f768854..0000000000
--- a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantScopeProvider.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System;
-using System.Threading;
-using Volo.Abp.DependencyInjection;
-
-namespace Volo.Abp.MultiTenancy
-{
- /* This cass uses TenantScope instead of TenantInfo because being null of CurrentScope is different that being null of CurrentScope.Tenant.
- * A null CurrentScope indicates that we haven't set any scope explicitly (and we can use tenant resolvers to determine the current tenant).
- * A null CurrentScope.Tenant indicates that we have set null tenant value (maybe to switch to host) explicitly.
- * A non-null CurrentScope.Tenant indicates that we have set a tenant value explicitly.
- */
-
- public class TenantScopeProvider : ISingletonDependency
- {
- public TenantScope CurrentScope
- {
- get => _currentScope.Value;
- private set => _currentScope.Value = value;
- }
-
- private readonly AsyncLocal _currentScope;
-
- public TenantScopeProvider()
- {
- _currentScope = new AsyncLocal();
- }
-
- public IDisposable EnterScope(TenantInfo tenant)
- {
- var parentScope = CurrentScope;
- CurrentScope = new TenantScope(tenant);
- return new DisposeAction(() =>
- {
- CurrentScope = parentScope;
- });
- }
- }
-}
\ No newline at end of file
diff --git a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantStoreExtensions.cs b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantStoreExtensions.cs
new file mode 100644
index 0000000000..6ac6c0517b
--- /dev/null
+++ b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantStoreExtensions.cs
@@ -0,0 +1,21 @@
+using System;
+using JetBrains.Annotations;
+using Volo.Abp.Threading;
+
+namespace Volo.Abp.MultiTenancy
+{
+ public static class TenantStoreExtensions
+ {
+ [CanBeNull]
+ public static TenantInfo Find(this ITenantStore tenantStore, string name)
+ {
+ return AsyncHelper.RunSync(() => tenantStore.FindAsync(name));
+ }
+
+ [CanBeNull]
+ public static TenantInfo Find(this ITenantStore tenantStore, Guid id)
+ {
+ return AsyncHelper.RunSync(() => tenantStore.FindAsync(id));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.MultiTenancy.Domain/Volo.Abp.MultiTenancy.Domain.csproj b/src/Volo.Abp.MultiTenancy.Domain/Volo.Abp.MultiTenancy.Domain.csproj
index b969b03419..fa42b9aee9 100644
--- a/src/Volo.Abp.MultiTenancy.Domain/Volo.Abp.MultiTenancy.Domain.csproj
+++ b/src/Volo.Abp.MultiTenancy.Domain/Volo.Abp.MultiTenancy.Domain.csproj
@@ -14,6 +14,7 @@
+
diff --git a/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/AbpMultiTenancyDomainMappingProfile.cs b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/AbpMultiTenancyDomainMappingProfile.cs
new file mode 100644
index 0000000000..9c38155721
--- /dev/null
+++ b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/AbpMultiTenancyDomainMappingProfile.cs
@@ -0,0 +1,27 @@
+using AutoMapper;
+using Volo.Abp.Data;
+
+namespace Volo.Abp.MultiTenancy
+{
+ public class AbpMultiTenancyDomainMappingProfile : Profile
+ {
+ public AbpMultiTenancyDomainMappingProfile()
+ {
+ CreateMap()
+ .ForMember(ti => ti.ConnectionStrings, opts =>
+ {
+ opts.ResolveUsing(tenant =>
+ {
+ var connStrings = new ConnectionStrings();
+
+ foreach (var connectionString in tenant.ConnectionStrings)
+ {
+ connStrings[connectionString.Name] = connectionString.Value;
+ }
+
+ return connStrings;
+ });
+ });
+ }
+ }
+}
diff --git a/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/AbpMultiTenancyDomainModule.cs b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/AbpMultiTenancyDomainModule.cs
index dcb2c5eeb4..1912ef52a4 100644
--- a/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/AbpMultiTenancyDomainModule.cs
+++ b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/AbpMultiTenancyDomainModule.cs
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.AutoMapper;
using Volo.Abp.Data;
using Volo.Abp.Modularity;
@@ -8,10 +9,16 @@ namespace Volo.Abp.MultiTenancy
[DependsOn(typeof(AbpMultiTenancyDomainSharedModule))]
[DependsOn(typeof(AbpDataModule))]
[DependsOn(typeof(AbpDddModule))]
+ [DependsOn(typeof(AbpAutoMapperModule))]
public class AbpMultiTenancyDomainModule : AbpModule
{
public override void ConfigureServices(IServiceCollection services)
{
+ services.Configure(options =>
+ {
+ options.AddProfile(validate: true);
+ });
+
services.AddAssemblyOf();
}
}
diff --git a/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/ITenantRepository.cs b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/ITenantRepository.cs
new file mode 100644
index 0000000000..d8486c8ae6
--- /dev/null
+++ b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/ITenantRepository.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Threading.Tasks;
+using Volo.Abp.Domain.Repositories;
+
+namespace Volo.Abp.MultiTenancy
+{
+ public interface ITenantRepository : IRepository
+ {
+ Task FindByNameIncludeDetailsAsync(string name);
+
+ Task FindWithIncludeDetailsAsync(Guid id);
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/Tenant.cs b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/Tenant.cs
index ffaa37181f..4a572526c5 100644
--- a/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/Tenant.cs
+++ b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/Tenant.cs
@@ -8,9 +8,9 @@ namespace Volo.Abp.MultiTenancy
{
public class Tenant : AggregateRoot
{
- public string Name { get; protected set; }
+ public virtual string Name { get; protected set; }
- public List ConnectionStrings { get; protected set; }
+ public virtual List ConnectionStrings { get; protected set; }
protected Tenant()
{
diff --git a/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/TenantStore.cs b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/TenantStore.cs
new file mode 100644
index 0000000000..45a6001bca
--- /dev/null
+++ b/src/Volo.Abp.MultiTenancy.Domain/Volo/Abp/MultiTenancy/TenantStore.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.ObjectMapping;
+
+namespace Volo.Abp.MultiTenancy
+{
+ public class TenantStore : ITenantStore, ITransientDependency
+ {
+ private readonly ITenantRepository _tenantRepository;
+ private readonly IObjectMapper _objectMapper;
+ private readonly ICurrentTenant _currentTenant;
+
+ public TenantStore(
+ ITenantRepository tenantRepository,
+ IObjectMapper objectMapper,
+ ICurrentTenant currentTenant)
+ {
+ _tenantRepository = tenantRepository;
+ _objectMapper = objectMapper;
+ _currentTenant = currentTenant;
+ }
+
+ public async Task FindAsync(string name)
+ {
+ using (_currentTenant.Clear()) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
+ {
+ var tenant = await _tenantRepository.FindByNameIncludeDetailsAsync(name);
+ if (tenant == null)
+ {
+ return null;
+ }
+
+ return _objectMapper.Map(tenant);
+ }
+ }
+
+ public async Task FindAsync(Guid id)
+ {
+ using (_currentTenant.Clear()) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
+ {
+ var tenant = await _tenantRepository.FindWithIncludeDetailsAsync(id);
+ if (tenant == null)
+ {
+ return null;
+ }
+
+ return _objectMapper.Map(tenant);
+ }
+ }
+ }
+}
diff --git a/src/Volo.Abp.MultiTenancy.EntityFrameworkCore/Volo/Abp/MultiTenancy/EfCoreTenantRepository.cs b/src/Volo.Abp.MultiTenancy.EntityFrameworkCore/Volo/Abp/MultiTenancy/EfCoreTenantRepository.cs
new file mode 100644
index 0000000000..66b6ed0fa3
--- /dev/null
+++ b/src/Volo.Abp.MultiTenancy.EntityFrameworkCore/Volo/Abp/MultiTenancy/EfCoreTenantRepository.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
+using Volo.Abp.EntityFrameworkCore;
+using Volo.Abp.MultiTenancy.EntityFrameworkCore;
+
+namespace Volo.Abp.MultiTenancy
+{
+ public class EfCoreTenantRepository : EfCoreRepository, ITenantRepository
+ {
+ public EfCoreTenantRepository(IDbContextProvider dbContextProvider)
+ : base(dbContextProvider)
+ {
+ }
+
+ public Task FindByNameIncludeDetailsAsync(string name)
+ {
+ return DbSet
+ .Include(t => t.ConnectionStrings) //TODO: Why not creating a virtual Include method in EfCoreRepository and override to add included properties to be available for every query..?
+ .FirstOrDefaultAsync(t => t.Name == name);
+ }
+
+ public Task FindWithIncludeDetailsAsync(Guid id)
+ {
+ return DbSet
+ .Include(t => t.ConnectionStrings) //TODO: Why not creating a virtual Include method in EfCoreRepository and override to add included properties to be available for every query..?
+ .FirstOrDefaultAsync(t => t.Id == id);
+ }
+ }
+}
diff --git a/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo.Abp.AspNetCore.MultiTenancy.Tests.csproj b/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo.Abp.AspNetCore.MultiTenancy.Tests.csproj
index 459df49b3a..5340dc86c9 100644
--- a/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo.Abp.AspNetCore.MultiTenancy.Tests.csproj
+++ b/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo.Abp.AspNetCore.MultiTenancy.Tests.csproj
@@ -13,7 +13,6 @@
-
diff --git a/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/App/AppModule.cs b/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/App/AppModule.cs
index 5656b06a47..235fe34618 100644
--- a/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/App/AppModule.cs
+++ b/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/App/AppModule.cs
@@ -13,8 +13,7 @@ namespace Volo.Abp.AspNetCore.App
{
[DependsOn(
typeof(AbpAspNetCoreMultiTenancyModule),
- typeof(AbpAspNetCoreTestBaseModule),
- typeof(AbpMultiTenancyDomainModule)
+ typeof(AbpAspNetCoreTestBaseModule)
)]
public class AppModule : AbpModule
{
@@ -29,7 +28,9 @@ namespace Volo.Abp.AspNetCore.App
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
-
+
+ app.UseMultiTenancy();
+
app.Run(async (ctx) =>
{
var currentTenant = ctx.RequestServices.GetRequiredService();
diff --git a/test/Volo.Abp.MultiTenancy.Tests/Volo.Abp.MultiTenancy.Tests.csproj b/test/Volo.Abp.MultiTenancy.Tests/Volo.Abp.MultiTenancy.Tests.csproj
index a14cc94a90..a76a44b67d 100644
--- a/test/Volo.Abp.MultiTenancy.Tests/Volo.Abp.MultiTenancy.Tests.csproj
+++ b/test/Volo.Abp.MultiTenancy.Tests/Volo.Abp.MultiTenancy.Tests.csproj
@@ -12,7 +12,7 @@
-
+
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
index d3fdcfc4b5..4ea231cc67 100644
--- 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
@@ -9,15 +9,18 @@ namespace Volo.Abp.Data.MultiTenancy
{
public class MultiTenantConnectionStringResolver_Tests : MultiTenancyTestBase
{
- private readonly ICurrentTenant _currentTenant;
+ private readonly Guid _tenant1Id = Guid.NewGuid();
+ private readonly Guid _tenant2Id = Guid.NewGuid();
+
private readonly IConnectionStringResolver _connectionResolver;
+ private readonly ICurrentTenant _currentTenant;
public MultiTenantConnectionStringResolver_Tests()
{
- _currentTenant = ServiceProvider.GetRequiredService();
-
_connectionResolver = ServiceProvider.GetRequiredService();
_connectionResolver.ShouldBeOfType();
+
+ _currentTenant = ServiceProvider.GetRequiredService();
}
protected override void BeforeAddApplication(IServiceCollection services)
@@ -32,7 +35,7 @@ namespace Volo.Abp.Data.MultiTenancy
{
options.Tenants = new[]
{
- new TenantInfo(Guid.NewGuid(), "tenant1")
+ new TenantInfo(_tenant1Id, "tenant1")
{
ConnectionStrings =
{
@@ -40,7 +43,7 @@ namespace Volo.Abp.Data.MultiTenancy
{"db1", "tenant1-db1-value"}
}
},
- new TenantInfo(Guid.NewGuid(), "tenant2")
+ new TenantInfo(_tenant2Id, "tenant2")
};
});
}
@@ -53,7 +56,7 @@ namespace Volo.Abp.Data.MultiTenancy
_connectionResolver.Resolve("db1").ShouldBe("db1-default-value");
//Overrided connection strings for tenant1
- using (_currentTenant.Change("tenant1"))
+ using (_currentTenant.Change(_tenant1Id))
{
_connectionResolver.Resolve().ShouldBe("tenant1-default-value");
_connectionResolver.Resolve("db1").ShouldBe("tenant1-db1-value");
@@ -64,7 +67,7 @@ namespace Volo.Abp.Data.MultiTenancy
_connectionResolver.Resolve("db1").ShouldBe("db1-default-value");
//Undefined connection strings for tenant2
- using (_currentTenant.Change("tenant2"))
+ using (_currentTenant.Change(_tenant2Id))
{
_connectionResolver.Resolve().ShouldBe("default-value");
_connectionResolver.Resolve("db1").ShouldBe("db1-default-value");
diff --git a/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/CurrentTenant_Tests.cs b/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/CurrentTenant_Tests.cs
index 87fb6040d2..feb4d418fb 100644
--- a/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/CurrentTenant_Tests.cs
+++ b/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/CurrentTenant_Tests.cs
@@ -10,9 +10,8 @@ namespace Volo.Abp.MultiTenancy
{
private readonly ICurrentTenant _currentTenant;
- private readonly string _tenantA = "A";
- private readonly string _tenantB = "B";
- private string _tenantToBeResolved;
+ private readonly Guid _tenantAId = Guid.NewGuid();
+ private readonly Guid _tenantBId = Guid.NewGuid();
public CurrentTenant_Tests()
{
@@ -33,87 +32,36 @@ namespace Volo.Abp.MultiTenancy
{
options.Tenants = new[]
{
- new TenantInfo(Guid.NewGuid(), _tenantA),
- new TenantInfo(Guid.NewGuid(), _tenantB)
+ new TenantInfo(_tenantAId, "A"),
+ new TenantInfo(_tenantAId, "B")
};
});
}
- protected override void AfterAddApplication(IServiceCollection services)
- {
- services.Configure(options =>
- {
- options.TenantResolvers.Add(new ActionTenantResolveContributer(context =>
- {
- if (_tenantToBeResolved == _tenantA)
- {
- context.TenantIdOrName = _tenantA;
- }
- }));
-
- options.TenantResolvers.Add(new ActionTenantResolveContributer(context =>
- {
- if (_tenantToBeResolved == _tenantB)
- {
- context.TenantIdOrName = _tenantB;
- }
- }));
- });
- }
-
- [Fact]
- public void Should_Get_Current_Tenant_From_Single_Resolver()
- {
- //Arrange
-
- _tenantToBeResolved = _tenantA;
-
- //Assert
-
- Assert.NotNull(_currentTenant.Id);
- _currentTenant.Name.ShouldBe(_tenantA);
- }
-
[Fact]
- public void Should_Get_Current_Tenant_From_Multiple_Resolvers()
+ public void Should_Get_Null_If_Not_Set()
{
- //Arrange
-
- _tenantToBeResolved = _tenantB;
-
- //Assert
-
- Assert.NotNull(_currentTenant.Id);
- _currentTenant.Name.ShouldBe(_tenantB);
+ _currentTenant.Id.ShouldBeNull();
}
[Fact]
- public void Should_Get_Changed_Tenant_If_Wanted()
+ public void Should_Get_Changed_Tenant_If()
{
_currentTenant.Id.ShouldBe(null);
- _tenantToBeResolved = _tenantB;
-
- Assert.NotNull(_currentTenant.Id);
- _currentTenant.Name.ShouldBe(_tenantB);
-
- using (_currentTenant.Change(_tenantA))
+ using (_currentTenant.Change(_tenantAId))
{
- Assert.NotNull(_currentTenant.Id);
- _currentTenant.Name.ShouldBe(_tenantA);
+ _currentTenant.Id.ShouldBe(_tenantAId);
- using (_currentTenant.Change(_tenantB))
+ using (_currentTenant.Change(_tenantBId))
{
- Assert.NotNull(_currentTenant.Id);
- _currentTenant.Name.ShouldBe(_tenantB);
+ _currentTenant.Id.ShouldBe(_tenantBId);
}
- Assert.NotNull(_currentTenant.Id);
- _currentTenant.Name.ShouldBe(_tenantA);
+ _currentTenant.Id.ShouldBe(_tenantAId);
}
- Assert.NotNull(_currentTenant.Id);
- _currentTenant.Name.ShouldBe(_tenantB);
+ _currentTenant.Id.ShouldBeNull();
}
}
}
diff --git a/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/MultiTenancyTestModule.cs b/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/MultiTenancyTestModule.cs
index cd1093b6ac..78fc476f07 100644
--- a/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/MultiTenancyTestModule.cs
+++ b/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/MultiTenancyTestModule.cs
@@ -2,7 +2,8 @@
namespace Volo.Abp.MultiTenancy
{
- [DependsOn(typeof(AbpMultiTenancyDomainModule))]
+ //TODO: Renaming this project to Volo.Abp.MultiTenancy.Tests would be better!
+ [DependsOn(typeof(AbpMultiTenancyAbstractionsModule))]
public class MultiTenancyTestModule : AbpModule
{