mirror of https://github.com/abpframework/abp.git
27 changed files with 568 additions and 55 deletions
@ -1,14 +0,0 @@ |
|||||
using Volo.Abp.Settings; |
|
||||
|
|
||||
namespace Volo.Abp.FeatureManagement |
|
||||
{ |
|
||||
public class FeatureManagementSettingDefinitionProvider : SettingDefinitionProvider |
|
||||
{ |
|
||||
public override void Define(ISettingDefinitionContext context) |
|
||||
{ |
|
||||
/* Define module settings here. |
|
||||
* Use names from FeatureManagementSettings class. |
|
||||
*/ |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,11 +0,0 @@ |
|||||
namespace Volo.Abp.FeatureManagement |
|
||||
{ |
|
||||
public static class FeatureManagementSettings |
|
||||
{ |
|
||||
public const string GroupName = "AbpFeatureManagement"; |
|
||||
|
|
||||
/* Add constants for setting names. Example: |
|
||||
* public const string MySettingName = GroupName + ".MySettingName"; |
|
||||
*/ |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,26 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Features; |
||||
|
|
||||
|
namespace Volo.Abp.FeatureManagement |
||||
|
{ |
||||
|
public class DefaultValueFeatureManagementProvider : IFeatureManagementProvider, ISingletonDependency |
||||
|
{ |
||||
|
public string Name => DefaultValueFeatureValueProvider.ProviderName; |
||||
|
|
||||
|
public Task<string> GetOrNullAsync(FeatureDefinition feature, string providerKey) |
||||
|
{ |
||||
|
return Task.FromResult(feature.DefaultValue); |
||||
|
} |
||||
|
|
||||
|
public Task SetAsync(FeatureDefinition feature, string value, string providerKey) |
||||
|
{ |
||||
|
throw new AbpException($"Can not set default value of a feature. It is only possible while defining the feature in a {typeof(IFeatureDefinitionProvider)} implementation."); |
||||
|
} |
||||
|
|
||||
|
public Task ClearAsync(FeatureDefinition feature, string providerKey) |
||||
|
{ |
||||
|
throw new AbpException($"Can not clear default value of a feature. It is only possible while defining the feature in a {typeof(IFeatureDefinitionProvider)} implementation."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp.Features; |
||||
|
|
||||
|
namespace Volo.Abp.FeatureManagement |
||||
|
{ |
||||
|
public static class DefaultValueFeatureManagerExtensions |
||||
|
{ |
||||
|
public static Task<string> GetOrNullDefaultAsync(this IFeatureManager featureManager, [NotNull] string name, bool fallback = true) |
||||
|
{ |
||||
|
return featureManager.GetOrNullAsync(name, DefaultValueFeatureValueProvider.ProviderName, null, fallback); |
||||
|
} |
||||
|
|
||||
|
public static Task<List<FeatureNameValue>> GetAllDefaultAsync(this IFeatureManager featureManager, bool fallback = true) |
||||
|
{ |
||||
|
return featureManager.GetAllAsync(DefaultValueFeatureValueProvider.ProviderName, null, fallback); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using Volo.Abp.Collections; |
||||
|
|
||||
|
namespace Volo.Abp.FeatureManagement |
||||
|
{ |
||||
|
public class FeatureManagementOptions |
||||
|
{ |
||||
|
public ITypeList<IFeatureManagementProvider> Providers { get; } |
||||
|
|
||||
|
public FeatureManagementOptions() |
||||
|
{ |
||||
|
Providers = new TypeList<IFeatureManagementProvider>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Features; |
||||
|
|
||||
|
namespace Volo.Abp.FeatureManagement |
||||
|
{ |
||||
|
public abstract class FeatureManagementProvider : IFeatureManagementProvider |
||||
|
{ |
||||
|
public abstract string Name { get; } |
||||
|
|
||||
|
protected IFeatureManagementStore Store { get; } |
||||
|
|
||||
|
protected FeatureManagementProvider(IFeatureManagementStore store) |
||||
|
{ |
||||
|
Store = store; |
||||
|
} |
||||
|
|
||||
|
public async Task<string> GetOrNullAsync(FeatureDefinition feature, string providerKey) |
||||
|
{ |
||||
|
return await Store.GetOrNullAsync(feature.Name, Name, NormalizeProviderKey(providerKey)); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task SetAsync(FeatureDefinition feature, string value, string providerKey) |
||||
|
{ |
||||
|
await Store.SetAsync(feature.Name, value, Name, NormalizeProviderKey(providerKey)); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task ClearAsync(FeatureDefinition feature, string providerKey) |
||||
|
{ |
||||
|
await Store.DeleteAsync(feature.Name, Name, NormalizeProviderKey(providerKey)); |
||||
|
} |
||||
|
|
||||
|
protected virtual string NormalizeProviderKey(string providerKey) |
||||
|
{ |
||||
|
return providerKey; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,81 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Caching; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Guids; |
||||
|
|
||||
|
namespace Volo.Abp.FeatureManagement |
||||
|
{ |
||||
|
public class FeatureManagementStore : IFeatureManagementStore, ITransientDependency |
||||
|
{ |
||||
|
protected IDistributedCache<FeatureValueCacheItem> Cache { get; } |
||||
|
protected IFeatureValueRepository FeatureValueRepository { get; } |
||||
|
protected IGuidGenerator GuidGenerator { get; } |
||||
|
|
||||
|
public FeatureManagementStore( |
||||
|
IFeatureValueRepository featureValueRepository, |
||||
|
IGuidGenerator guidGenerator, |
||||
|
IDistributedCache<FeatureValueCacheItem> cache) |
||||
|
{ |
||||
|
FeatureValueRepository = featureValueRepository; |
||||
|
GuidGenerator = guidGenerator; |
||||
|
Cache = cache; |
||||
|
} |
||||
|
|
||||
|
public async Task<string> GetOrNullAsync(string name, string providerName, string providerKey) |
||||
|
{ |
||||
|
var cacheItem = await GetCacheItemAsync(name, providerName, providerKey); |
||||
|
return cacheItem.Value; |
||||
|
} |
||||
|
|
||||
|
public async Task SetAsync(string name, string value, string providerName, string providerKey) |
||||
|
{ |
||||
|
var featureValue = await FeatureValueRepository.FindAsync(name, providerName, providerKey); |
||||
|
if (featureValue == null) |
||||
|
{ |
||||
|
featureValue = new FeatureValue(GuidGenerator.Create(), name, value, providerName, providerKey); |
||||
|
await FeatureValueRepository.InsertAsync(featureValue); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
featureValue.Value = value; |
||||
|
await FeatureValueRepository.UpdateAsync(featureValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task DeleteAsync(string name, string providerName, string providerKey) |
||||
|
{ |
||||
|
var featureValue = await FeatureValueRepository.FindAsync(name, providerName, providerKey); |
||||
|
if (featureValue != null) |
||||
|
{ |
||||
|
await FeatureValueRepository.DeleteAsync(featureValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<FeatureValueCacheItem> GetCacheItemAsync(string name, string providerName, string providerKey) |
||||
|
{ |
||||
|
var cacheKey = CalculateCacheKey(name, providerName, providerKey); |
||||
|
var cacheItem = await Cache.GetAsync(cacheKey); |
||||
|
|
||||
|
if (cacheItem != null) |
||||
|
{ |
||||
|
return cacheItem; |
||||
|
} |
||||
|
|
||||
|
var featureValue = await FeatureValueRepository.FindAsync(name, providerName, providerKey); |
||||
|
|
||||
|
cacheItem = new FeatureValueCacheItem(featureValue?.Value); |
||||
|
|
||||
|
await Cache.SetAsync( |
||||
|
cacheKey, |
||||
|
cacheItem |
||||
|
); |
||||
|
|
||||
|
return cacheItem; |
||||
|
} |
||||
|
|
||||
|
protected virtual string CalculateCacheKey(string name, string providerName, string providerKey) |
||||
|
{ |
||||
|
return FeatureValueCacheItem.CalculateCacheKey(name, providerName, providerKey); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,186 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Features; |
||||
|
|
||||
|
namespace Volo.Abp.FeatureManagement |
||||
|
{ |
||||
|
public class FeatureManager : IFeatureManager, ISingletonDependency |
||||
|
{ |
||||
|
protected IFeatureDefinitionManager FeatureDefinitionManager { get; } |
||||
|
protected List<IFeatureManagementProvider> Providers => _lazyProviders.Value; |
||||
|
protected FeatureManagementOptions Options { get; } |
||||
|
|
||||
|
private readonly Lazy<List<IFeatureManagementProvider>> _lazyProviders; |
||||
|
|
||||
|
public FeatureManager( |
||||
|
IOptions<FeatureManagementOptions> options, |
||||
|
IServiceProvider serviceProvider, |
||||
|
IFeatureDefinitionManager featureDefinitionManager) |
||||
|
{ |
||||
|
FeatureDefinitionManager = featureDefinitionManager; |
||||
|
Options = options.Value; |
||||
|
|
||||
|
//TODO: Instead, use IHybridServiceScopeFactory and create a scope..?
|
||||
|
|
||||
|
_lazyProviders = new Lazy<List<IFeatureManagementProvider>>( |
||||
|
() => Options |
||||
|
.Providers |
||||
|
.Select(c => serviceProvider.GetRequiredService(c) as IFeatureManagementProvider) |
||||
|
.ToList(), |
||||
|
true |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public virtual Task<string> GetOrNullAsync( |
||||
|
string name, |
||||
|
string providerName, |
||||
|
string providerKey, |
||||
|
bool fallback = true) |
||||
|
{ |
||||
|
Check.NotNull(name, nameof(name)); |
||||
|
Check.NotNull(providerName, nameof(providerName)); |
||||
|
|
||||
|
return GetOrNullInternalAsync(name, providerName, providerKey, fallback); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<List<FeatureNameValue>> GetAllAsync( |
||||
|
string providerName, |
||||
|
string providerKey, |
||||
|
bool fallback = true) |
||||
|
{ |
||||
|
Check.NotNull(providerName, nameof(providerName)); |
||||
|
|
||||
|
var featureDefinitions = FeatureDefinitionManager.GetAll(); |
||||
|
var providers = Enumerable.Reverse(Providers) |
||||
|
.SkipWhile(c => c.Name != providerName); |
||||
|
|
||||
|
if (!fallback) |
||||
|
{ |
||||
|
providers = providers.TakeWhile(c => c.Name == providerName); |
||||
|
} |
||||
|
|
||||
|
var providerList = providers.Reverse().ToList(); |
||||
|
|
||||
|
if (!providerList.Any()) |
||||
|
{ |
||||
|
return new List<FeatureNameValue>(); |
||||
|
} |
||||
|
|
||||
|
var featureValues = new Dictionary<string, FeatureNameValue>(); |
||||
|
|
||||
|
foreach (var feature in featureDefinitions) |
||||
|
{ |
||||
|
string value = null; |
||||
|
|
||||
|
foreach (var provider in providerList) |
||||
|
{ |
||||
|
var providerValue = await provider.GetOrNullAsync( |
||||
|
feature, |
||||
|
provider.Name == providerName ? providerKey : null |
||||
|
); |
||||
|
|
||||
|
if (providerValue != null) |
||||
|
{ |
||||
|
value = providerValue; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (value != null) |
||||
|
{ |
||||
|
featureValues[feature.Name] = new FeatureNameValue(feature.Name, value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return featureValues.Values.ToList(); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task SetAsync( |
||||
|
string name, |
||||
|
string value, |
||||
|
string providerName, |
||||
|
string providerKey, |
||||
|
bool forceToSet = false) |
||||
|
{ |
||||
|
Check.NotNull(name, nameof(name)); |
||||
|
Check.NotNull(providerName, nameof(providerName)); |
||||
|
|
||||
|
var feature = FeatureDefinitionManager.Get(name); |
||||
|
|
||||
|
var providers = Enumerable |
||||
|
.Reverse(Providers) |
||||
|
.SkipWhile(p => p.Name != providerName) |
||||
|
.ToList(); |
||||
|
|
||||
|
if (!providers.Any()) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (providers.Count > 1 && !forceToSet && value != null) |
||||
|
{ |
||||
|
var fallbackValue = await GetOrNullInternalAsync(name, providers[1].Name, null); |
||||
|
if (fallbackValue == value) |
||||
|
{ |
||||
|
//Clear the value if it's same as it's fallback value
|
||||
|
value = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
providers = providers |
||||
|
.TakeWhile(p => p.Name == providerName) |
||||
|
.ToList(); //Getting list for case of there are more than one provider with same providerName
|
||||
|
|
||||
|
if (value == null) |
||||
|
{ |
||||
|
foreach (var provider in providers) |
||||
|
{ |
||||
|
await provider.ClearAsync(feature, providerKey); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
foreach (var provider in providers) |
||||
|
{ |
||||
|
await provider.SetAsync(feature, value, providerKey); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<string> GetOrNullInternalAsync( |
||||
|
string name, |
||||
|
string providerName, |
||||
|
string providerKey, |
||||
|
bool fallback = true) //TODO: Fallback is not used
|
||||
|
{ |
||||
|
var feature = FeatureDefinitionManager.Get(name); |
||||
|
var providers = Enumerable |
||||
|
.Reverse(Providers); |
||||
|
|
||||
|
if (providerName != null) |
||||
|
{ |
||||
|
providers = providers.SkipWhile(c => c.Name != providerName); |
||||
|
} |
||||
|
|
||||
|
string value = null; |
||||
|
foreach (var provider in providers) |
||||
|
{ |
||||
|
value = await provider.GetOrNullAsync( |
||||
|
feature, |
||||
|
provider.Name == providerName ? providerKey : null |
||||
|
); |
||||
|
|
||||
|
if (value != null) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.FeatureManagement |
||||
|
{ |
||||
|
[Serializable] |
||||
|
public class FeatureNameValue : NameValue |
||||
|
{ |
||||
|
public FeatureNameValue() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public FeatureNameValue(string name, string value) |
||||
|
{ |
||||
|
Name = name; |
||||
|
Value = value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.FeatureManagement |
||||
|
{ |
||||
|
[Serializable] |
||||
|
public class FeatureValueCacheItem |
||||
|
{ |
||||
|
public string Value { get; set; } |
||||
|
|
||||
|
public FeatureValueCacheItem() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public FeatureValueCacheItem(string value) |
||||
|
{ |
||||
|
Value = value; |
||||
|
} |
||||
|
|
||||
|
public static string CalculateCacheKey(string name, string providerName, string providerKey) |
||||
|
{ |
||||
|
return "pn:" + providerName + ",pk:" + providerKey + ",n:" + name; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Caching; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Domain.Entities.Events; |
||||
|
using Volo.Abp.EventBus; |
||||
|
|
||||
|
namespace Volo.Abp.FeatureManagement |
||||
|
{ |
||||
|
public class FeatureValueCacheItemInvalidator : |
||||
|
ILocalEventHandler<EntityChangedEventData<FeatureValue>>, |
||||
|
ITransientDependency |
||||
|
{ |
||||
|
protected IDistributedCache<FeatureValueCacheItem> Cache { get; } |
||||
|
|
||||
|
public FeatureValueCacheItemInvalidator(IDistributedCache<FeatureValueCacheItem> cache) |
||||
|
{ |
||||
|
Cache = cache; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task HandleEventAsync(EntityChangedEventData<FeatureValue> eventData) |
||||
|
{ |
||||
|
var cacheKey = CalculateCacheKey( |
||||
|
eventData.Entity.Name, |
||||
|
eventData.Entity.ProviderName, |
||||
|
eventData.Entity.ProviderKey |
||||
|
); |
||||
|
|
||||
|
await Cache.RemoveAsync(cacheKey); |
||||
|
} |
||||
|
|
||||
|
protected virtual string CalculateCacheKey(string name, string providerName, string providerKey) |
||||
|
{ |
||||
|
return FeatureValueCacheItem.CalculateCacheKey(name, providerName, providerKey); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp.Features; |
||||
|
|
||||
|
namespace Volo.Abp.FeatureManagement |
||||
|
{ |
||||
|
public interface IFeatureManagementProvider |
||||
|
{ |
||||
|
string Name { get; } |
||||
|
|
||||
|
Task<string> GetOrNullAsync([NotNull] FeatureDefinition feature, [CanBeNull] string providerKey); |
||||
|
|
||||
|
Task SetAsync([NotNull] FeatureDefinition feature, [NotNull] string value, [CanBeNull] string providerKey); |
||||
|
|
||||
|
Task ClearAsync([NotNull] FeatureDefinition feature, [CanBeNull] string providerKey); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.FeatureManagement |
||||
|
{ |
||||
|
public interface IFeatureManagementStore |
||||
|
{ |
||||
|
Task<string> GetOrNullAsync(string name, string providerName, string providerKey); |
||||
|
|
||||
|
Task SetAsync(string name, string value, string providerName, string providerKey); |
||||
|
|
||||
|
Task DeleteAsync(string name, string providerName, string providerKey); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using JetBrains.Annotations; |
||||
|
|
||||
|
namespace Volo.Abp.FeatureManagement |
||||
|
{ |
||||
|
public interface IFeatureManager |
||||
|
{ |
||||
|
Task<string> GetOrNullAsync([NotNull]string name, [NotNull] string providerName, [CanBeNull] string providerKey, bool fallback = true); |
||||
|
|
||||
|
Task<List<FeatureNameValue>> GetAllAsync([NotNull] string providerName, [CanBeNull] string providerKey, bool fallback = true); |
||||
|
|
||||
|
Task SetAsync([NotNull] string name, [CanBeNull] string value, [NotNull] string providerName, [CanBeNull] string providerKey, bool forceToSet = false); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using Volo.Abp.Features; |
||||
|
|
||||
|
namespace Volo.Abp.FeatureManagement |
||||
|
{ |
||||
|
public class TenantFeatureManagementProvider : FeatureManagementProvider |
||||
|
{ |
||||
|
public override string Name => TenantFeatureValueProvider.ProviderName; |
||||
|
|
||||
|
public TenantFeatureManagementProvider(IFeatureManagementStore store) |
||||
|
: base(store) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp.Features; |
||||
|
|
||||
|
namespace Volo.Abp.FeatureManagement |
||||
|
{ |
||||
|
public static class TenantFeatureManagerExtensions |
||||
|
{ |
||||
|
public static Task<string> GetOrNullForTenantAsync(this IFeatureManager featureManager, [NotNull] string name, Guid tenantId, bool fallback = true) |
||||
|
{ |
||||
|
return featureManager.GetOrNullAsync(name, TenantFeatureValueProvider.ProviderName, tenantId.ToString(), fallback); |
||||
|
} |
||||
|
|
||||
|
public static Task<string> GetOrNullForCurrentTenantAsync(this IFeatureManager featureManager, [NotNull] string name, bool fallback = true) |
||||
|
{ |
||||
|
return featureManager.GetOrNullAsync(name, TenantFeatureValueProvider.ProviderName, null, fallback); |
||||
|
} |
||||
|
|
||||
|
public static Task<List<FeatureNameValue>> GetAllForTenantAsync(this IFeatureManager featureManager, Guid tenantId, bool fallback = true) |
||||
|
{ |
||||
|
return featureManager.GetAllAsync(TenantFeatureValueProvider.ProviderName, tenantId.ToString(), fallback); |
||||
|
} |
||||
|
|
||||
|
public static Task<List<FeatureNameValue>> GetAllForCurrentTenantAsync(this IFeatureManager featureManager, bool fallback = true) |
||||
|
{ |
||||
|
return featureManager.GetAllAsync(TenantFeatureValueProvider.ProviderName, null, fallback); |
||||
|
} |
||||
|
|
||||
|
public static Task SetForTenantAsync(this IFeatureManager featureManager, Guid tenantId, [NotNull] string name, [CanBeNull] string value, bool forceToSet = false) |
||||
|
{ |
||||
|
return featureManager.SetAsync(name, value, TenantFeatureValueProvider.ProviderName, tenantId.ToString(), forceToSet); |
||||
|
} |
||||
|
|
||||
|
public static Task SetForCurrentTenantAsync(this IFeatureManager featureManager, [NotNull] string name, [CanBeNull] string value, bool forceToSet = false) |
||||
|
{ |
||||
|
return featureManager.SetAsync(name, value, TenantFeatureValueProvider.ProviderName, null, forceToSet); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue