diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/AsyncDisposeFunc.cs b/framework/src/Volo.Abp.Core/Volo/Abp/AsyncDisposeFunc.cs
new file mode 100644
index 0000000000..76e6699d23
--- /dev/null
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/AsyncDisposeFunc.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+
+namespace Volo.Abp;
+
+///
+/// This class can be used to provide an action when
+/// DisposeAsync method is called.
+///
+public class AsyncDisposeFunc : IAsyncDisposable
+{
+ private readonly Func _func;
+
+ ///
+ /// Creates a new object.
+ ///
+ /// func to be executed when this object is DisposeAsync.
+ public AsyncDisposeFunc([NotNull] Func func)
+ {
+ Check.NotNull(func, nameof(func));
+
+ _func = func;
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ await _func();
+ }
+}
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/NullAsyncDisposable.cs b/framework/src/Volo.Abp.Core/Volo/Abp/NullAsyncDisposable.cs
new file mode 100644
index 0000000000..1307e8764c
--- /dev/null
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/NullAsyncDisposable.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Volo.Abp;
+
+public sealed class NullAsyncDisposable : IAsyncDisposable
+{
+ public static NullAsyncDisposable Instance { get; } = new NullAsyncDisposable();
+
+ private NullAsyncDisposable()
+ {
+
+ }
+
+ public ValueTask DisposeAsync()
+ {
+ return default;
+ }
+}
diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs
index 10c43bf7b2..68cf94d334 100644
--- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs
+++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs
@@ -20,8 +20,8 @@ public class AbpFeatureManagementDomainModule : AbpModule
options.Providers.Add();
options.Providers.Add();
- //TODO: Should be moved to the Tenant Management module
- options.Providers.Add();
+ //TODO: Should be moved to the Tenant Management module
+ options.Providers.Add();
options.ProviderPolicies[TenantFeatureValueProvider.ProviderName] = "AbpTenantManagement.Tenants.ManageFeatures";
});
diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/DefaultValueFeatureManagementProvider.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/DefaultValueFeatureManagementProvider.cs
index 1cfa7012b5..809dddab2b 100644
--- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/DefaultValueFeatureManagementProvider.cs
+++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/DefaultValueFeatureManagementProvider.cs
@@ -1,4 +1,5 @@
-using System.Threading.Tasks;
+using System;
+using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
@@ -13,6 +14,11 @@ public class DefaultValueFeatureManagementProvider : IFeatureManagementProvider,
return providerName == Name;
}
+ public Task HandleContextAsync(string providerName, string providerKey)
+ {
+ return Task.FromResult(NullAsyncDisposable.Instance);
+ }
+
public virtual Task GetOrNullAsync(FeatureDefinition feature, string providerKey)
{
return Task.FromResult(feature.DefaultValue);
diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementProvider.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementProvider.cs
index 61c2347290..7998a98e60 100644
--- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementProvider.cs
+++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementProvider.cs
@@ -1,4 +1,5 @@
-using System.Threading.Tasks;
+using System;
+using System.Threading.Tasks;
using Volo.Abp.Features;
namespace Volo.Abp.FeatureManagement;
@@ -19,6 +20,11 @@ public abstract class FeatureManagementProvider : IFeatureManagementProvider
return providerName == Name;
}
+ public virtual Task HandleContextAsync(string providerName, string providerKey)
+ {
+ return Task.FromResult(NullAsyncDisposable.Instance);
+ }
+
public virtual async Task GetOrNullAsync(FeatureDefinition feature, string providerKey)
{
return await Store.GetOrNullAsync(feature.Name, Name, await NormalizeProviderKeyAsync(providerKey));
diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManager.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManager.cs
index 9114cc4117..06c293219b 100644
--- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManager.cs
+++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManager.cs
@@ -132,7 +132,7 @@ public class FeatureManager : IFeatureManager, ISingletonDependency
var feature = FeatureDefinitionManager.Get(name);
- if (feature?.ValueType?.Validator.IsValid(value) == false)
+ if (feature.ValueType?.Validator.IsValid(value) == false)
{
throw new FeatureValueInvalidException(feature.DisplayName.Localize(StringLocalizerFactory));
}
@@ -149,11 +149,14 @@ public class FeatureManager : IFeatureManager, ISingletonDependency
if (providers.Count > 1 && !forceToSet && value != null)
{
- var fallbackValue = await GetOrNullInternalAsync(name, providers[1].Name, null);
- if (fallbackValue.Value == value)
+ await using (await providers[0].HandleContextAsync(providerName, providerKey))
{
- //Clear the value if it's same as it's fallback value
- value = null;
+ var fallbackValue = await GetOrNullInternalAsync(name, providers[1].Name, null);
+ if (fallbackValue.Value == value)
+ {
+ //Clear the value if it's same as it's fallback value
+ value = null;
+ }
}
}
diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureManagementProvider.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureManagementProvider.cs
index 05cf79d45d..6da43abdd7 100644
--- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureManagementProvider.cs
+++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureManagementProvider.cs
@@ -1,4 +1,5 @@
-using System.Threading.Tasks;
+using System;
+using System.Threading.Tasks;
using JetBrains.Annotations;
using Volo.Abp.Features;
@@ -11,6 +12,9 @@ public interface IFeatureManagementProvider
//TODO: Other better method name.
bool Compatible(string providerName);
+ //TODO: Other better method name.
+ Task HandleContextAsync(string providerName, string providerKey);
+
Task GetOrNullAsync([NotNull] FeatureDefinition feature, [CanBeNull] string providerKey);
Task SetAsync([NotNull] FeatureDefinition feature, [NotNull] string value, [CanBeNull] string providerKey);
diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/TenantFeatureManagementProvider.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/TenantFeatureManagementProvider.cs
index 42e54bc1cc..7a6c8d5819 100644
--- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/TenantFeatureManagementProvider.cs
+++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/TenantFeatureManagementProvider.cs
@@ -1,4 +1,5 @@
-using System.Threading.Tasks;
+using System;
+using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
using Volo.Abp.MultiTenancy;
@@ -19,6 +20,24 @@ public class TenantFeatureManagementProvider : FeatureManagementProvider, ITrans
CurrentTenant = currentTenant;
}
+ public override Task HandleContextAsync(string providerName, string providerKey)
+ {
+ if (providerName == Name && !providerKey.IsNullOrWhiteSpace())
+ {
+ if (Guid.TryParse(providerKey, out var tenantId))
+ {
+ var disposable = CurrentTenant.Change(tenantId);
+ return Task.FromResult(new AsyncDisposeFunc(() =>
+ {
+ disposable.Dispose();
+ return Task.CompletedTask;
+ }));
+ }
+ }
+
+ return base.HandleContextAsync(providerName, providerKey);
+ }
+
protected override Task NormalizeProviderKeyAsync(string providerKey)
{
if (providerKey != null)
diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.Domain.Tests/Volo/Abp/FeatureManagement/FeatureManager_Tests.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.Domain.Tests/Volo/Abp/FeatureManagement/FeatureManager_Tests.cs
index c58a24e952..0344a67440 100644
--- a/modules/feature-management/test/Volo.Abp.FeatureManagement.Domain.Tests/Volo/Abp/FeatureManagement/FeatureManager_Tests.cs
+++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.Domain.Tests/Volo/Abp/FeatureManagement/FeatureManager_Tests.cs
@@ -12,12 +12,14 @@ public class FeatureManager_Tests : FeatureManagementDomainTestBase
private readonly IFeatureManager _featureManager;
private readonly ICurrentTenant _currentTenant;
private readonly IFeatureChecker _featureChecker;
+ private readonly IFeatureValueRepository _featureValueRepository;
public FeatureManager_Tests()
{
_featureManager = GetRequiredService();
_featureChecker = GetRequiredService();
_currentTenant = GetRequiredService();
+ _featureValueRepository = GetRequiredService();
}
[Fact]
@@ -153,4 +155,44 @@ public class FeatureManager_Tests : FeatureManagementDomainTestBase
x.Provider.Name == EditionFeatureValueProvider.ProviderName);
}
+ [Fact]
+ public async Task Test_HandleContextAsync()
+ {
+ var featureValue = await _featureValueRepository.FindAsync(
+ TestFeatureDefinitionProvider.EmailSupport,
+ TenantFeatureValueProvider.ProviderName,
+ TestEditionIds.TenantId.ToString()
+ );
+
+ featureValue.ShouldNotBeNull();
+ featureValue.Value.ShouldBe(false.ToString().ToLower());
+
+
+ featureValue = await _featureValueRepository.FindAsync(
+ TestFeatureDefinitionProvider.EmailSupport,
+ NextTenantFeatureManagementProvider.ProviderName,
+ TestEditionIds.TenantId.ToString()
+ );
+
+ featureValue.ShouldNotBeNull();
+ featureValue.Value.ShouldBe(true.ToString().ToLower());
+
+ await _featureManager.SetAsync(TestFeatureDefinitionProvider.EmailSupport, true.ToString().ToLower(),
+ TenantFeatureValueProvider.ProviderName, TestEditionIds.TenantId.ToString());
+
+ featureValue = await _featureValueRepository.FindAsync(
+ TestFeatureDefinitionProvider.EmailSupport,
+ TenantFeatureValueProvider.ProviderName,
+ TestEditionIds.TenantId.ToString()
+ );
+ featureValue.ShouldBeNull();
+
+ featureValue = await _featureValueRepository.FindAsync(
+ TestFeatureDefinitionProvider.EmailSupport,
+ NextTenantFeatureManagementProvider.ProviderName,
+ TestEditionIds.TenantId.ToString()
+ );
+ featureValue.ShouldNotBeNull();
+ featureValue.Value.ShouldBe(true.ToString().ToLower());
+ }
}
diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementTestBaseModule.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementTestBaseModule.cs
index 9e14de0b8c..76df8f2fba 100644
--- a/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementTestBaseModule.cs
+++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementTestBaseModule.cs
@@ -1,4 +1,5 @@
-using Microsoft.Extensions.DependencyInjection;
+using System.Collections.Generic;
+using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Authorization;
using Volo.Abp.Autofac;
using Volo.Abp.Features;
@@ -29,8 +30,10 @@ public class FeatureManagementTestBaseModule : AbpModule
{
context.Services.Configure(options =>
{
- //TODO: Any value can pass. After completing the permission unit test, look at it again.
- options.ProviderPolicies[EditionFeatureValueProvider.ProviderName] = EditionFeatureValueProvider.ProviderName;
+ options.Providers.InsertBefore(typeof(TenantFeatureManagementProvider), typeof(NextTenantFeatureManagementProvider));
+
+ //TODO: Any value can pass. After completing the permission unit test, look at it again.
+ options.ProviderPolicies[EditionFeatureValueProvider.ProviderName] = EditionFeatureValueProvider.ProviderName;
});
}
diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementTestDataBuilder.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementTestDataBuilder.cs
index d19a784f92..1685264389 100644
--- a/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementTestDataBuilder.cs
+++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/FeatureManagementTestDataBuilder.cs
@@ -23,6 +23,28 @@ public class FeatureManagementTestDataBuilder : ITransientDependency
public async Task BuildAsync()
{
+ // Tenant EmailSupport
+ await _featureValueRepository.InsertAsync(
+ new FeatureValue(
+ _guidGenerator.Create(),
+ TestFeatureDefinitionProvider.EmailSupport,
+ false.ToString().ToLowerInvariant(),
+ TenantFeatureValueProvider.ProviderName,
+ TestEditionIds.TenantId.ToString()
+ )
+ );
+
+ // NextTenant EmailSupport
+ await _featureValueRepository.InsertAsync(
+ new FeatureValue(
+ _guidGenerator.Create(),
+ TestFeatureDefinitionProvider.EmailSupport,
+ true.ToString().ToLowerInvariant(),
+ NextTenantFeatureManagementProvider.ProviderName,
+ TestEditionIds.TenantId.ToString()
+ )
+ );
+
#region "Regular" edition features
//SocialLogins
diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/NextTenantFeatureManagementProvider.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/NextTenantFeatureManagementProvider.cs
new file mode 100644
index 0000000000..acb0510521
--- /dev/null
+++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/NextTenantFeatureManagementProvider.cs
@@ -0,0 +1,32 @@
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.MultiTenancy;
+
+namespace Volo.Abp.FeatureManagement;
+
+public class NextTenantFeatureManagementProvider : FeatureManagementProvider, ITransientDependency
+{
+ public static string ProviderName => "TENANT";
+
+ public override string Name => ProviderName;
+
+ protected ICurrentTenant CurrentTenant { get; }
+
+ public NextTenantFeatureManagementProvider(
+ IFeatureManagementStore store,
+ ICurrentTenant currentTenant)
+ : base(store)
+ {
+ CurrentTenant = currentTenant;
+ }
+
+ protected override Task NormalizeProviderKeyAsync(string providerKey)
+ {
+ if (providerKey != null)
+ {
+ return Task.FromResult(providerKey);
+ }
+
+ return Task.FromResult(CurrentTenant.Id?.ToString());
+ }
+}
diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/TestEditionIds.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/TestEditionIds.cs
index ff8ce1f91f..545dbfe742 100644
--- a/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/TestEditionIds.cs
+++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/TestEditionIds.cs
@@ -8,10 +8,13 @@ public static class TestEditionIds
public static Guid Enterprise { get; }
public static Guid Ultimate { get; }
+ public static Guid TenantId { get; }
+
static TestEditionIds()
{
Regular = Guid.Parse("532599ab-c0c0-4345-a04a-e322867b6e15");
Enterprise = Guid.Parse("27e50758-1feb-436a-be4f-cae8519e0cb2");
Ultimate = Guid.Parse("6ea78c22-be32-497e-aaba-a2332c564c5e");
+ TenantId = Guid.Parse("8899799a-9edf-44ac-9121-310d25ef4fa1");
}
}