Browse Source

Implement infrastructure, Domain and EF Core modules.

pull/13881/head
maliming 4 years ago
parent
commit
d62540a492
  1. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs
  2. 10
      framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeatureOptions.cs
  3. 2
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureChecker.cs
  4. 13
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs
  5. 120
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs
  6. 12
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureGroupDefinition.cs
  7. 16
      framework/src/Volo.Abp.Features/Volo/Abp/Features/ICanCreateChildFeature.cs
  8. 13
      framework/src/Volo.Abp.Features/Volo/Abp/Features/IDynamicFeatureDefinitionStore.cs
  9. 9
      framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs
  10. 13
      framework/src/Volo.Abp.Features/Volo/Abp/Features/IStaticFeatureDefinitionStore.cs
  11. 33
      framework/src/Volo.Abp.Features/Volo/Abp/Features/NullDynamicFeatureDefinitionStore.cs
  12. 121
      framework/src/Volo.Abp.Features/Volo/Abp/Features/StaticFeatureDefinitionStore.cs
  13. 21
      framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureDefinitionManager_Tests.cs
  14. 4
      modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/AbpFeatureManagementApplicationContractsModule.cs
  15. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs
  16. 1
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo.Abp.FeatureManagement.Domain.Shared.csproj
  17. 16
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/FeatureDefinitionRecordConsts.cs
  18. 8
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/FeatureGroupDefinitionRecordConsts.cs
  19. 0
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/FeatureValueConsts.cs
  20. 0
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/IValueValidatorFactory.cs
  21. 4
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/NewtonsoftStringValueTypeJsonConverter.cs
  22. 0
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/SelectionStringValueItemSourceJsonConverter.cs
  23. 4
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/StringValueTypeJsonConverter.cs
  24. 0
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorFactory.cs
  25. 4
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorJsonConverter.cs
  26. 8
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/ValueValidatorFactoryOptions.cs
  27. 4
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo.Abp.FeatureManagement.Domain.csproj
  28. 171
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/DynamicFeatureDefinitionStore.cs
  29. 119
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/DynamicFeatureDefinitionStoreInMemoryCache.cs
  30. 205
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDefinitionRecord.cs
  31. 102
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDefinitionSerializer.cs
  32. 85
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureGroupDefinitionRecord.cs
  33. 10
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementOptions.cs
  34. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs
  35. 6
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManager.cs
  36. 26
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IDynamicFeatureDefinitionStoreInMemoryCache.cs
  37. 9
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureDefinitionRecordRepository.cs
  38. 15
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureDefinitionSerializer.cs
  39. 9
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureGroupDefinitionRecordRepository.cs
  40. 8
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IStaticFeatureSaver.cs
  41. 294
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/StaticFeatureSaver.cs
  42. 25
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/StringValueTypeSerializer.cs
  43. 6
      modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/AbpFeatureManagementEntityFrameworkCoreModule.cs
  44. 16
      modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/EfCoreFeatureDefinitionRecordRepository.cs
  45. 16
      modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/EfCoreFeatureGroupDefinitionRecordRepository.cs
  46. 4
      modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/FeatureManagementDbContext.cs
  47. 36
      modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/FeatureManagementDbContextModelCreatingExtensions.cs
  48. 4
      modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/IFeatureManagementDbContext.cs
  49. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/AbpFeatureManagementHttpApiModule.cs
  50. 2
      modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs

