From 731c4b1154706c8316fce46bbed70fd01548eb09 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 10 Mar 2022 18:59:45 +0800 Subject: [PATCH] Add `HandleContextAsync` method to the `IFeatureManagementProvider`. --- .../Volo/Abp/AsyncDisposeFunc.cs | 30 +++++++++++++ .../Volo/Abp/NullAsyncDisposable.cs | 19 +++++++++ .../AbpFeatureManagementDomainModule.cs | 4 +- .../DefaultValueFeatureManagementProvider.cs | 8 +++- .../FeatureManagementProvider.cs | 8 +++- .../Abp/FeatureManagement/FeatureManager.cs | 13 +++--- .../IFeatureManagementProvider.cs | 6 ++- .../TenantFeatureManagementProvider.cs | 21 +++++++++- .../FeatureManagement/FeatureManager_Tests.cs | 42 +++++++++++++++++++ .../FeatureManagementTestBaseModule.cs | 9 ++-- .../FeatureManagementTestDataBuilder.cs | 22 ++++++++++ .../NextTenantFeatureManagementProvider.cs | 32 ++++++++++++++ .../Abp/FeatureManagement/TestEditionIds.cs | 3 ++ 13 files changed, 203 insertions(+), 14 deletions(-) create mode 100644 framework/src/Volo.Abp.Core/Volo/Abp/AsyncDisposeFunc.cs create mode 100644 framework/src/Volo.Abp.Core/Volo/Abp/NullAsyncDisposable.cs create mode 100644 modules/feature-management/test/Volo.Abp.FeatureManagement.TestBase/Volo/Abp/FeatureManagement/NextTenantFeatureManagementProvider.cs 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"); } }