@ -291,7 +291,7 @@ public class AbpApplicationConfigurationAppService : ApplicationService, IAbpApp
{
var result = new ApplicationFeatureConfigurationDto();
foreach (var featureDefinition in _featureDefinitionManager.GetAll())
foreach (var featureDefinition in await _featureDefinitionManager.GetAllAsync())
{
if (!featureDefinition.IsVisibleToClients)
{

10
framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeatureOptions.cs

@ -1,4 +1,5 @@
using Volo.Abp.Collections;
using System.Collections.Generic;
using Volo.Abp.Collections;
namespace Volo.Abp.Features;
@ -8,9 +9,16 @@ public class AbpFeatureOptions
public ITypeList<IFeatureValueProvider> ValueProviders { get; }
public HashSet<string> DeletedFeatures { get; }
public HashSet<string> DeletedFeatureGroups { get; }
public AbpFeatureOptions()
{
DefinitionProviders = new TypeList<IFeatureDefinitionProvider>();
ValueProviders = new TypeList<IFeatureValueProvider>();
DeletedFeatures = new HashSet<string>();
DeletedFeatureGroups = new HashSet<string>();
}
}

2
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureChecker.cs

@ -37,7 +37,7 @@ public class FeatureChecker : FeatureCheckerBase
public override async Task<string> GetOrNullAsync(string name)
{
var featureDefinition = FeatureDefinitionManager.Get(name);
var featureDefinition = await FeatureDefinitionManager.GetAsync(name);
var providers = Enumerable
.Reverse(Providers);

13
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs

@ -7,7 +7,7 @@ using Volo.Abp.Validation.StringValues;
namespace Volo.Abp.Features;
public class FeatureDefinition
public class FeatureDefinition : ICanCreateChildFeature
{
/// <summary>
/// Unique name of the feature.
@ -178,6 +178,17 @@ public class FeatureDefinition
_children.Remove(featureToRemove);
}
public FeatureDefinition CreateChildFeature(string name,
string defaultValue = null,
ILocalizableString displayName = null,
ILocalizableString description = null,
IStringValueType valueType = null,
bool isVisibleToClients = true,
bool isAvailableToHost = true)
{
return this.CreateChild(name, defaultValue, displayName, description, valueType, isVisibleToClients, isAvailableToHost);
}
public override string ToString()
{
return $"[{nameof(FeatureDefinition)}: {Name}]";

120
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs

@ -2,120 +2,70 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Features;
public class FeatureDefinitionManager : IFeatureDefinitionManager, ISingletonDependency
{
protected IDictionary<string, FeatureGroupDefinition> FeatureGroupDefinitions => _lazyFeatureGroupDefinitions.Value;
private readonly Lazy<Dictionary<string, FeatureGroupDefinition>> _lazyFeatureGroupDefinitions;
protected IDictionary<string, FeatureDefinition> FeatureDefinitions => _lazyFeatureDefinitions.Value;
private readonly Lazy<Dictionary<string, FeatureDefinition>> _lazyFeatureDefinitions;
protected AbpFeatureOptions Options { get; }
private readonly IServiceScopeFactory _serviceScopeFactory;
protected IStaticFeatureDefinitionStore StaticStore;
protected IDynamicFeatureDefinitionStore DynamicStore;
public FeatureDefinitionManager(
IOptions<AbpFeatureOptions> options,
IServiceScopeFactory serviceScopeFactory)
IStaticFeatureDefinitionStore staticStore,
IDynamicFeatureDefinitionStore dynamicStore)
{
_serviceScopeFactory = serviceScopeFactory;
Options = options.Value;
_lazyFeatureDefinitions = new Lazy<Dictionary<string, FeatureDefinition>>(
CreateFeatureDefinitions,
isThreadSafe: true
);
_lazyFeatureGroupDefinitions = new Lazy<Dictionary<string, FeatureGroupDefinition>>(
CreateFeatureGroupDefinitions,
isThreadSafe: true
);
StaticStore = staticStore;
DynamicStore = dynamicStore;
}
public virtual FeatureDefinition Get(string name)
public virtual async Task<FeatureDefinition> GetAsync(string name)
{
Check.NotNull(name, nameof(name));
var feature = GetOrNull(name);
if (feature == null)
var permission = await GetOrNullAsync(name);
if (permission == null)
{
throw new AbpException("Undefined feature: " + name);
}
return feature;
}
public virtual IReadOnlyList<FeatureDefinition> GetAll()
{
return FeatureDefinitions.Values.ToImmutableList();
}
public virtual FeatureDefinition GetOrNull(string name)
{
return FeatureDefinitions.GetOrDefault(name);
}
public IReadOnlyList<FeatureGroupDefinition> GetGroups()
{
return FeatureGroupDefinitions.Values.ToImmutableList();
return permission;
}
protected virtual Dictionary<string, FeatureDefinition> CreateFeatureDefinitions()
public virtual async Task<FeatureDefinition> GetOrNullAsync(string name)
{
var features = new Dictionary<string, FeatureDefinition>();
foreach (var groupDefinition in FeatureGroupDefinitions.Values)
{
foreach (var feature in groupDefinition.Features)
{
AddFeatureToDictionaryRecursively(features, feature);
}
}
Check.NotNull(name, nameof(name));
return features;
return await StaticStore.GetOrNullAsync(name) ??
await DynamicStore.GetOrNullAsync(name);
}
protected virtual void AddFeatureToDictionaryRecursively(
Dictionary<string, FeatureDefinition> features,
FeatureDefinition feature)
public virtual async Task<IReadOnlyList<FeatureDefinition>> GetAllAsync()
{
if (features.ContainsKey(feature.Name))
{
throw new AbpException("Duplicate feature name: " + feature.Name);
}
var staticFeatures = await StaticStore.GetFeaturesAsync();
var staticFeatureNames = staticFeatures
.Select(p => p.Name)
.ToImmutableHashSet();
features[feature.Name] = feature;
var dynamicFeatures = await DynamicStore.GetFeaturesAsync();
foreach (var child in feature.Children)
{
AddFeatureToDictionaryRecursively(features, child);
}
/* We prefer static features over dynamics */
return staticFeatures.Concat(
dynamicFeatures.Where(d => !staticFeatureNames.Contains(d.Name))
).ToImmutableList();
}
protected virtual Dictionary<string, FeatureGroupDefinition> CreateFeatureGroupDefinitions()
public virtual async Task<IReadOnlyList<FeatureGroupDefinition>> GetGroupsAsync()
{
var context = new FeatureDefinitionContext();
var staticGroups = await StaticStore.GetGroupsAsync();
var staticGroupNames = staticGroups
.Select(p => p.Name)
.ToImmutableHashSet();
using (var scope = _serviceScopeFactory.CreateScope())
{
var providers = Options
.DefinitionProviders
.Select(p => scope.ServiceProvider.GetRequiredService(p) as IFeatureDefinitionProvider)
.ToList();
foreach (var provider in providers)
{
provider.Define(context);
}
}
var dynamicGroups = await DynamicStore.GetGroupsAsync();
return context.Groups;
/* We prefer static groups over dynamics */
return staticGroups.Concat(
dynamicGroups.Where(d => !staticGroupNames.Contains(d.Name))
).ToImmutableList();
}
}

12
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureGroupDefinition.cs

@ -5,7 +5,7 @@ using Volo.Abp.Validation.StringValues;
namespace Volo.Abp.Features;
public class FeatureGroupDefinition
public class FeatureGroupDefinition : ICanCreateChildFeature
{
/// <summary>
/// Unique name of the group.
@ -69,6 +69,16 @@ public class FeatureGroupDefinition
return feature;
}
public FeatureDefinition CreateChildFeature(string name,
string defaultValue = null,
ILocalizableString displayName = null,
ILocalizableString description = null,
IStringValueType valueType = null,
bool isVisibleToClients = true,
bool isAvailableToHost = true)
{
return AddFeature(name, defaultValue, displayName, description, valueType, isVisibleToClients);
}
public virtual List<FeatureDefinition> GetFeaturesWithChildren()
{
var features = new List<FeatureDefinition>();

16
framework/src/Volo.Abp.Features/Volo/Abp/Features/ICanCreateChildFeature.cs

@ -0,0 +1,16 @@
using Volo.Abp.Localization;
using Volo.Abp.Validation.StringValues;
namespace Volo.Abp.Features;
public interface ICanCreateChildFeature
{
FeatureDefinition CreateChildFeature(
string name,
string defaultValue = null,
ILocalizableString displayName = null,
ILocalizableString description = null,
IStringValueType valueType = null,
bool isVisibleToClients = true,
bool isAvailableToHost = true);
}

13
framework/src/Volo.Abp.Features/Volo/Abp/Features/IDynamicFeatureDefinitionStore.cs

@ -0,0 +1,13 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Volo.Abp.Features;
public interface IDynamicFeatureDefinitionStore
{
Task<FeatureDefinition> GetOrNullAsync(string name);
Task<IReadOnlyList<FeatureDefinition>> GetFeaturesAsync();
Task<IReadOnlyList<FeatureGroupDefinition>> GetGroupsAsync();
}

9
framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.Features;
@ -6,11 +7,11 @@ namespace Volo.Abp.Features;
public interface IFeatureDefinitionManager
{
[NotNull]
FeatureDefinition Get([NotNull] string name);
Task<FeatureDefinition> GetAsync([NotNull] string name);
IReadOnlyList<FeatureDefinition> GetAll();
Task<IReadOnlyList<FeatureDefinition>> GetAllAsync();
FeatureDefinition GetOrNull(string name);
Task<FeatureDefinition> GetOrNullAsync(string name);
IReadOnlyList<FeatureGroupDefinition> GetGroups();
Task<IReadOnlyList<FeatureGroupDefinition>> GetGroupsAsync();
}

13
framework/src/Volo.Abp.Features/Volo/Abp/Features/IStaticFeatureDefinitionStore.cs

@ -0,0 +1,13 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Volo.Abp.Features;
public interface IStaticFeatureDefinitionStore
{
Task<FeatureDefinition> GetOrNullAsync(string name);
Task<IReadOnlyList<FeatureDefinition>> GetFeaturesAsync();
Task<IReadOnlyList<FeatureGroupDefinition>> GetGroupsAsync();
}

33
framework/src/Volo.Abp.Features/Volo/Abp/Features/NullDynamicFeatureDefinitionStore.cs

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Features;
public class NullDynamicFeatureDefinitionStore : IDynamicFeatureDefinitionStore, ISingletonDependency
{
private static readonly Task<FeatureDefinition> CachedFeatureResult = Task.FromResult((FeatureDefinition)null);
private static readonly Task<IReadOnlyList<FeatureDefinition>> CachedFeaturesResult =
Task.FromResult((IReadOnlyList<FeatureDefinition>)Array.Empty<FeatureDefinition>().ToImmutableList());
private static readonly Task<IReadOnlyList<FeatureGroupDefinition>> CachedGroupsResult =
Task.FromResult((IReadOnlyList<FeatureGroupDefinition>)Array.Empty<FeatureGroupDefinition>().ToImmutableList());
public Task<FeatureDefinition> GetOrNullAsync(string name)
{
return CachedFeatureResult;
}
public Task<IReadOnlyList<FeatureDefinition>> GetFeaturesAsync()
{
return CachedFeaturesResult;
}
public Task<IReadOnlyList<FeatureGroupDefinition>> GetGroupsAsync()
{
return CachedGroupsResult;
}
}

121
framework/src/Volo.Abp.Features/Volo/Abp/Features/StaticFeatureDefinitionStore.cs

@ -0,0 +1,121 @@
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;
namespace Volo.Abp.Features;
public class StaticFeatureDefinitionStore: IStaticFeatureDefinitionStore, ISingletonDependency
{
protected IDictionary<string, FeatureGroupDefinition> FeatureGroupDefinitions => _lazyFeatureGroupDefinitions.Value;
private readonly Lazy<Dictionary<string, FeatureGroupDefinition>> _lazyFeatureGroupDefinitions;
protected IDictionary<string, FeatureDefinition> FeatureDefinitions => _lazyFeatureDefinitions.Value;
private readonly Lazy<Dictionary<string, FeatureDefinition>> _lazyFeatureDefinitions;
protected AbpFeatureOptions Options { get; }
private readonly IServiceProvider _serviceProvider;
public StaticFeatureDefinitionStore(
IOptions<AbpFeatureOptions> options,
IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
Options = options.Value;
_lazyFeatureDefinitions = new Lazy<Dictionary<string, FeatureDefinition>>(
CreateFeatureDefinitions,
isThreadSafe: true
);
_lazyFeatureGroupDefinitions = new Lazy<Dictionary<string, FeatureGroupDefinition>>(
CreateFeatureGroupDefinitions,
isThreadSafe: true
);
}
public virtual async Task<FeatureDefinition> GetAsync(string name)
{
Check.NotNull(name, nameof(name));
var feature = await GetOrNullAsync(name);
if (feature == null)
{
throw new AbpException("Undefined feature: " + name);
}
return feature;
}
protected virtual Dictionary<string, FeatureDefinition> CreateFeatureDefinitions()
{
var features = new Dictionary<string, FeatureDefinition>();
foreach (var groupDefinition in FeatureGroupDefinitions.Values)
{
foreach (var feature in groupDefinition.Features)
{
AddFeatureToDictionaryRecursively(features, feature);
}
}
return features;
}
protected virtual void AddFeatureToDictionaryRecursively(
Dictionary<string, FeatureDefinition> features,
FeatureDefinition feature)
{
if (features.ContainsKey(feature.Name))
{
throw new AbpException("Duplicate feature name: " + feature.Name);
}
features[feature.Name] = feature;
foreach (var child in feature.Children)
{
AddFeatureToDictionaryRecursively(features, child);
}
}
protected virtual Dictionary<string, FeatureGroupDefinition> CreateFeatureGroupDefinitions()
{
var context = new FeatureDefinitionContext();
using (var scope = _serviceProvider.CreateScope())
{
var providers = Options
.DefinitionProviders
.Select(p => scope.ServiceProvider.GetRequiredService(p) as IFeatureDefinitionProvider)
.ToList();
foreach (var provider in providers)
{
provider.Define(context);
}
}
return context.Groups;
}
public virtual Task<FeatureDefinition> GetOrNullAsync(string name)
{
return Task.FromResult(FeatureDefinitions.GetOrDefault(name));
}
public virtual Task<IReadOnlyList<FeatureDefinition>> GetFeaturesAsync()
{
return Task.FromResult<IReadOnlyList<FeatureDefinition>>(FeatureDefinitions.Values.ToList());
}
public virtual Task<IReadOnlyList<FeatureGroupDefinition>> GetGroupsAsync()
{
return Task.FromResult<IReadOnlyList<FeatureGroupDefinition>>(FeatureGroupDefinitions.Values.ToList());
}
}

21
framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureDefinitionManager_Tests.cs

@ -1,4 +1,5 @@
using Shouldly;
using System.Threading.Tasks;
using Shouldly;
using Xunit;
namespace Volo.Abp.Features;
@ -13,22 +14,22 @@ public class FeatureDefinitionManager_Tests : FeatureTestBase
}
[Fact]
public void Should_Get_Defined_Features()
public async Task Should_Get_Defined_Features()
{
_featureDefinitionManager.GetOrNull("BooleanTestFeature1").ShouldNotBeNull();
_featureDefinitionManager.Get("BooleanTestFeature1").Name.ShouldBe("BooleanTestFeature1");
await _featureDefinitionManager.GetOrNullAsync("BooleanTestFeature1").ShouldNotBeNull();
(await _featureDefinitionManager.GetAsync("BooleanTestFeature1")).Name.ShouldBe("BooleanTestFeature1");
_featureDefinitionManager.GetOrNull("IntegerTestFeature1").ShouldNotBeNull();
_featureDefinitionManager.Get("IntegerTestFeature1").Name.ShouldBe("IntegerTestFeature1");
await _featureDefinitionManager.GetOrNullAsync("IntegerTestFeature1").ShouldNotBeNull();
(await _featureDefinitionManager.GetAsync("IntegerTestFeature1")).Name.ShouldBe("IntegerTestFeature1");
}
[Fact]
public void Should_Not_Get_Undefined_Features()
public async Task Should_Not_Get_Undefined_Features()
{
_featureDefinitionManager.GetOrNull("UndefinedFeature").ShouldBeNull();
Assert.Throws<AbpException>(() =>
(await _featureDefinitionManager.GetOrNullAsync("UndefinedFeature")).ShouldBeNull();
await Assert.ThrowsAsync<AbpException>(async () =>
{
_featureDefinitionManager.Get("UndefinedFeature");
await _featureDefinitionManager.GetAsync("UndefinedFeature");
});
}
}

4
modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/AbpFeatureManagementApplicationContractsModule.cs

@ -26,8 +26,8 @@ public class AbpFeatureManagementApplicationContractsModule : AbpModule
options.FileSets.AddEmbedded<AbpFeatureManagementApplicationContractsModule>();
});
var contractsOptionsActions = context.Services.GetPreConfigureActions<AbpFeatureManagementApplicationContractsOptions>();
Configure<AbpFeatureManagementApplicationContractsOptions>(options =>
var contractsOptionsActions = context.Services.GetPreConfigureActions<ValueValidatorFactoryOptions>();
Configure<ValueValidatorFactoryOptions>(options =>
{
contractsOptionsActions.Configure(options);
});

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs

@ -35,7 +35,7 @@ public class FeatureAppService : FeatureManagementAppServiceBase, IFeatureAppSer
Groups = new List<FeatureGroupDto>()
};
foreach (var group in FeatureDefinitionManager.GetGroups())
foreach (var group in await FeatureDefinitionManager.GetGroupsAsync())
{
var groupDto = new FeatureGroupDto
{

1
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo.Abp.FeatureManagement.Domain.Shared.csproj

@ -16,6 +16,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="6.0.5" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Json\Volo.Abp.Json.csproj" />
</ItemGroup>
<ItemGroup>

16
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/FeatureDefinitionRecordConsts.cs

@ -0,0 +1,16 @@
namespace Volo.Abp.FeatureManagement;
public static class FeatureDefinitionRecordConsts
{
public static int MaxNameLength { get; set; } = 128;
public static int MaxDisplayNameLength { get; set; } = 256;
public static int MaxDescriptionLength { get; set; } = 256;
public static int MaxDefaultValueLength { get; set; } = 256;
public static int MaxAllowedProvidersLength { get; set; } = 256;
public static int MaxValueTypeLength { get; set; } = 256;
}

8
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/FeatureGroupDefinitionRecordConsts.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.FeatureManagement;
public static class FeatureGroupDefinitionRecordConsts
{
public static int MaxNameLength { get; set; } = 128;
public static int MaxDisplayNameLength { get; set; } = 256;
}

0
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureValueConsts.cs → modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/FeatureValueConsts.cs

0
modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/IValueValidatorFactory.cs → modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/IValueValidatorFactory.cs

4
modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/NewtonsoftStringValueTypeJsonConverter.cs → modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/NewtonsoftStringValueTypeJsonConverter.cs

@ -14,9 +14,9 @@ public class NewtonsoftStringValueTypeJsonConverter : JsonConverter, ITransientD
{
public override bool CanWrite => false;
protected readonly AbpFeatureManagementApplicationContractsOptions Options;
protected readonly ValueValidatorFactoryOptions Options;
public NewtonsoftStringValueTypeJsonConverter(IOptions<AbpFeatureManagementApplicationContractsOptions> options)
public NewtonsoftStringValueTypeJsonConverter(IOptions<ValueValidatorFactoryOptions> options)
{
Options = options.Value;
}

0
modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/SelectionStringValueItemSourceJsonConverter.cs → modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/SelectionStringValueItemSourceJsonConverter.cs

4
modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/StringValueTypeJsonConverter.cs → modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/StringValueTypeJsonConverter.cs

@ -12,9 +12,9 @@ public class StringValueTypeJsonConverter : JsonConverter<IStringValueType>
private JsonSerializerOptions _writeJsonSerializerOptions;
protected readonly AbpFeatureManagementApplicationContractsOptions Options;
protected readonly ValueValidatorFactoryOptions Options;
public StringValueTypeJsonConverter(AbpFeatureManagementApplicationContractsOptions options)
public StringValueTypeJsonConverter(ValueValidatorFactoryOptions options)
{
Options = options;
}

0
modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorFactory.cs → modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorFactory.cs

4
modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorJsonConverter.cs → modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorJsonConverter.cs

@ -14,9 +14,9 @@ public class ValueValidatorJsonConverter : JsonConverter<IValueValidator>
private JsonSerializerOptions _writeJsonSerializerOptions;
protected readonly AbpFeatureManagementApplicationContractsOptions Options;
protected readonly ValueValidatorFactoryOptions Options;
public ValueValidatorJsonConverter(AbpFeatureManagementApplicationContractsOptions options)
public ValueValidatorJsonConverter(ValueValidatorFactoryOptions options)
{
Options = options;
}

8
modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/AbpFeatureManagementApplicationContractsOptions.cs → modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/ValueValidatorFactoryOptions.cs

@ -4,13 +4,13 @@ using Volo.Abp.Validation.StringValues;
namespace Volo.Abp.FeatureManagement;
public class AbpFeatureManagementApplicationContractsOptions
public class ValueValidatorFactoryOptions
{
public HashSet<IValueValidatorFactory> ValueValidatorFactory { get; }
public AbpFeatureManagementApplicationContractsOptions()
public ValueValidatorFactoryOptions()
{
ValueValidatorFactory = new HashSet<IValueValidatorFactory>
ValueValidatorFactory = new HashSet<IValueValidatorFactory>
{
new ValueValidatorFactory<AlwaysValidValueValidator>("NULL"),
new ValueValidatorFactory<BooleanValueValidator>("BOOLEAN"),

4
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo.Abp.FeatureManagement.Domain.csproj

@ -18,4 +18,8 @@
<ProjectReference Include="..\Volo.Abp.FeatureManagement.Domain.Shared\Volo.Abp.FeatureManagement.Domain.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Polly" Version="$(PollyPackageVersion)" />
</ItemGroup>
</Project>

171
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/DynamicFeatureDefinitionStore.cs

@ -0,0 +1,171 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DistributedLocking;
using Volo.Abp.Features;
using Volo.Abp.Threading;
namespace Volo.Abp.FeatureManagement;
[Dependency(ReplaceServices = true)]
public class DynamicFeatureDefinitionStore : IDynamicFeatureDefinitionStore, ITransientDependency
{
protected IFeatureGroupDefinitionRecordRepository FeatureGroupRepository { get; }
protected IFeatureDefinitionRecordRepository FeatureRepository { get; }
protected IFeatureDefinitionSerializer FeatureDefinitionSerializer { get; }
protected IDynamicFeatureDefinitionStoreInMemoryCache StoreCache { get; }
protected IDistributedCache DistributedCache { get; }
protected IAbpDistributedLock DistributedLock { get; }
public FeatureManagementOptions FeatureManagementOptions { get; }
protected AbpDistributedCacheOptions CacheOptions { get; }
public DynamicFeatureDefinitionStore(
IFeatureGroupDefinitionRecordRepository featureGroupRepository,
IFeatureDefinitionRecordRepository featureRepository,
IFeatureDefinitionSerializer featureDefinitionSerializer,
IDynamicFeatureDefinitionStoreInMemoryCache storeCache,
IDistributedCache distributedCache,
IOptions<AbpDistributedCacheOptions> cacheOptions,
IOptions<FeatureManagementOptions> featureManagementOptions,
IAbpDistributedLock distributedLock)
{
FeatureGroupRepository = featureGroupRepository;
FeatureRepository = featureRepository;
FeatureDefinitionSerializer = featureDefinitionSerializer;
StoreCache = storeCache;
DistributedCache = distributedCache;
DistributedLock = distributedLock;
FeatureManagementOptions = featureManagementOptions.Value;
CacheOptions = cacheOptions.Value;
}
public virtual async Task<FeatureDefinition> GetOrNullAsync(string name)
{
if (!FeatureManagementOptions.IsDynamicFeatureStoreEnabled)
{
return null;
}
using (await StoreCache.SyncSemaphore.LockAsync())
{
await EnsureCacheIsUptoDateAsync();
return StoreCache.GetFeatureOrNull(name);
}
}
public virtual async Task<IReadOnlyList<FeatureDefinition>> GetFeaturesAsync()
{
if (!FeatureManagementOptions.IsDynamicFeatureStoreEnabled)
{
return Array.Empty<FeatureDefinition>();
}
using (await StoreCache.SyncSemaphore.LockAsync())
{
await EnsureCacheIsUptoDateAsync();
return StoreCache.GetFeatures().ToImmutableList();
}
}
public virtual async Task<IReadOnlyList<FeatureGroupDefinition>> GetGroupsAsync()
{
if (!FeatureManagementOptions.IsDynamicFeatureStoreEnabled)
{
return Array.Empty<FeatureGroupDefinition>();
}
using (await StoreCache.SyncSemaphore.LockAsync())
{
await EnsureCacheIsUptoDateAsync();
return StoreCache.GetGroups().ToImmutableList();
}
}
protected virtual async Task EnsureCacheIsUptoDateAsync()
{
if (StoreCache.LastCheckTime.HasValue &&
DateTime.Now.Subtract(StoreCache.LastCheckTime.Value).TotalSeconds < 30)
{
/* We get the latest feature with a small delay for optimization */
return;
}
var stampInDistributedCache = await GetOrSetStampInDistributedCache();
if (stampInDistributedCache == StoreCache.CacheStamp)
{
StoreCache.LastCheckTime = DateTime.Now;
return;
}
await UpdateInMemoryStoreCache();
StoreCache.CacheStamp = stampInDistributedCache;
StoreCache.LastCheckTime = DateTime.Now;
}
protected virtual async Task UpdateInMemoryStoreCache()
{
var featureGroupRecords = await FeatureGroupRepository.GetListAsync();
var featureRecords = await FeatureRepository.GetListAsync();
await StoreCache.FillAsync(featureGroupRecords, featureRecords);
}
protected virtual async Task<string> GetOrSetStampInDistributedCache()
{
var cacheKey = GetCommonStampCacheKey();
var stampInDistributedCache = await DistributedCache.GetStringAsync(cacheKey);
if (stampInDistributedCache != null)
{
return stampInDistributedCache;
}
await using (var commonLockHandle = await DistributedLock
.TryAcquireAsync(GetCommonDistributedLockKey(), TimeSpan.FromMinutes(2)))
{
if (commonLockHandle == null)
{
/* This request will fail */
throw new AbpException(
"Could not acquire distributed lock for feature definition common stamp check!"
);
}
stampInDistributedCache = await DistributedCache.GetStringAsync(cacheKey);
if (stampInDistributedCache != null)
{
return stampInDistributedCache;
}
stampInDistributedCache = Guid.NewGuid().ToString();
await DistributedCache.SetStringAsync(
cacheKey,
stampInDistributedCache,
new DistributedCacheEntryOptions
{
SlidingExpiration = TimeSpan.FromDays(30) //TODO: Make it configurable?
}
);
}
return stampInDistributedCache;
}
protected virtual string GetCommonStampCacheKey()
{
return $"{CacheOptions.KeyPrefix}_AbpInMemoryFeatureCacheStamp";
}
protected virtual string GetCommonDistributedLockKey()
{
return $"{CacheOptions.KeyPrefix}_Common_AbpFeatureUpdateLock";
}
}

119
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/DynamicFeatureDefinitionStoreInMemoryCache.cs

@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
using Volo.Abp.Localization;
namespace Volo.Abp.FeatureManagement;
public class DynamicFeatureDefinitionStoreInMemoryCache:
IDynamicFeatureDefinitionStoreInMemoryCache,
ISingletonDependency
{
public string CacheStamp { get; set; }
protected IDictionary<string, FeatureGroupDefinition> FeatureGroupDefinitions { get; }
protected IDictionary<string, FeatureDefinition> FeatureDefinitions { get; }
protected StringValueTypeSerializer StateCheckerSerializer { get; }
protected ILocalizableStringSerializer LocalizableStringSerializer { get; }
public SemaphoreSlim SyncSemaphore { get; } = new(1, 1);
public DateTime? LastCheckTime { get; set; }
public DynamicFeatureDefinitionStoreInMemoryCache(
StringValueTypeSerializer stateCheckerSerializer,
ILocalizableStringSerializer localizableStringSerializer)
{
StateCheckerSerializer = stateCheckerSerializer;
LocalizableStringSerializer = localizableStringSerializer;
FeatureGroupDefinitions = new Dictionary<string, FeatureGroupDefinition>();
FeatureDefinitions = new Dictionary<string, FeatureDefinition>();
}
public Task FillAsync(
List<FeatureGroupDefinitionRecord> featureGroupRecords,
List<FeatureDefinitionRecord> featureRecords)
{
FeatureGroupDefinitions.Clear();
FeatureDefinitions.Clear();
var context = new FeatureDefinitionContext();
foreach (var featureGroupRecord in featureGroupRecords)
{
var featureGroup = context.AddGroup(
featureGroupRecord.Name,
LocalizableStringSerializer.Deserialize(featureGroupRecord.DisplayName)
);
FeatureGroupDefinitions[featureGroup.Name] = featureGroup;
foreach (var property in featureGroupRecord.ExtraProperties)
{
featureGroup[property.Key] = property.Value;
}
var featureRecordsInThisGroup = featureRecords
.Where(p => p.GroupName == featureGroup.Name);
foreach (var featureRecord in featureRecordsInThisGroup.Where(x => x.ParentName == null))
{
AddFeatureRecursively(featureGroup, featureRecord, featureRecords);
}
}
return Task.CompletedTask;
}
public FeatureDefinition GetFeatureOrNull(string name)
{
return FeatureDefinitions.GetOrDefault(name);
}
public IReadOnlyList<FeatureDefinition> GetFeatures()
{
return FeatureDefinitions.Values.ToList();
}
public IReadOnlyList<FeatureGroupDefinition> GetGroups()
{
return FeatureGroupDefinitions.Values.ToList();
}
private void AddFeatureRecursively(ICanCreateChildFeature featureContainer,
FeatureDefinitionRecord featureRecord,
List<FeatureDefinitionRecord> allFeatureRecords)
{
var feature = featureContainer.CreateChildFeature(
featureRecord.Name,
featureRecord.DefaultValue,
LocalizableStringSerializer.Deserialize(featureRecord.DisplayName),
LocalizableStringSerializer.Deserialize(featureRecord.Description),
StateCheckerSerializer.Deserialize(featureRecord.ValueType),
featureRecord.IsVisibleToClients,
featureRecord.IsAvailableToHost
);
FeatureDefinitions[feature.Name] = feature;
if (!featureRecord.AllowedProviders.IsNullOrWhiteSpace())
{
feature.AllowedProviders.AddRange(featureRecord.AllowedProviders.Split(','));
}
foreach (var property in featureRecord.ExtraProperties)
{
feature[property.Key] = property.Value;
}
foreach (var subFeature in allFeatureRecords.Where(p => p.ParentName == featureRecord.Name))
{
AddFeatureRecursively(feature, subFeature, allFeatureRecords);
}
}
}

205
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDefinitionRecord.cs

@ -0,0 +1,205 @@
using System;
using System.Text.Json.Serialization;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
namespace Volo.Abp.FeatureManagement;
public class FeatureDefinitionRecord : BasicAggregateRoot<Guid>, IHasExtraProperties
{
/* Ignoring Id because it is different whenever we create an instance of
* this class, and we are using Json Serialize, than Hash to understand
* if feature definitions have changed (in StaticFeatureSaver.CalculateHash()).
*/
[JsonIgnore] //TODO: TODO: Use JSON modifier to ignore this property
public override Guid Id { get; protected set; }
public string GroupName { get; set; }
public string Name { get; set; }
public string ParentName { get; set; }
public string DisplayName { get; set; }
public string Description { get; set; }
public string DefaultValue { get; set; }
public bool IsVisibleToClients { get; set; }
public bool IsAvailableToHost { get; set; }
/// <summary>
/// Comma separated list of provider names.
/// </summary>
public string AllowedProviders { get; set; }
/// <summary>
/// Serialized string to store info about the ValueType.
/// </summary>
public string ValueType { get; set; } // ToggleStringValueType
public ExtraPropertyDictionary ExtraProperties { get; protected set; }
public FeatureDefinitionRecord()
{
IsVisibleToClients = true;
IsAvailableToHost = true;
ExtraProperties = new ExtraPropertyDictionary();
this.SetDefaultsForExtraProperties();
}
public FeatureDefinitionRecord(
Guid id,
string groupName,
string name,
string parentName,
string displayName = null,
string description = null,
string defaultValue = null,
bool isVisibleToClients = true,
bool isAvailableToHost = true,
string allowedProviders = null,
string valueType = null)
: base(id)
{
GroupName = Check.NotNullOrWhiteSpace(groupName, nameof(groupName), FeatureDefinitionRecordConsts.MaxNameLength);
Name = Check.NotNullOrWhiteSpace(name, nameof(name), FeatureDefinitionRecordConsts.MaxNameLength);
ParentName = Check.Length(parentName, nameof(parentName), FeatureDefinitionRecordConsts.MaxNameLength);
DisplayName = Check.NotNullOrWhiteSpace(displayName, nameof(displayName), FeatureDefinitionRecordConsts.MaxDisplayNameLength);
Description = Check.Length(description, nameof(description), FeatureDefinitionRecordConsts.MaxDescriptionLength);
DefaultValue = Check.NotNullOrWhiteSpace(defaultValue, nameof(defaultValue), FeatureDefinitionRecordConsts.MaxDefaultValueLength);
IsVisibleToClients = isVisibleToClients;
IsAvailableToHost = isAvailableToHost;
AllowedProviders = Check.Length(allowedProviders, nameof(allowedProviders), FeatureDefinitionRecordConsts.MaxAllowedProvidersLength);
ValueType = Check.NotNullOrWhiteSpace(valueType, nameof(valueType), FeatureDefinitionRecordConsts.MaxValueTypeLength);
ExtraProperties = new ExtraPropertyDictionary();
this.SetDefaultsForExtraProperties();
}
public bool HasSameData(FeatureDefinitionRecord otherRecord)
{
if (Name != otherRecord.Name)
{
return false;
}
if (GroupName != otherRecord.GroupName)
{
return false;
}
if (ParentName != otherRecord.ParentName)
{
return false;
}
if (DisplayName != otherRecord.DisplayName)
{
return false;
}
if (Description != otherRecord.Description)
{
return false;
}
if (DefaultValue != otherRecord.DefaultValue)
{
return false;
}
if (IsVisibleToClients != otherRecord.IsVisibleToClients)
{
return false;
}
if (IsAvailableToHost != otherRecord.IsAvailableToHost)
{
return false;
}
if (AllowedProviders != otherRecord.AllowedProviders)
{
return false;
}
if (ValueType != otherRecord.ValueType)
{
return false;
}
if (!this.HasSameExtraProperties(otherRecord))
{
return false;
}
return true;
}
public void Patch(FeatureDefinitionRecord otherRecord)
{
if (Name != otherRecord.Name)
{
Name = otherRecord.Name;
}
if (GroupName != otherRecord.GroupName)
{
GroupName = otherRecord.GroupName;
}
if (ParentName != otherRecord.ParentName)
{
ParentName = otherRecord.ParentName;
}
if (DisplayName != otherRecord.DisplayName)
{
DisplayName = otherRecord.DisplayName;
}
if (Description != otherRecord.Description)
{
Description = otherRecord.Description;
}
if (DefaultValue != otherRecord.DefaultValue)
{
DefaultValue = otherRecord.DefaultValue;
}
if (IsVisibleToClients != otherRecord.IsVisibleToClients)
{
IsVisibleToClients = otherRecord.IsVisibleToClients;
}
if (IsAvailableToHost != otherRecord.IsAvailableToHost)
{
IsAvailableToHost = otherRecord.IsAvailableToHost;
}
if (AllowedProviders != otherRecord.AllowedProviders)
{
AllowedProviders = otherRecord.AllowedProviders;
}
if (ValueType != otherRecord.ValueType)
{
ValueType = otherRecord.ValueType;
}
if (!this.HasSameExtraProperties(otherRecord))
{
this.ExtraProperties.Clear();
foreach (var property in otherRecord.ExtraProperties)
{
this.ExtraProperties.Add(property.Key, property.Value);
}
}
}
}

102
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDefinitionSerializer.cs

@ -0,0 +1,102 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
using Volo.Abp.Guids;
using Volo.Abp.Localization;
using Volo.Abp.Validation.StringValues;
namespace Volo.Abp.FeatureManagement;
public class FeatureDefinitionSerializer : IFeatureDefinitionSerializer, ITransientDependency
{
protected IGuidGenerator GuidGenerator { get; }
protected ILocalizableStringSerializer LocalizableStringSerializer { get; }
protected StringValueTypeSerializer StringValueTypeSerializer { get; }
public FeatureDefinitionSerializer(IGuidGenerator guidGenerator, ILocalizableStringSerializer localizableStringSerializer, StringValueTypeSerializer stringValueTypeSerializer)
{
GuidGenerator = guidGenerator;
LocalizableStringSerializer = localizableStringSerializer;
StringValueTypeSerializer = stringValueTypeSerializer;
}
public async Task<(FeatureGroupDefinitionRecord[], FeatureDefinitionRecord[])> SerializeAsync(IEnumerable<FeatureGroupDefinition> featureGroups)
{
var featureGroupRecords = new List<FeatureGroupDefinitionRecord>();
var featureRecords = new List<FeatureDefinitionRecord>();
foreach (var featureGroup in featureGroups)
{
featureGroupRecords.Add(await SerializeAsync(featureGroup));
foreach (var feature in featureGroup.GetFeaturesWithChildren())
{
featureRecords.Add(await SerializeAsync(feature, featureGroup));
}
}
return (featureGroupRecords.ToArray(), featureRecords.ToArray());
}
public Task<FeatureGroupDefinitionRecord> SerializeAsync(FeatureGroupDefinition featureGroup)
{
using (CultureHelper.Use(CultureInfo.InvariantCulture))
{
var featureGroupRecord = new FeatureGroupDefinitionRecord(
GuidGenerator.Create(),
featureGroup.Name,
LocalizableStringSerializer.Serialize(featureGroup.DisplayName)
);
foreach (var property in featureGroup.Properties)
{
featureGroupRecord.SetProperty(property.Key, property.Value);
}
return Task.FromResult(featureGroupRecord);
}
}
public Task<FeatureDefinitionRecord> SerializeAsync(FeatureDefinition feature, FeatureGroupDefinition featureGroup)
{
using (CultureHelper.Use(CultureInfo.InvariantCulture))
{
var featureRecord = new FeatureDefinitionRecord(
GuidGenerator.Create(),
featureGroup?.Name,
feature.Name,
feature.Parent?.Name,
LocalizableStringSerializer.Serialize(feature.DisplayName),
LocalizableStringSerializer.Serialize(feature.Description),
feature.DefaultValue,
feature.IsVisibleToClients,
feature.IsAvailableToHost,
SerializeProviders(feature.AllowedProviders),
SerializeStringValueType(feature.ValueType)
);
foreach (var property in feature.Properties)
{
featureRecord.SetProperty(property.Key, property.Value);
}
return Task.FromResult(featureRecord);
}
}
protected virtual string SerializeProviders(ICollection<string> providers)
{
return providers.Any()
? providers.JoinAsString(",")
: null;
}
protected virtual string SerializeStringValueType(IStringValueType stringValueType)
{
return StringValueTypeSerializer.Serialize(stringValueType);
}
}

85
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureGroupDefinitionRecord.cs

@ -0,0 +1,85 @@
using System;
using Newtonsoft.Json;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
namespace Volo.Abp.FeatureManagement;
public class FeatureGroupDefinitionRecord : BasicAggregateRoot<Guid>, IHasExtraProperties
{
/* Ignoring Id because it is different whenever we create an instance of
* this class, and we are using Json Serialize, than Hash to understand
* if feature definitions have changed (in StaticFeatureSaver.CalculateHash()).
*/
[JsonIgnore] //TODO: TODO: Use JSON modifier to ignore this property
public override Guid Id { get; protected set; }
public string Name { get; set; }
public string DisplayName { get; set; }
public ExtraPropertyDictionary ExtraProperties { get; protected set; }
public FeatureGroupDefinitionRecord()
{
ExtraProperties = new ExtraPropertyDictionary();
this.SetDefaultsForExtraProperties();
}
public FeatureGroupDefinitionRecord(
Guid id,
string name,
string displayName)
: base(id)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name), FeatureGroupDefinitionRecordConsts.MaxNameLength);
DisplayName = Check.NotNullOrWhiteSpace(displayName, nameof(displayName), FeatureGroupDefinitionRecordConsts.MaxDisplayNameLength);;
ExtraProperties = new ExtraPropertyDictionary();
this.SetDefaultsForExtraProperties();
}
public bool HasSameData(FeatureGroupDefinitionRecord otherRecord)
{
if (Name != otherRecord.Name)
{
return false;
}
if (DisplayName != otherRecord.DisplayName)
{
return false;
}
if (!this.HasSameExtraProperties(otherRecord))
{
return false;
}
return true;
}
public void Patch(FeatureGroupDefinitionRecord otherRecord)
{
if (Name != otherRecord.Name)
{
Name = otherRecord.Name;
}
if (DisplayName != otherRecord.DisplayName)
{
DisplayName = otherRecord.DisplayName;
}
if (!this.HasSameExtraProperties(otherRecord))
{
this.ExtraProperties.Clear();
foreach (var property in otherRecord.ExtraProperties)
{
this.ExtraProperties.Add(property.Key, property.Value);
}
}
}
}

10
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementOptions.cs

@ -9,6 +9,16 @@ public class FeatureManagementOptions
public Dictionary<string, string> ProviderPolicies { get; }
/// <summary>
/// Default: true.
/// </summary>
public bool SaveStaticFeaturesToDatabase { get; set; } = true;
/// <summary>
/// Default: false.
/// </summary>
public bool IsDynamicFeatureStoreEnabled { get; set; }
public FeatureManagementOptions()
{
Providers = new TypeList<IFeatureManagementProvider>();

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs

@ -87,7 +87,7 @@ public class FeatureManagementStore : IFeatureManagementStore, ITransientDepende
string currentName,
FeatureValueCacheItem currentCacheItem)
{
var featureDefinitions = FeatureDefinitionManager.GetAll();
var featureDefinitions = await FeatureDefinitionManager.GetAllAsync();
var featuresDictionary = (await FeatureValueRepository.GetListAsync(providerName, providerKey))
.ToDictionary(s => s.Name, s => s.Value);

6
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManager.cs

@ -75,7 +75,7 @@ public class FeatureManager : IFeatureManager, ISingletonDependency
{
Check.NotNull(providerName, nameof(providerName));
var featureDefinitions = FeatureDefinitionManager.GetAll();
var featureDefinitions = await FeatureDefinitionManager.GetAllAsync();
var providers = Enumerable.Reverse(Providers).SkipWhile(c => c.Name != providerName);
if (!fallback)
@ -130,7 +130,7 @@ public class FeatureManager : IFeatureManager, ISingletonDependency
Check.NotNull(name, nameof(name));
Check.NotNull(providerName, nameof(providerName));
var feature = FeatureDefinitionManager.Get(name);
var feature = await FeatureDefinitionManager.GetAsync(name);
if (feature.ValueType?.Validator.IsValid(value) == false)
{
@ -186,7 +186,7 @@ public class FeatureManager : IFeatureManager, ISingletonDependency
string providerKey,
bool fallback = true) //TODO: Fallback is not used
{
var feature = FeatureDefinitionManager.Get(name);
var feature = await FeatureDefinitionManager.GetAsync(name);
var providers = Enumerable
.Reverse(Providers);

26
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IDynamicFeatureDefinitionStoreInMemoryCache.cs

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Features;
namespace Volo.Abp.FeatureManagement;
public interface IDynamicFeatureDefinitionStoreInMemoryCache
{
string CacheStamp { get; set; }
SemaphoreSlim SyncSemaphore { get; }
DateTime? LastCheckTime { get; set; }
Task FillAsync(
List<FeatureGroupDefinitionRecord> featureGroupRecords,
List<FeatureDefinitionRecord> featureRecords);
FeatureDefinition GetFeatureOrNull(string name);
IReadOnlyList<FeatureDefinition> GetFeatures();
IReadOnlyList<FeatureGroupDefinition> GetGroups();
}

9
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureDefinitionRecordRepository.cs

@ -0,0 +1,9 @@
using System;
using Volo.Abp.Domain.Repositories;
namespace Volo.Abp.FeatureManagement;
public interface IFeatureDefinitionRecordRepository : IBasicRepository<FeatureDefinitionRecord, Guid>
{
}

15
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureDefinitionSerializer.cs

@ -0,0 +1,15 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Volo.Abp.Features;
namespace Volo.Abp.FeatureManagement;
public interface IFeatureDefinitionSerializer
{
Task<(FeatureGroupDefinitionRecord[], FeatureDefinitionRecord[])> SerializeAsync(IEnumerable<FeatureGroupDefinition> featureGroups);
Task<FeatureGroupDefinitionRecord> SerializeAsync(FeatureGroupDefinition featureGroup);
Task<FeatureDefinitionRecord> SerializeAsync(FeatureDefinition feature, [CanBeNull] FeatureGroupDefinition featureGroup);
}

9
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureGroupDefinitionRecordRepository.cs

@ -0,0 +1,9 @@
using System;
using Volo.Abp.Domain.Repositories;
namespace Volo.Abp.FeatureManagement;
public interface IFeatureGroupDefinitionRecordRepository : IBasicRepository<FeatureGroupDefinitionRecord, Guid>
{
}

8
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IStaticFeatureSaver.cs

@ -0,0 +1,8 @@
using System.Threading.Tasks;
namespace Volo.Abp.FeatureManagement;
public interface IStaticFeatureSaver
{
Task SaveAsync();
}

294
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/StaticFeatureSaver.cs

@ -0,0 +1,294 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DistributedLocking;
using Volo.Abp.Features;
using Volo.Abp.Threading;
using Volo.Abp.Uow;
namespace Volo.Abp.FeatureManagement;
public class StaticFeatureSaver : IStaticFeatureSaver, ITransientDependency
{
protected IStaticFeatureDefinitionStore StaticStore { get; }
protected IFeatureGroupDefinitionRecordRepository FeatureGroupRepository { get; }
protected IFeatureDefinitionRecordRepository FeatureRepository { get; }
protected IFeatureDefinitionSerializer FeatureSerializer { get; }
protected IDistributedCache Cache { get; }
protected IApplicationNameAccessor ApplicationNameAccessor { get; }
protected IAbpDistributedLock DistributedLock { get; }
protected AbpFeatureOptions FeatureOptions { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
protected AbpDistributedCacheOptions CacheOptions { get; }
public StaticFeatureSaver(
IStaticFeatureDefinitionStore staticStore,
IFeatureGroupDefinitionRecordRepository featureGroupRepository,
IFeatureDefinitionRecordRepository featureRepository,
IFeatureDefinitionSerializer featureSerializer,
IDistributedCache cache,
IOptions<AbpDistributedCacheOptions> cacheOptions,
IApplicationNameAccessor applicationNameAccessor,
IAbpDistributedLock distributedLock,
IOptions<AbpFeatureOptions> featureManagementOptions,
ICancellationTokenProvider cancellationTokenProvider)
{
StaticStore = staticStore;
FeatureGroupRepository = featureGroupRepository;
FeatureRepository = featureRepository;
FeatureSerializer = featureSerializer;
Cache = cache;
ApplicationNameAccessor = applicationNameAccessor;
DistributedLock = distributedLock;
CancellationTokenProvider = cancellationTokenProvider;
FeatureOptions = featureManagementOptions.Value;
CacheOptions = cacheOptions.Value;
}
[UnitOfWork]
public virtual async Task SaveAsync()
{
await using var applicationLockHandle = await DistributedLock.TryAcquireAsync(
GetApplicationDistributedLockKey()
);
if (applicationLockHandle == null)
{
/* Another application instance is already doing it */
return;
}
/* NOTE: This can be further optimized by using 4 cache values for:
* Groups, features, deleted groups and deleted features.
* But the code would be more complex. This is enough for now.
*/
var cacheKey = GetApplicationHashCacheKey();
var cachedHash = await Cache.GetStringAsync(cacheKey, CancellationTokenProvider.Token);
var (featureGroupRecords, featureRecords) = await FeatureSerializer.SerializeAsync(
await StaticStore.GetGroupsAsync()
);
var currentHash = CalculateHash(
featureGroupRecords,
featureRecords,
FeatureOptions.DeletedFeatureGroups,
FeatureOptions.DeletedFeatures
);
if (cachedHash == currentHash)
{
return;
}
await using (var commonLockHandle = await DistributedLock.TryAcquireAsync(
GetCommonDistributedLockKey(),
TimeSpan.FromMinutes(5)))
{
if (commonLockHandle == null)
{
/* It will re-try */
throw new AbpException("Could not acquire distributed lock for saving static features!");
}
var hasChangesInGroups = await UpdateChangedFeatureGroupsAsync(featureGroupRecords);
var hasChangesInFeatures = await UpdateChangedFeaturesAsync(featureRecords);
if (hasChangesInGroups ||hasChangesInFeatures)
{
await Cache.SetStringAsync(
GetCommonStampCacheKey(),
Guid.NewGuid().ToString(),
new DistributedCacheEntryOptions {
SlidingExpiration = TimeSpan.FromDays(30) //TODO: Make it configurable?
},
CancellationTokenProvider.Token
);
}
}
await Cache.SetStringAsync(
cacheKey,
currentHash,
new DistributedCacheEntryOptions {
SlidingExpiration = TimeSpan.FromDays(30) //TODO: Make it configurable?
},
CancellationTokenProvider.Token
);
}
private async Task<bool> UpdateChangedFeatureGroupsAsync(
IEnumerable<FeatureGroupDefinitionRecord> featureGroupRecords)
{
var newRecords = new List<FeatureGroupDefinitionRecord>();
var changedRecords = new List<FeatureGroupDefinitionRecord>();
var featureGroupRecordsInDatabase = (await FeatureGroupRepository.GetListAsync())
.ToDictionary(x => x.Name);
foreach (var featureGroupRecord in featureGroupRecords)
{
var featureGroupRecordInDatabase = featureGroupRecordsInDatabase.GetOrDefault(featureGroupRecord.Name);
if (featureGroupRecordInDatabase == null)
{
/* New group */
newRecords.Add(featureGroupRecord);
continue;
}
if (featureGroupRecord.HasSameData(featureGroupRecordInDatabase))
{
/* Not changed */
continue;
}
/* Changed */
featureGroupRecordInDatabase.Patch(featureGroupRecord);
changedRecords.Add(featureGroupRecordInDatabase);
}
/* Deleted */
var deletedRecords = FeatureOptions.DeletedFeatureGroups.Any()
? featureGroupRecordsInDatabase.Values
.Where(x => FeatureOptions.DeletedFeatureGroups.Contains(x.Name))
.ToArray()
: Array.Empty<FeatureGroupDefinitionRecord>();
if (newRecords.Any())
{
await FeatureGroupRepository.InsertManyAsync(newRecords);
}
if (changedRecords.Any())
{
await FeatureGroupRepository.UpdateManyAsync(changedRecords);
}
if (deletedRecords.Any())
{
await FeatureGroupRepository.DeleteManyAsync(deletedRecords);
}
return newRecords.Any() || changedRecords.Any() || deletedRecords.Any();
}
private async Task<bool> UpdateChangedFeaturesAsync(
IEnumerable<FeatureDefinitionRecord> featureRecords)
{
var newRecords = new List<FeatureDefinitionRecord>();
var changedRecords = new List<FeatureDefinitionRecord>();
var featureRecordsInDatabase = (await FeatureRepository.GetListAsync())
.ToDictionary(x => x.Name);
foreach (var featureRecord in featureRecords)
{
var featureRecordInDatabase = featureRecordsInDatabase.GetOrDefault(featureRecord.Name);
if (featureRecordInDatabase == null)
{
/* New group */
newRecords.Add(featureRecord);
continue;
}
if (featureRecord.HasSameData(featureRecordInDatabase))
{
/* Not changed */
continue;
}
/* Changed */
featureRecordInDatabase.Patch(featureRecord);
changedRecords.Add(featureRecordInDatabase);
}
/* Deleted */
var deletedRecords = new List<FeatureDefinitionRecord>();
if (FeatureOptions.DeletedFeatures.Any())
{
deletedRecords.AddRange(
featureRecordsInDatabase.Values
.Where(x => FeatureOptions.DeletedFeatures.Contains(x.Name))
);
}
if (FeatureOptions.DeletedFeatureGroups.Any())
{
deletedRecords.AddIfNotContains(
featureRecordsInDatabase.Values
.Where(x => FeatureOptions.DeletedFeatureGroups.Contains(x.GroupName))
);
}
if (newRecords.Any())
{
await FeatureRepository.InsertManyAsync(newRecords);
}
if (changedRecords.Any())
{
await FeatureRepository.UpdateManyAsync(changedRecords);
}
if (deletedRecords.Any())
{
await FeatureRepository.DeleteManyAsync(deletedRecords);
}
return newRecords.Any() || changedRecords.Any() || deletedRecords.Any();
}
private string GetApplicationDistributedLockKey()
{
return $"{CacheOptions.KeyPrefix}_{ApplicationNameAccessor.ApplicationName}_AbpFeatureUpdateLock";
}
private string GetCommonDistributedLockKey()
{
return $"{CacheOptions.KeyPrefix}_Common_AbpFeatureUpdateLock";
}
private string GetApplicationHashCacheKey()
{
return $"{CacheOptions.KeyPrefix}_{ApplicationNameAccessor.ApplicationName}_AbpFeaturesHash";
}
private string GetCommonStampCacheKey()
{
return $"{CacheOptions.KeyPrefix}_AbpInMemoryFeatureCacheStamp";
}
private static string CalculateHash(
FeatureGroupDefinitionRecord[] featureGroupRecords,
FeatureDefinitionRecord[] featureRecords,
IEnumerable<string> deletedFeatureGroups,
IEnumerable<string> deletedFeatures)
{
var stringBuilder = new StringBuilder();
stringBuilder.Append("FeatureGroupRecords:");
stringBuilder.AppendLine(JsonSerializer.Serialize(featureGroupRecords));
stringBuilder.Append("FeatureRecords:");
stringBuilder.AppendLine(JsonSerializer.Serialize(featureRecords));
stringBuilder.Append("DeletedFeatureGroups:");
stringBuilder.AppendLine(deletedFeatureGroups.JoinAsString(","));
stringBuilder.Append("DeletedFeature:");
stringBuilder.Append(deletedFeatures.JoinAsString(","));
return stringBuilder
.ToString()
.ToMd5();
}
}

25
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/StringValueTypeSerializer.cs

@ -0,0 +1,25 @@
using Volo.Abp.DependencyInjection;
using Volo.Abp.Json;
using Volo.Abp.Validation.StringValues;
namespace Volo.Abp.FeatureManagement;
public class StringValueTypeSerializer : ITransientDependency
{
protected IJsonSerializer JsonSerializer { get; }
public StringValueTypeSerializer(IJsonSerializer jsonSerializer)
{
JsonSerializer = jsonSerializer;
}
public virtual string Serialize(IStringValueType stringValueType)
{
return JsonSerializer.Serialize(stringValueType);
}
public virtual IStringValueType Deserialize(string value)
{
return JsonSerializer.Deserialize<IStringValueType>(value);
}
}

6
modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/AbpFeatureManagementEntityFrameworkCoreModule.cs

@ -1,12 +1,14 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Json;
using Volo.Abp.Modularity;
namespace Volo.Abp.FeatureManagement.EntityFrameworkCore;
[DependsOn(
typeof(AbpFeatureManagementDomainModule),
typeof(AbpEntityFrameworkCoreModule)
typeof(AbpEntityFrameworkCoreModule),
typeof(AbpJsonModule)
)]
public class AbpFeatureManagementEntityFrameworkCoreModule : AbpModule
{
@ -14,6 +16,8 @@ public class AbpFeatureManagementEntityFrameworkCoreModule : AbpModule
{
context.Services.AddAbpDbContext<FeatureManagementDbContext>(options =>
{
options.AddRepository<FeatureGroupDefinitionRecord, EfCoreFeatureGroupDefinitionRecordRepository>();
options.AddRepository<FeatureDefinitionRecord, EfCoreFeatureDefinitionRecordRepository>();
options.AddDefaultRepositories<IFeatureManagementDbContext>();
options.AddRepository<FeatureValue, EfCoreFeatureValueRepository>();

16
modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/EfCoreFeatureDefinitionRecordRepository.cs

@ -0,0 +1,16 @@
using System;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
namespace Volo.Abp.FeatureManagement.EntityFrameworkCore;
public class EfCoreFeatureDefinitionRecordRepository :
EfCoreRepository<IFeatureManagementDbContext, FeatureDefinitionRecord, Guid>,
IFeatureDefinitionRecordRepository
{
public EfCoreFeatureDefinitionRecordRepository(
IDbContextProvider<IFeatureManagementDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
}

16
modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/EfCoreFeatureGroupDefinitionRecordRepository.cs

@ -0,0 +1,16 @@
using System;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
namespace Volo.Abp.FeatureManagement.EntityFrameworkCore;
public class EfCoreFeatureGroupDefinitionRecordRepository :
EfCoreRepository<IFeatureManagementDbContext, FeatureGroupDefinitionRecord, Guid>,
IFeatureGroupDefinitionRecordRepository
{
public EfCoreFeatureGroupDefinitionRecordRepository(
IDbContextProvider<IFeatureManagementDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
}

4
modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/FeatureManagementDbContext.cs

@ -9,6 +9,10 @@ namespace Volo.Abp.FeatureManagement.EntityFrameworkCore;
[ConnectionStringName(AbpFeatureManagementDbProperties.ConnectionStringName)]
public class FeatureManagementDbContext : AbpDbContext<FeatureManagementDbContext>, IFeatureManagementDbContext
{
public DbSet<FeatureGroupDefinitionRecord> FeatureGroups { get; set; }
public DbSet<FeatureDefinitionRecord> Features { get; set; }
public DbSet<FeatureValue> FeatureValues { get; set; }
public FeatureManagementDbContext(DbContextOptions<FeatureManagementDbContext> options)

36
modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/FeatureManagementDbContextModelCreatingExtensions.cs

@ -26,7 +26,41 @@ public static class FeatureManagementDbContextModelCreatingExtensions
b.Property(x => x.ProviderName).HasMaxLength(FeatureValueConsts.MaxProviderNameLength);
b.Property(x => x.ProviderKey).HasMaxLength(FeatureValueConsts.MaxProviderKeyLength);
b.HasIndex(x => new { x.Name, x.ProviderName, x.ProviderKey }).IsUnique(true);
b.HasIndex(x => new { x.Name, x.ProviderName, x.ProviderKey }).IsUnique();
b.ApplyObjectExtensionMappings();
});
builder.Entity<FeatureGroupDefinitionRecord>(b =>
{
b.ToTable(AbpFeatureManagementDbProperties.DbTablePrefix + "FeatureGroups", AbpFeatureManagementDbProperties.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Name).HasMaxLength(FeatureGroupDefinitionRecordConsts.MaxNameLength).IsRequired();
b.Property(x => x.DisplayName).HasMaxLength(FeatureGroupDefinitionRecordConsts.MaxDisplayNameLength).IsRequired();
b.HasIndex(x => new { x.Name }).IsUnique();
b.ApplyObjectExtensionMappings();
});
builder.Entity<FeatureDefinitionRecord>(b =>
{
b.ToTable(AbpFeatureManagementDbProperties.DbTablePrefix + "Features", AbpFeatureManagementDbProperties.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.GroupName).HasMaxLength(FeatureGroupDefinitionRecordConsts.MaxNameLength).IsRequired();
b.Property(x => x.Name).HasMaxLength(FeatureDefinitionRecordConsts.MaxNameLength).IsRequired();
b.Property(x => x.ParentName).HasMaxLength(FeatureDefinitionRecordConsts.MaxNameLength);
b.Property(x => x.DisplayName).HasMaxLength(FeatureDefinitionRecordConsts.MaxDisplayNameLength).IsRequired();
b.Property(x => x.Description).HasMaxLength(FeatureDefinitionRecordConsts.MaxDescriptionLength);
b.Property(x => x.DefaultValue).HasMaxLength(FeatureDefinitionRecordConsts.MaxDefaultValueLength);
b.Property(x => x.AllowedProviders).HasMaxLength(FeatureDefinitionRecordConsts.MaxAllowedProvidersLength);
b.Property(x => x.ValueType).HasMaxLength(FeatureDefinitionRecordConsts.MaxValueTypeLength);
b.HasIndex(x => new { x.Name }).IsUnique();
b.HasIndex(x => new { x.GroupName });
b.ApplyObjectExtensionMappings();
});

4
modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/IFeatureManagementDbContext.cs

@ -9,5 +9,9 @@ namespace Volo.Abp.FeatureManagement.EntityFrameworkCore;
[ConnectionStringName(AbpFeatureManagementDbProperties.ConnectionStringName)]
public interface IFeatureManagementDbContext : IEfCoreDbContext
{
DbSet<FeatureGroupDefinitionRecord> FeatureGroups { get; }
DbSet<FeatureDefinitionRecord> Features { get; }
DbSet<FeatureValue> FeatureValues { get; }
}

2
modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/AbpFeatureManagementHttpApiModule.cs

@ -32,7 +32,7 @@ public class AbpFeatureManagementHttpApiModule : AbpModule
.AddBaseTypes(typeof(AbpUiResource));
});
var contractsOptions = context.Services.ExecutePreConfiguredActions<AbpFeatureManagementApplicationContractsOptions>();
var contractsOptions = context.Services.ExecutePreConfiguredActions<ValueValidatorFactoryOptions>();
Configure<JsonOptions>(options =>
{
options.JsonSerializerOptions.Converters.AddIfNotContains(new StringValueTypeJsonConverter(contractsOptions));

2
modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs

@ -20,7 +20,7 @@ public abstract class StringValueJsonConverter_Tests : FeatureManagementApplicat
protected override void BeforeAddApplication(IServiceCollection services)
{
services.PreConfigure<AbpFeatureManagementApplicationContractsOptions>(options =>
services.PreConfigure<ValueValidatorFactoryOptions>(options =>
{
options.ValueValidatorFactory.Add(new ValueValidatorFactory<UrlValueValidator>("URL"));
});

Loading…
Cancel
Save