diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs index 85c9b86a3d..0deae4b5b3 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs @@ -308,7 +308,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) { diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/AbpPermissionOptions.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/AbpPermissionOptions.cs index 17ff28798e..4bdd7c0bb8 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/AbpPermissionOptions.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/Permissions/AbpPermissionOptions.cs @@ -1,4 +1,5 @@ -using Volo.Abp.Collections; +using System.Collections.Generic; +using Volo.Abp.Collections; namespace Volo.Abp.Authorization.Permissions; @@ -8,9 +9,16 @@ public class AbpPermissionOptions public ITypeList ValueProviders { get; } + public HashSet DeletedPermissions { get; } + + public HashSet DeletedPermissionGroups { get; } + public AbpPermissionOptions() { DefinitionProviders = new TypeList(); ValueProviders = new TypeList(); + + DeletedPermissions = new HashSet(); + DeletedPermissionGroups = new HashSet(); } } diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeatureOptions.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeatureOptions.cs index 22c1067082..87ff7716db 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeatureOptions.cs +++ b/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 ValueProviders { get; } + public HashSet DeletedFeatures { get; } + + public HashSet DeletedFeatureGroups { get; } + public AbpFeatureOptions() { DefinitionProviders = new TypeList(); ValueProviders = new TypeList(); + + DeletedFeatures = new HashSet(); + DeletedFeatureGroups = new HashSet(); } } diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureChecker.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureChecker.cs index 580881ce84..9554860078 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureChecker.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureChecker.cs @@ -37,7 +37,7 @@ public class FeatureChecker : FeatureCheckerBase public override async Task GetOrNullAsync(string name) { - var featureDefinition = FeatureDefinitionManager.Get(name); + var featureDefinition = await FeatureDefinitionManager.GetAsync(name); var providers = Enumerable .Reverse(Providers); diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs index 3123faefc2..5ea7377fd7 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs +++ b/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 { /// /// 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}]"; diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs index c39db18771..d0293d5859 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs +++ b/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 FeatureGroupDefinitions => _lazyFeatureGroupDefinitions.Value; - private readonly Lazy> _lazyFeatureGroupDefinitions; - - protected IDictionary FeatureDefinitions => _lazyFeatureDefinitions.Value; - private readonly Lazy> _lazyFeatureDefinitions; - - protected AbpFeatureOptions Options { get; } - - private readonly IServiceScopeFactory _serviceScopeFactory; + protected IStaticFeatureDefinitionStore StaticStore; + protected IDynamicFeatureDefinitionStore DynamicStore; public FeatureDefinitionManager( - IOptions options, - IServiceScopeFactory serviceScopeFactory) + IStaticFeatureDefinitionStore staticStore, + IDynamicFeatureDefinitionStore dynamicStore) { - _serviceScopeFactory = serviceScopeFactory; - Options = options.Value; - - _lazyFeatureDefinitions = new Lazy>( - CreateFeatureDefinitions, - isThreadSafe: true - ); - - _lazyFeatureGroupDefinitions = new Lazy>( - CreateFeatureGroupDefinitions, - isThreadSafe: true - ); + StaticStore = staticStore; + DynamicStore = dynamicStore; } - public virtual FeatureDefinition Get(string name) + public virtual async Task 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 GetAll() - { - return FeatureDefinitions.Values.ToImmutableList(); - } - - public virtual FeatureDefinition GetOrNull(string name) - { - return FeatureDefinitions.GetOrDefault(name); - } - - public IReadOnlyList GetGroups() - { - return FeatureGroupDefinitions.Values.ToImmutableList(); + return permission; } - protected virtual Dictionary CreateFeatureDefinitions() + public virtual async Task GetOrNullAsync(string name) { - var features = new Dictionary(); - - 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 features, - FeatureDefinition feature) + public virtual async Task> 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 CreateFeatureGroupDefinitions() + public virtual async Task> 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(); } } diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureGroupDefinition.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureGroupDefinition.cs index cba2e06584..e584a95c50 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureGroupDefinition.cs +++ b/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 { /// /// 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 GetFeaturesWithChildren() { var features = new List(); diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/ICanCreateChildFeature.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/ICanCreateChildFeature.cs new file mode 100644 index 0000000000..ecba11e31e --- /dev/null +++ b/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); +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IDynamicFeatureDefinitionStore.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IDynamicFeatureDefinitionStore.cs new file mode 100644 index 0000000000..90fe14a5e0 --- /dev/null +++ b/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 GetOrNullAsync(string name); + + Task> GetFeaturesAsync(); + + Task> GetGroupsAsync(); +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs index ebef29b64a..f0201937df 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs +++ b/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 GetAsync([NotNull] string name); - IReadOnlyList GetAll(); + Task> GetAllAsync(); - FeatureDefinition GetOrNull(string name); + Task GetOrNullAsync(string name); - IReadOnlyList GetGroups(); + Task> GetGroupsAsync(); } diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IStaticFeatureDefinitionStore.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IStaticFeatureDefinitionStore.cs new file mode 100644 index 0000000000..586747904e --- /dev/null +++ b/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 GetOrNullAsync(string name); + + Task> GetFeaturesAsync(); + + Task> GetGroupsAsync(); +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/NullDynamicFeatureDefinitionStore.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/NullDynamicFeatureDefinitionStore.cs new file mode 100644 index 0000000000..63e7b476c4 --- /dev/null +++ b/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 CachedFeatureResult = Task.FromResult((FeatureDefinition)null); + + private static readonly Task> CachedFeaturesResult = + Task.FromResult((IReadOnlyList)Array.Empty().ToImmutableList()); + + private static readonly Task> CachedGroupsResult = + Task.FromResult((IReadOnlyList)Array.Empty().ToImmutableList()); + + public Task GetOrNullAsync(string name) + { + return CachedFeatureResult; + } + + public Task> GetFeaturesAsync() + { + return CachedFeaturesResult; + } + + public Task> GetGroupsAsync() + { + return CachedGroupsResult; + } +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/StaticFeatureDefinitionStore.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/StaticFeatureDefinitionStore.cs new file mode 100644 index 0000000000..7d683d6caf --- /dev/null +++ b/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 FeatureGroupDefinitions => _lazyFeatureGroupDefinitions.Value; + private readonly Lazy> _lazyFeatureGroupDefinitions; + + protected IDictionary FeatureDefinitions => _lazyFeatureDefinitions.Value; + private readonly Lazy> _lazyFeatureDefinitions; + + protected AbpFeatureOptions Options { get; } + + private readonly IServiceProvider _serviceProvider; + + public StaticFeatureDefinitionStore( + IOptions options, + IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + Options = options.Value; + + _lazyFeatureDefinitions = new Lazy>( + CreateFeatureDefinitions, + isThreadSafe: true + ); + + _lazyFeatureGroupDefinitions = new Lazy>( + CreateFeatureGroupDefinitions, + isThreadSafe: true + ); + } + + public virtual async Task 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 CreateFeatureDefinitions() + { + var features = new Dictionary(); + + foreach (var groupDefinition in FeatureGroupDefinitions.Values) + { + foreach (var feature in groupDefinition.Features) + { + AddFeatureToDictionaryRecursively(features, feature); + } + } + + return features; + } + + protected virtual void AddFeatureToDictionaryRecursively( + Dictionary 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 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 GetOrNullAsync(string name) + { + return Task.FromResult(FeatureDefinitions.GetOrDefault(name)); + } + + public virtual Task> GetFeaturesAsync() + { + return Task.FromResult>(FeatureDefinitions.Values.ToList()); + } + + public virtual Task> GetGroupsAsync() + { + return Task.FromResult>(FeatureGroupDefinitions.Values.ToList()); + } +} diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizableStringSerializer.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizableStringSerializer.cs index da431bca65..5006d62ea8 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizableStringSerializer.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizableStringSerializer.cs @@ -15,6 +15,11 @@ public class LocalizableStringSerializer : ILocalizableStringSerializer, ITransi public virtual string Serialize(ILocalizableString localizableString) { + if (localizableString == null) + { + return null; + } + if (localizableString is LocalizableString realLocalizableString) { return $"L:{realLocalizableString.ResourceName},{realLocalizableString.Name}"; @@ -36,7 +41,7 @@ public class LocalizableStringSerializer : ILocalizableStringSerializer, ITransi { return new FixedLocalizableString(value); } - + var type = value[0]; switch (type) { @@ -48,7 +53,7 @@ public class LocalizableStringSerializer : ILocalizableStringSerializer, ITransi { throw new AbpException("Invalid LocalizableString value: " + value); } - + var resourceName = value.Substring(2, commaPosition - 2); var name = value.Substring(commaPosition + 1); if (name.IsNullOrWhiteSpace()) @@ -61,4 +66,4 @@ public class LocalizableStringSerializer : ILocalizableStringSerializer, ITransi return new FixedLocalizableString(value); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/StaticPermissionDefinitionStore_Tests.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/StaticPermissionDefinitionStore_Tests.cs new file mode 100644 index 0000000000..d3a484c32f --- /dev/null +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/StaticPermissionDefinitionStore_Tests.cs @@ -0,0 +1,47 @@ +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Authorization.Permissions; +using Xunit; + +namespace Volo.Abp.Authorization; + +public class StaticPermissionDefinitionStore_Tests : AuthorizationTestBase +{ + private readonly IStaticPermissionDefinitionStore _store; + + public StaticPermissionDefinitionStore_Tests() + { + _store = GetRequiredService(); + } + + [Fact] + public async Task GetOrNullAsync() + { + var permission = await _store.GetOrNullAsync("MyPermission1"); + permission.ShouldNotBeNull(); + permission.Name.ShouldBe("MyPermission1"); + permission.StateCheckers.ShouldContain(x => x.GetType() == typeof(TestRequireEditionPermissionSimpleStateChecker)); + + permission = await _store.GetOrNullAsync("NotExists"); + permission.ShouldBeNull(); + } + + [Fact] + public async Task GetPermissionsAsync() + { + var permissions = await _store.GetPermissionsAsync(); + permissions.ShouldContain(x => x.Name == "MyAuthorizedService1"); + permissions.ShouldContain(x => x.Name == "MyPermission1"); + permissions.ShouldContain(x => x.Name == "MyPermission2"); + permissions.ShouldContain(x => x.Name == "MyPermission3"); + permissions.ShouldContain(x => x.Name == "MyPermission4"); + permissions.ShouldContain(x => x.Name == "MyPermission5"); + } + + [Fact] + public async Task GetGroupsAsync() + { + var groups = await _store.GetGroupsAsync(); + groups.ShouldNotContain(x => x.Name == "TestGetGroup"); + } +} diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureDefinitionManager_Tests.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureDefinitionManager_Tests.cs index 246ffe537d..5e3cb96741 100644 --- a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureDefinitionManager_Tests.cs +++ b/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(() => + (await _featureDefinitionManager.GetOrNullAsync("UndefinedFeature")).ShouldBeNull(); + await Assert.ThrowsAsync(async () => { - _featureDefinitionManager.Get("UndefinedFeature"); + await _featureDefinitionManager.GetAsync("UndefinedFeature"); }); } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/AbpFeatureManagementApplicationContractsModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/AbpFeatureManagementApplicationContractsModule.cs index cfbcf1c95e..a69cb893d0 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/AbpFeatureManagementApplicationContractsModule.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/AbpFeatureManagementApplicationContractsModule.cs @@ -1,11 +1,6 @@ -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Application; +using Volo.Abp.Application; using Volo.Abp.Authorization; -using Volo.Abp.FeatureManagement.JsonConverters; using Volo.Abp.Json; -using Volo.Abp.Json.Newtonsoft; -using Volo.Abp.Json.SystemTextJson; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; @@ -25,21 +20,5 @@ public class AbpFeatureManagementApplicationContractsModule : AbpModule { options.FileSets.AddEmbedded(); }); - - var contractsOptionsActions = context.Services.GetPreConfigureActions(); - Configure(options => - { - contractsOptionsActions.Configure(options); - }); - - Configure(options => - { - options.Converters.Add(); - }); - - Configure(options => - { - options.JsonSerializerOptions.Converters.AddIfNotContains(new StringValueTypeJsonConverter(contractsOptionsActions.Configure())); - }); } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs index 095840655c..c21f40fb65 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs @@ -5,8 +5,8 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Options; -using Volo.Abp.Authorization.Permissions; using Volo.Abp.Features; +using Volo.Abp.Localization; namespace Volo.Abp.FeatureManagement; @@ -35,14 +35,9 @@ public class FeatureAppService : FeatureManagementAppServiceBase, IFeatureAppSer Groups = new List() }; - foreach (var group in FeatureDefinitionManager.GetGroups()) + foreach (var group in await FeatureDefinitionManager.GetGroupsAsync()) { - var groupDto = new FeatureGroupDto - { - Name = group.Name, - DisplayName = group.DisplayName.Localize(StringLocalizerFactory), - Features = new List() - }; + var groupDto = CreateFeatureGroupDto(group); foreach (var featureDefinition in group.GetFeaturesWithChildren()) { @@ -55,20 +50,7 @@ public class FeatureAppService : FeatureManagementAppServiceBase, IFeatureAppSer } var feature = await FeatureManager.GetOrNullWithProviderAsync(featureDefinition.Name, providerName, providerKey); - groupDto.Features.Add(new FeatureDto - { - Name = featureDefinition.Name, - DisplayName = featureDefinition.DisplayName?.Localize(StringLocalizerFactory), - ValueType = featureDefinition.ValueType, - Description = featureDefinition.Description?.Localize(StringLocalizerFactory), - ParentName = featureDefinition.Parent?.Name, - Value = feature.Value, - Provider = new FeatureProviderDto - { - Name = feature.Provider?.Name, - Key = feature.Provider?.Key - } - }); + groupDto.Features.Add(CreateFeatureDto(feature, featureDefinition)); } SetFeatureDepth(groupDto.Features, providerName, providerKey); @@ -82,6 +64,36 @@ public class FeatureAppService : FeatureManagementAppServiceBase, IFeatureAppSer return result; } + private FeatureGroupDto CreateFeatureGroupDto(FeatureGroupDefinition groupDefinition) + { + return new FeatureGroupDto + { + Name = groupDefinition.Name, + DisplayName = groupDefinition.DisplayName?.Localize(StringLocalizerFactory), + Features = new List() + }; + } + + private FeatureDto CreateFeatureDto(FeatureNameValueWithGrantedProvider featureNameValueWithGrantedProvider, FeatureDefinition featureDefinition) + { + return new FeatureDto + { + Name = featureDefinition.Name, + DisplayName = featureDefinition.DisplayName?.Localize(StringLocalizerFactory), + Description = featureDefinition.Description?.Localize(StringLocalizerFactory), + + ValueType = featureDefinition.ValueType, + + ParentName = featureDefinition.Parent?.Name, + Value = featureNameValueWithGrantedProvider.Value, + Provider = new FeatureProviderDto + { + Name = featureNameValueWithGrantedProvider.Provider?.Name, + Key = featureNameValueWithGrantedProvider.Provider?.Key + } + }; + } + public virtual async Task UpdateAsync([NotNull] string providerName, string providerKey, UpdateFeaturesDto input) { await CheckProviderPolicy(providerName, providerKey); diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs index f63dc6f37f..095db9d12b 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs @@ -50,9 +50,9 @@ public partial class FeatureManagementModal ToggleValues = new Dictionary(); SelectionStringValues = new Dictionary(); - Groups = (await FeatureAppService.GetAsync(ProviderName, ProviderKey))?.Groups; + var result = await FeatureAppService.GetAsync(ProviderName, ProviderKey); - Groups ??= new List(); + Groups = result?.Groups ?? new List(); if (Groups.Any()) { diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo.Abp.FeatureManagement.Domain.Shared.csproj b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo.Abp.FeatureManagement.Domain.Shared.csproj index 20e31fe161..9f1260ee56 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo.Abp.FeatureManagement.Domain.Shared.csproj +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo.Abp.FeatureManagement.Domain.Shared.csproj @@ -16,6 +16,7 @@ + diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainSharedModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainSharedModule.cs index 303f4cd13f..5d924f1457 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainSharedModule.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainSharedModule.cs @@ -1,4 +1,11 @@ -using Volo.Abp.FeatureManagement.Localization; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.FeatureManagement.JsonConverters; +using Volo.Abp.FeatureManagement.Localization; +using Volo.Abp.Json; +using Volo.Abp.Json.Newtonsoft; +using Volo.Abp.Json.SystemTextJson; using Volo.Abp.Localization; using Volo.Abp.Localization.ExceptionHandling; using Volo.Abp.Modularity; @@ -9,8 +16,9 @@ using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.FeatureManagement; [DependsOn( - typeof(AbpValidationModule) - )] + typeof(AbpValidationModule), + typeof(AbpJsonModule) +)] public class AbpFeatureManagementDomainSharedModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) @@ -33,5 +41,21 @@ public class AbpFeatureManagementDomainSharedModule : AbpModule { options.MapCodeNamespace("Volo.Abp.FeatureManagement", typeof(AbpFeatureManagementResource)); }); + + Configure(options => + { + options.Converters.Add(); + }); + + var valueValidatorFactoryOptions = context.Services.GetPreConfigureActions(); + Configure(options => + { + valueValidatorFactoryOptions.Configure(options); + }); + + Configure(options => + { + options.JsonSerializerOptions.Converters.Add(new StringValueTypeJsonConverter(valueValidatorFactoryOptions.Configure())); + }); } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/FeatureDefinitionRecordConsts.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/FeatureDefinitionRecordConsts.cs new file mode 100644 index 0000000000..4088424774 --- /dev/null +++ b/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; +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/FeatureGroupDefinitionRecordConsts.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/FeatureGroupDefinitionRecordConsts.cs new file mode 100644 index 0000000000..e90fdd9479 --- /dev/null +++ b/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; +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureValueConsts.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/FeatureValueConsts.cs similarity index 100% rename from modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureValueConsts.cs rename to modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/FeatureValueConsts.cs diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/IValueValidatorFactory.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/IValueValidatorFactory.cs similarity index 100% rename from modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/IValueValidatorFactory.cs rename to modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/IValueValidatorFactory.cs diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/NewtonsoftStringValueTypeJsonConverter.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/NewtonsoftStringValueTypeJsonConverter.cs similarity index 94% rename from modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/NewtonsoftStringValueTypeJsonConverter.cs rename to modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/NewtonsoftStringValueTypeJsonConverter.cs index aa9a913718..2bb3e1fd2d 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/NewtonsoftStringValueTypeJsonConverter.cs +++ b/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 options) + public NewtonsoftStringValueTypeJsonConverter(IOptions options) { Options = options.Value; } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/SelectionStringValueItemSourceJsonConverter.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/SelectionStringValueItemSourceJsonConverter.cs similarity index 100% rename from modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/SelectionStringValueItemSourceJsonConverter.cs rename to modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/SelectionStringValueItemSourceJsonConverter.cs diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/StringValueTypeJsonConverter.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/StringValueTypeJsonConverter.cs similarity index 92% rename from modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/StringValueTypeJsonConverter.cs rename to modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/StringValueTypeJsonConverter.cs index ca91efd58a..fd463866d6 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/StringValueTypeJsonConverter.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/StringValueTypeJsonConverter.cs @@ -12,9 +12,9 @@ public class StringValueTypeJsonConverter : JsonConverter private JsonSerializerOptions _writeJsonSerializerOptions; - protected readonly AbpFeatureManagementApplicationContractsOptions Options; + protected readonly ValueValidatorFactoryOptions Options; - public StringValueTypeJsonConverter(AbpFeatureManagementApplicationContractsOptions options) + public StringValueTypeJsonConverter(ValueValidatorFactoryOptions options) { Options = options; } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorFactory.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorFactory.cs similarity index 100% rename from modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorFactory.cs rename to modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorFactory.cs diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorJsonConverter.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorJsonConverter.cs similarity index 93% rename from modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorJsonConverter.cs rename to modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorJsonConverter.cs index 69f954f4ee..b8a4387de9 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorJsonConverter.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorJsonConverter.cs @@ -14,9 +14,9 @@ public class ValueValidatorJsonConverter : JsonConverter private JsonSerializerOptions _writeJsonSerializerOptions; - protected readonly AbpFeatureManagementApplicationContractsOptions Options; + protected readonly ValueValidatorFactoryOptions Options; - public ValueValidatorJsonConverter(AbpFeatureManagementApplicationContractsOptions options) + public ValueValidatorJsonConverter(ValueValidatorFactoryOptions options) { Options = options; } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/AbpFeatureManagementApplicationContractsOptions.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/ValueValidatorFactoryOptions.cs similarity index 82% rename from modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/AbpFeatureManagementApplicationContractsOptions.cs rename to modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/ValueValidatorFactoryOptions.cs index 6c99bb648f..110e4752c6 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/AbpFeatureManagementApplicationContractsOptions.cs +++ b/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 ValueValidatorFactory { get; } - - public AbpFeatureManagementApplicationContractsOptions() + + public ValueValidatorFactoryOptions() { - ValueValidatorFactory = new HashSet + ValueValidatorFactory = new HashSet { new ValueValidatorFactory("NULL"), new ValueValidatorFactory("BOOLEAN"), diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo.Abp.FeatureManagement.Domain.csproj b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo.Abp.FeatureManagement.Domain.csproj index 10033847c2..2e5185c28f 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo.Abp.FeatureManagement.Domain.csproj +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo.Abp.FeatureManagement.Domain.csproj @@ -18,4 +18,8 @@ + + + + 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 68cf94d334..74cd00cffb 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 @@ -1,8 +1,18 @@ -using Volo.Abp.Caching; +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Polly; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; using Volo.Abp.FeatureManagement.Localization; using Volo.Abp.Features; using Volo.Abp.Localization.ExceptionHandling; using Volo.Abp.Modularity; +using Volo.Abp.Threading; namespace Volo.Abp.FeatureManagement; @@ -30,4 +40,130 @@ public class AbpFeatureManagementDomainModule : AbpModule options.MapCodeNamespace("AbpFeatureManagement", typeof(AbpFeatureManagementResource)); }); } + + private readonly CancellationTokenSource _cancellationTokenSource = new(); + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context)); + } + + public override Task OnApplicationInitializationAsync(ApplicationInitializationContext context) + { + InitializeDynamicFeatures(context); + return Task.CompletedTask; + } + + public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context) + { + _cancellationTokenSource.Cancel(); + return Task.CompletedTask; + } + + private void InitializeDynamicFeatures(ApplicationInitializationContext context) + { + var options = context + .ServiceProvider + .GetRequiredService>() + .Value; + + if (!options.SaveStaticFeaturesToDatabase && !options.IsDynamicFeatureStoreEnabled) + { + return; + } + + var rootServiceProvider = context.ServiceProvider.GetRequiredService(); + + Task.Run(async () => + { + using var scope = rootServiceProvider.CreateScope(); + var applicationLifetime = scope.ServiceProvider.GetService(); + var cancellationTokenProvider = scope.ServiceProvider.GetRequiredService(); + var cancellationToken = applicationLifetime?.ApplicationStopping ?? _cancellationTokenSource.Token; + + try + { + using (cancellationTokenProvider.Use(cancellationToken)) + { + if (cancellationTokenProvider.Token.IsCancellationRequested) + { + return; + } + + await SaveStaticFeaturesToDatabaseAsync(options, scope, cancellationTokenProvider); + + if (cancellationTokenProvider.Token.IsCancellationRequested) + { + return; + } + + await PreCacheDynamicFeaturesAsync(options, scope); + } + } + // ReSharper disable once EmptyGeneralCatchClause (No need to log since it is logged above) + catch { } + }); + } + + private static async Task SaveStaticFeaturesToDatabaseAsync( + FeatureManagementOptions options, + IServiceScope scope, + ICancellationTokenProvider cancellationTokenProvider) + { + if (!options.SaveStaticFeaturesToDatabase) + { + return; + } + + await Policy + .Handle() + .WaitAndRetryAsync(8, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt) * 10)) + .ExecuteAsync(async _ => + { + try + { + // ReSharper disable once AccessToDisposedClosure + await scope + .ServiceProvider + .GetRequiredService() + .SaveAsync(); + } + catch (Exception ex) + { + // ReSharper disable once AccessToDisposedClosure + scope.ServiceProvider + .GetService>()? + .LogException(ex); + + throw; // Polly will catch it + } + }, cancellationTokenProvider.Token); + } + + private static async Task PreCacheDynamicFeaturesAsync(FeatureManagementOptions options, IServiceScope scope) + { + if (!options.IsDynamicFeatureStoreEnabled) + { + return; + } + + try + { + // Pre-cache features, so first request doesn't wait + await scope + .ServiceProvider + .GetRequiredService() + .GetGroupsAsync(); + } + catch (Exception ex) + { + // ReSharper disable once AccessToDisposedClosure + scope + .ServiceProvider + .GetService>()? + .LogException(ex); + + throw; // It will be cached in InitializeDynamicFeatures + } + } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/DynamicFeatureDefinitionStore.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/DynamicFeatureDefinitionStore.cs new file mode 100644 index 0000000000..d8c3bca255 --- /dev/null +++ b/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 cacheOptions, + IOptions 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 GetOrNullAsync(string name) + { + if (!FeatureManagementOptions.IsDynamicFeatureStoreEnabled) + { + return null; + } + + using (await StoreCache.SyncSemaphore.LockAsync()) + { + await EnsureCacheIsUptoDateAsync(); + return StoreCache.GetFeatureOrNull(name); + } + } + + public virtual async Task> GetFeaturesAsync() + { + if (!FeatureManagementOptions.IsDynamicFeatureStoreEnabled) + { + return Array.Empty(); + } + + using (await StoreCache.SyncSemaphore.LockAsync()) + { + await EnsureCacheIsUptoDateAsync(); + return StoreCache.GetFeatures().ToImmutableList(); + } + } + + public virtual async Task> GetGroupsAsync() + { + if (!FeatureManagementOptions.IsDynamicFeatureStoreEnabled) + { + return Array.Empty(); + } + + 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 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"; + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/DynamicFeatureDefinitionStoreInMemoryCache.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/DynamicFeatureDefinitionStoreInMemoryCache.cs new file mode 100644 index 0000000000..cefa0b3ade --- /dev/null +++ b/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 FeatureGroupDefinitions { get; } + protected IDictionary 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(); + FeatureDefinitions = new Dictionary(); + } + + public Task FillAsync( + List featureGroupRecords, + List 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 GetFeatures() + { + return FeatureDefinitions.Values.ToList(); + } + + public IReadOnlyList GetGroups() + { + return FeatureGroupDefinitions.Values.ToList(); + } + + private void AddFeatureRecursively(ICanCreateChildFeature featureContainer, + FeatureDefinitionRecord featureRecord, + List 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); + } + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDefinitionRecord.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDefinitionRecord.cs new file mode 100644 index 0000000000..618f52d7a1 --- /dev/null +++ b/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, 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; } + + /// + /// Comma separated list of provider names. + /// + public string AllowedProviders { get; set; } + + /// + /// Serialized string to store info about the ValueType. + /// + 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); + } + } + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDefinitionSerializer.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDefinitionSerializer.cs new file mode 100644 index 0000000000..70a2503b9c --- /dev/null +++ b/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 featureGroups) + { + var featureGroupRecords = new List(); + var featureRecords = new List(); + + 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 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 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 providers) + { + return providers.Any() + ? providers.JoinAsString(",") + : null; + } + + protected virtual string SerializeStringValueType(IStringValueType stringValueType) + { + return StringValueTypeSerializer.Serialize(stringValueType); + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureGroupDefinitionRecord.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureGroupDefinitionRecord.cs new file mode 100644 index 0000000000..c40f74c2cd --- /dev/null +++ b/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, 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); + } + } + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementOptions.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementOptions.cs index b04f4fbef2..a3bf5eefc5 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementOptions.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementOptions.cs @@ -9,6 +9,16 @@ public class FeatureManagementOptions public Dictionary ProviderPolicies { get; } + /// + /// Default: true. + /// + public bool SaveStaticFeaturesToDatabase { get; set; } = true; + + /// + /// Default: false. + /// + public bool IsDynamicFeatureStoreEnabled { get; set; } + public FeatureManagementOptions() { Providers = new TypeList(); diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs index 57db058216..59a0085d4c 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs +++ b/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); 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 36800a9a33..2c255071f7 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 @@ -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); diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IDynamicFeatureDefinitionStoreInMemoryCache.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IDynamicFeatureDefinitionStoreInMemoryCache.cs new file mode 100644 index 0000000000..b51b63d1f8 --- /dev/null +++ b/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 featureGroupRecords, + List featureRecords); + + FeatureDefinition GetFeatureOrNull(string name); + + IReadOnlyList GetFeatures(); + + IReadOnlyList GetGroups(); +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureDefinitionRecordRepository.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureDefinitionRecordRepository.cs new file mode 100644 index 0000000000..d8718903c9 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureDefinitionRecordRepository.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; + +namespace Volo.Abp.FeatureManagement; + +public interface IFeatureDefinitionRecordRepository : IBasicRepository +{ + Task FindByNameAsync( + string name, + CancellationToken cancellationToken = default); +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureDefinitionSerializer.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureDefinitionSerializer.cs new file mode 100644 index 0000000000..96a8f542ab --- /dev/null +++ b/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 featureGroups); + + Task SerializeAsync(FeatureGroupDefinition featureGroup); + + Task SerializeAsync(FeatureDefinition feature, [CanBeNull] FeatureGroupDefinition featureGroup); +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureGroupDefinitionRecordRepository.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IFeatureGroupDefinitionRecordRepository.cs new file mode 100644 index 0000000000..e48e8bedc2 --- /dev/null +++ b/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 +{ + +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IStaticFeatureSaver.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/IStaticFeatureSaver.cs new file mode 100644 index 0000000000..d72d48525e --- /dev/null +++ b/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(); +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/StaticFeatureSaver.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/StaticFeatureSaver.cs new file mode 100644 index 0000000000..8786f9ddb6 --- /dev/null +++ b/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 cacheOptions, + IApplicationNameAccessor applicationNameAccessor, + IAbpDistributedLock distributedLock, + IOptions 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 UpdateChangedFeatureGroupsAsync( + IEnumerable featureGroupRecords) + { + var newRecords = new List(); + var changedRecords = new List(); + + 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(); + + 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 UpdateChangedFeaturesAsync( + IEnumerable featureRecords) + { + var newRecords = new List(); + var changedRecords = new List(); + + 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(); + + 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 deletedFeatureGroups, + IEnumerable 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(); + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/StringValueTypeSerializer.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/StringValueTypeSerializer.cs new file mode 100644 index 0000000000..f16ffda192 --- /dev/null +++ b/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(value); + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/AbpFeatureManagementEntityFrameworkCoreModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/AbpFeatureManagementEntityFrameworkCoreModule.cs index 716410022f..e8274b4a28 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/AbpFeatureManagementEntityFrameworkCoreModule.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/AbpFeatureManagementEntityFrameworkCoreModule.cs @@ -14,6 +14,8 @@ public class AbpFeatureManagementEntityFrameworkCoreModule : AbpModule { context.Services.AddAbpDbContext(options => { + options.AddRepository(); + options.AddRepository(); options.AddDefaultRepositories(); options.AddRepository(); diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/EfCoreFeatureDefinitionRecordRepository.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/EfCoreFeatureDefinitionRecordRepository.cs new file mode 100644 index 0000000000..3547b9c009 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/EfCoreFeatureDefinitionRecordRepository.cs @@ -0,0 +1,27 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace Volo.Abp.FeatureManagement.EntityFrameworkCore; + +public class EfCoreFeatureDefinitionRecordRepository : + EfCoreRepository, + IFeatureDefinitionRecordRepository +{ + public EfCoreFeatureDefinitionRecordRepository( + IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + public virtual async Task FindByNameAsync(string name, CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .OrderBy(x => x.Id) + .FirstOrDefaultAsync(r => r.Name == name, cancellationToken); + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/EfCoreFeatureGroupDefinitionRecordRepository.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/EfCoreFeatureGroupDefinitionRecordRepository.cs new file mode 100644 index 0000000000..81b37e8452 --- /dev/null +++ b/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, + IFeatureGroupDefinitionRecordRepository +{ + public EfCoreFeatureGroupDefinitionRecordRepository( + IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/FeatureManagementDbContext.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/FeatureManagementDbContext.cs index 1999a11984..0bc5fdee54 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/FeatureManagementDbContext.cs +++ b/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, IFeatureManagementDbContext { + public DbSet FeatureGroups { get; set; } + + public DbSet Features { get; set; } + public DbSet FeatureValues { get; set; } public FeatureManagementDbContext(DbContextOptions options) diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/FeatureManagementDbContextModelCreatingExtensions.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/FeatureManagementDbContextModelCreatingExtensions.cs index 97a9ba83d7..1ed23241dc 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/FeatureManagementDbContextModelCreatingExtensions.cs +++ b/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(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(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(); }); diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/IFeatureManagementDbContext.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/IFeatureManagementDbContext.cs index 7848f56cab..dce762b2f2 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/IFeatureManagementDbContext.cs +++ b/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 FeatureGroups { get; } + + DbSet Features { get; } + DbSet FeatureValues { get; } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/AbpFeatureManagementHttpApiModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/AbpFeatureManagementHttpApiModule.cs index 2a0b33d818..8efb3e6903 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/AbpFeatureManagementHttpApiModule.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/AbpFeatureManagementHttpApiModule.cs @@ -32,10 +32,10 @@ public class AbpFeatureManagementHttpApiModule : AbpModule .AddBaseTypes(typeof(AbpUiResource)); }); - var contractsOptions = context.Services.ExecutePreConfiguredActions(); + var valueValidatorFactoryOptions = context.Services.ExecutePreConfiguredActions(); Configure(options => { - options.JsonSerializerOptions.Converters.AddIfNotContains(new StringValueTypeJsonConverter(contractsOptions)); + options.JsonSerializerOptions.Converters.AddIfNotContains(new StringValueTypeJsonConverter(valueValidatorFactoryOptions)); }); } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/AbpFeatureManagementMongoDbModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/AbpFeatureManagementMongoDbModule.cs index 45d3d37271..cf1f917115 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/AbpFeatureManagementMongoDbModule.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/AbpFeatureManagementMongoDbModule.cs @@ -16,6 +16,8 @@ public class AbpFeatureManagementMongoDbModule : AbpModule { options.AddDefaultRepositories(); + options.AddRepository(); + options.AddRepository(); options.AddRepository(); }); } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/FeatureManagementMongoDbContext.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/FeatureManagementMongoDbContext.cs index 37273a3dcc..7709276746 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/FeatureManagementMongoDbContext.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/FeatureManagementMongoDbContext.cs @@ -9,6 +9,8 @@ namespace Volo.Abp.FeatureManagement.MongoDB; [ConnectionStringName(AbpFeatureManagementDbProperties.ConnectionStringName)] public class FeatureManagementMongoDbContext : AbpMongoDbContext, IFeatureManagementMongoDbContext { + public IMongoCollection FeatureGroups => Collection(); + public IMongoCollection Features => Collection(); public IMongoCollection FeatureValues => Collection(); protected override void CreateModel(IMongoModelBuilder modelBuilder) diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/FeatureManagementMongoDbContextExtensions.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/FeatureManagementMongoDbContextExtensions.cs index 8d263bb71e..bb1e035659 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/FeatureManagementMongoDbContextExtensions.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/FeatureManagementMongoDbContextExtensions.cs @@ -9,6 +9,16 @@ public static class FeatureManagementMongoDbContextExtensions { Check.NotNull(builder, nameof(builder)); + builder.Entity(b => + { + b.CollectionName = AbpFeatureManagementDbProperties.DbTablePrefix + "FeatureGroups"; + }); + + builder.Entity(b => + { + b.CollectionName = AbpFeatureManagementDbProperties.DbTablePrefix + "Features"; + }); + builder.Entity(b => { b.CollectionName = AbpFeatureManagementDbProperties.DbTablePrefix + "FeatureValues"; diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/IFeatureManagementMongoDbContext.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/IFeatureManagementMongoDbContext.cs index 1fefaf67cf..65004f1d7e 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/IFeatureManagementMongoDbContext.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/IFeatureManagementMongoDbContext.cs @@ -9,5 +9,9 @@ namespace Volo.Abp.FeatureManagement.MongoDB; [ConnectionStringName(AbpFeatureManagementDbProperties.ConnectionStringName)] public interface IFeatureManagementMongoDbContext : IAbpMongoDbContext { + IMongoCollection FeatureGroups { get; } + + IMongoCollection Features { get; } + IMongoCollection FeatureValues { get; } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureDefinitionRecordRepository.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureDefinitionRecordRepository.cs new file mode 100644 index 0000000000..cb7f48aeb4 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureDefinitionRecordRepository.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MongoDB.Driver.Linq; +using Volo.Abp.Domain.Repositories.MongoDB; +using Volo.Abp.MongoDB; + +namespace Volo.Abp.FeatureManagement.MongoDB; + +public class MongoFeatureDefinitionRecordRepository : + MongoDbRepository, + IFeatureDefinitionRecordRepository +{ + public MongoFeatureDefinitionRecordRepository( + IMongoDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + public virtual async Task FindByNameAsync(string name, CancellationToken cancellationToken = default) + { + cancellationToken = GetCancellationToken(cancellationToken); + return await (await GetMongoQueryableAsync(cancellationToken)) + .OrderBy(x => x.Id) + .FirstOrDefaultAsync( + s => s.Name == name, + cancellationToken + ); + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureGroupDefinitionRecordRepository.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureGroupDefinitionRecordRepository.cs new file mode 100644 index 0000000000..279479dcfa --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureGroupDefinitionRecordRepository.cs @@ -0,0 +1,16 @@ +using System; +using Volo.Abp.Domain.Repositories.MongoDB; +using Volo.Abp.MongoDB; + +namespace Volo.Abp.FeatureManagement.MongoDB; + +public class MongoFeatureGroupDefinitionRecordRepository : + MongoDbRepository, + IFeatureGroupDefinitionRecordRepository +{ + public MongoFeatureGroupDefinitionRecordRepository( + IMongoDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureValueRepository.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureValueRepository.cs index 61d59b893b..9d9071ea1a 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureValueRepository.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureValueRepository.cs @@ -9,7 +9,9 @@ using Volo.Abp.MongoDB; namespace Volo.Abp.FeatureManagement.MongoDB; -public class MongoFeatureValueRepository : MongoDbRepository, IFeatureValueRepository +public class MongoFeatureValueRepository : + MongoDbRepository, + IFeatureValueRepository { public MongoFeatureValueRepository(IMongoDbContextProvider dbContextProvider) : base(dbContextProvider) diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs index c31f25afbc..23c8a99ff1 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs @@ -1,12 +1,15 @@ +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; using Volo.Abp.EventBus.Local; using Volo.Abp.Features; +using Volo.Abp.Localization; using Volo.Abp.Validation.StringValues; namespace Volo.Abp.FeatureManagement.Web.Pages.FeatureManagement; @@ -31,14 +34,18 @@ public class FeatureManagementModal : AbpPageModel protected ILocalEventBus LocalEventBus { get; } + public AbpLocalizationOptions LocalizationOptions { get; } + public FeatureManagementModal( IFeatureAppService featureAppService, - ILocalEventBus localEventBus) + ILocalEventBus localEventBus, + IOptions localizationOptions) { ObjectMapperContext = typeof(AbpFeatureManagementWebModule); FeatureAppService = featureAppService; LocalEventBus = localEventBus; + LocalizationOptions = localizationOptions.Value; } public virtual async Task OnGetAsync() diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs index 28b8e55052..be4b431b82 100644 --- a/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs +++ b/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(options => + services.PreConfigure(options => { options.ValueValidatorFactory.Add(new ValueValidatorFactory("URL")); }); diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.Domain.Tests/Volo/Abp/FeatureManagement/FeatureDefinitionSerializer_Tests.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.Domain.Tests/Volo/Abp/FeatureManagement/FeatureDefinitionSerializer_Tests.cs new file mode 100644 index 0000000000..b025d1fa48 --- /dev/null +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.Domain.Tests/Volo/Abp/FeatureManagement/FeatureDefinitionSerializer_Tests.cs @@ -0,0 +1,87 @@ +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Data; +using Volo.Abp.FeatureManagement.Localization; +using Volo.Abp.Features; +using Volo.Abp.Localization; +using Volo.Abp.Validation.StringValues; +using Xunit; + +namespace Volo.Abp.FeatureManagement; + +public class FeatureDefinitionSerializer_Tests : FeatureManagementDomainTestBase +{ + private readonly IFeatureDefinitionSerializer _serializer; + + public FeatureDefinitionSerializer_Tests() + { + _serializer = GetRequiredService(); + } + + [Fact] + public async Task Serialize_Feature_Group_Definition() + { + // Arrange + + var context = new FeatureDefinitionContext(); + var group1 = CreateFeatureGroup1(context); + + // Act + + var featureGroupRecord = await _serializer.SerializeAsync(group1); + + //Assert + + featureGroupRecord.Name.ShouldBe("Group1"); + featureGroupRecord.DisplayName.ShouldBe("F:Group one"); + featureGroupRecord.GetProperty("CustomProperty1").ShouldBe("CustomValue1"); + } + + [Fact] + public async Task Serialize_Complex_Feature_Definition() + { + // Arrange + + var context = new FeatureDefinitionContext(); + var group1 = CreateFeatureGroup1(context); + var feature1 = group1.AddFeature( + "Feature1", + "default", + new LocalizableString(typeof(AbpFeatureManagementResource), "Feature1"), + new LocalizableString(typeof(AbpFeatureManagementResource), "Feature1"), + new ToggleStringValueType(), + isVisibleToClients: true + ) + .WithProviders("ProviderA", "ProviderB") + .WithProperty("CustomProperty2", "CustomValue2"); + + // Act + + var featureRecord = await _serializer.SerializeAsync( + feature1, + group1 + ); + + //Assert + + featureRecord.Name.ShouldBe("Feature1"); + featureRecord.GroupName.ShouldBe("Group1"); + featureRecord.DisplayName.ShouldBe("L:AbpFeatureManagement,Feature1"); + featureRecord.ValueType.ShouldBe("{\"name\":\"ToggleStringValueType\",\"properties\":{},\"validator\":{\"name\":\"BOOLEAN\",\"properties\":{}}}"); + featureRecord.GetProperty("CustomProperty2").ShouldBe("CustomValue2"); + featureRecord.AllowedProviders.ShouldBe("ProviderA,ProviderB"); + } + + private static FeatureGroupDefinition CreateFeatureGroup1( + IFeatureDefinitionContext context) + { + var group = context.AddGroup( + "Group1", + displayName: new FixedLocalizableString("Group one") + ); + + group["CustomProperty1"] = "CustomValue1"; + + return group; + } +} diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.Domain.Tests/Volo/Abp/FeatureManagement/StringValueTypeSerializer_Tests.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.Domain.Tests/Volo/Abp/FeatureManagement/StringValueTypeSerializer_Tests.cs new file mode 100644 index 0000000000..1eb9fe11ce --- /dev/null +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.Domain.Tests/Volo/Abp/FeatureManagement/StringValueTypeSerializer_Tests.cs @@ -0,0 +1,52 @@ +using System; +using Shouldly; +using Volo.Abp.Validation.StringValues; +using Xunit; + +namespace Volo.Abp.FeatureManagement; + +public class StringValueTypeSerializer_Tests : FeatureManagementDomainTestBase +{ + private readonly StringValueTypeSerializer _serializer; + + public StringValueTypeSerializer_Tests() + { + _serializer = GetRequiredService(); + } + + [Fact] + public void Serialize_And_Deserialize_Test() + { + // Arrange + + var valueType = new SelectionStringValueType + { + ItemSource = new StaticSelectionStringValueItemSource( + new LocalizableSelectionStringValueItem + { + Value = "TestValue", + DisplayText = new LocalizableStringInfo("TestResourceName", "TestName") + }), + Validator = new AlwaysValidValueValidator() + }; + + // Act + + var valueTypeJson = _serializer.Serialize(valueType); + + //Assert + valueTypeJson.ShouldBe("{\"itemSource\":{\"items\":[{\"value\":\"TestValue\",\"displayText\":{\"resourceName\":\"TestResourceName\",\"name\":\"TestName\"}}]},\"name\":\"SelectionStringValueType\",\"properties\":{},\"validator\":{\"name\":\"NULL\",\"properties\":{}}}"); + + // Act + var valueType2 = _serializer.Deserialize(valueTypeJson); + + //Assert + valueType2.ShouldBeOfType(); + valueType2.Validator.ShouldBeOfType(); + valueType2.As().ItemSource.Items.ShouldBeOfType(); + valueType2.As().ItemSource.Items.ShouldContain(x => + x.Value == "TestValue" && x.DisplayText.ResourceName == "TestResourceName" && + x.DisplayText.Name == "TestName"); + } + +} diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestModule.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestModule.cs index a3ac1f0099..8949d7dafd 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestModule.cs +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestModule.cs @@ -5,9 +5,11 @@ using Volo.Abp.Identity.EntityFrameworkCore; using Volo.Abp.Identity.Localization; using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.PermissionManagement; using Volo.Abp.PermissionManagement.Identity; using Volo.Abp.Threading; using Volo.Abp.VirtualFileSystem; +using Volo.Abp.Uow; namespace Volo.Abp.Identity; @@ -36,6 +38,17 @@ public class AbpIdentityDomainTestModule : AbpModule .Get() .AddVirtualJson("/Volo/Abp/Identity/LocalizationExtensions"); }); + + Configure(options => + { + options.IsDynamicPermissionStoreEnabled = false; + options.SaveStaticPermissionsToDatabase = false; + }); + + Configure(options => + { + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; + }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs index 8579fa28ac..9ef60a17e4 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs @@ -103,7 +103,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService { return new PermissionGrantInfoDto { Name = permission.Name, - DisplayName = permission.DisplayName.Localize(StringLocalizerFactory), + DisplayName = permission.DisplayName?.Localize(StringLocalizerFactory), ParentName = permission.Parent?.Name, AllowedProviders = permission.Providers, GrantedProviders = new List() @@ -117,7 +117,7 @@ public class PermissionAppService : ApplicationService, IPermissionAppService return new PermissionGroupDto { Name = group.Name, - DisplayName = group.DisplayName.Localize(StringLocalizerFactory), + DisplayName = group.DisplayName?.Localize(StringLocalizerFactory), DisplayNameKey = localizableDisplayName?.Name, DisplayNameResource = localizableDisplayName?.ResourceType != null ? LocalizationResourceNameAttribute.GetName(localizableDisplayName.ResourceType) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/AbpPermissionManagementDomainModule.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/AbpPermissionManagementDomainModule.cs index 1e05590176..ddf5be479e 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/AbpPermissionManagementDomainModule.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/AbpPermissionManagementDomainModule.cs @@ -25,7 +25,12 @@ namespace Volo.Abp.PermissionManagement; public class AbpPermissionManagementDomainModule : AbpModule { private readonly CancellationTokenSource _cancellationTokenSource = new(); - + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context)); + } + public override Task OnApplicationInitializationAsync(ApplicationInitializationContext context) { InitializeDynamicPermissions(context); @@ -44,12 +49,12 @@ public class AbpPermissionManagementDomainModule : AbpModule .ServiceProvider .GetRequiredService>() .Value; - + if (!options.SaveStaticPermissionsToDatabase && !options.IsDynamicPermissionStoreEnabled) { return; } - + var rootServiceProvider = context.ServiceProvider.GetRequiredService(); Task.Run(async () => @@ -58,7 +63,7 @@ public class AbpPermissionManagementDomainModule : AbpModule var applicationLifetime = scope.ServiceProvider.GetService(); var cancellationTokenProvider = scope.ServiceProvider.GetRequiredService(); var cancellationToken = applicationLifetime?.ApplicationStopping ?? _cancellationTokenSource.Token; - + try { using (cancellationTokenProvider.Use(cancellationToken)) @@ -67,7 +72,7 @@ public class AbpPermissionManagementDomainModule : AbpModule { return; } - + await SaveStaticPermissionsToDatabaseAsync(options, scope, cancellationTokenProvider); if (cancellationTokenProvider.Token.IsCancellationRequested) @@ -127,7 +132,7 @@ public class AbpPermissionManagementDomainModule : AbpModule try { - // Pre-cache permissions, so first request doesn't wait + // Pre-cache permissions, so first request doesn't wait await scope .ServiceProvider .GetRequiredService() @@ -144,4 +149,4 @@ public class AbpPermissionManagementDomainModule : AbpModule throw; // It will be cached in InitializeDynamicPermissions } } -} \ No newline at end of file +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionManagementOptions.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionManagementOptions.cs index 2109941a27..489ae26e3d 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionManagementOptions.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionManagementOptions.cs @@ -8,11 +8,7 @@ public class PermissionManagementOptions public ITypeList ManagementProviders { get; } public Dictionary ProviderPolicies { get; } - - public HashSet DeletedPermissions { get; } - - public HashSet DeletedPermissionGroups { get; } - + /// /// Default: true. /// @@ -27,8 +23,5 @@ public class PermissionManagementOptions { ManagementProviders = new TypeList(); ProviderPolicies = new Dictionary(); - - DeletedPermissions = new HashSet(); - DeletedPermissionGroups = new HashSet(); } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionSaver.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionSaver.cs index 5754031509..ba0be474f8 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionSaver.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionSaver.cs @@ -24,20 +24,20 @@ public class StaticPermissionSaver : IStaticPermissionSaver, ITransientDependenc protected IDistributedCache Cache { get; } protected IApplicationNameAccessor ApplicationNameAccessor { get; } protected IAbpDistributedLock DistributedLock { get; } - protected PermissionManagementOptions PermissionManagementOptions { get; } + protected AbpPermissionOptions PermissionOptions { get; } protected ICancellationTokenProvider CancellationTokenProvider { get; } protected AbpDistributedCacheOptions CacheOptions { get; } - + public StaticPermissionSaver( IStaticPermissionDefinitionStore staticStore, IPermissionGroupDefinitionRecordRepository permissionGroupRepository, IPermissionDefinitionRecordRepository permissionRepository, IPermissionDefinitionSerializer permissionSerializer, - IDistributedCache cache, + IDistributedCache cache, IOptions cacheOptions, IApplicationNameAccessor applicationNameAccessor, IAbpDistributedLock distributedLock, - IOptions permissionManagementOptions, + IOptions permissionOptions, ICancellationTokenProvider cancellationTokenProvider) { StaticStore = staticStore; @@ -48,23 +48,23 @@ public class StaticPermissionSaver : IStaticPermissionSaver, ITransientDependenc ApplicationNameAccessor = applicationNameAccessor; DistributedLock = distributedLock; CancellationTokenProvider = cancellationTokenProvider; - PermissionManagementOptions = permissionManagementOptions.Value; + PermissionOptions = permissionOptions.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, permissions, deleted groups and deleted permissions. * But the code would be more complex. This is enough for now. @@ -80,10 +80,10 @@ public class StaticPermissionSaver : IStaticPermissionSaver, ITransientDependenc var currentHash = CalculateHash( permissionGroupRecords, permissionRecords, - PermissionManagementOptions.DeletedPermissionGroups, - PermissionManagementOptions.DeletedPermissions + PermissionOptions.DeletedPermissionGroups, + PermissionOptions.DeletedPermissions ); - + if (cachedHash == currentHash) { return; @@ -154,11 +154,11 @@ public class StaticPermissionSaver : IStaticPermissionSaver, ITransientDependenc permissionGroupRecordInDatabase.Patch(permissionGroupRecord); changedRecords.Add(permissionGroupRecordInDatabase); } - + /* Deleted */ - var deletedRecords = PermissionManagementOptions.DeletedPermissionGroups.Any() + var deletedRecords = PermissionOptions.DeletedPermissionGroups.Any() ? permissionGroupRecordsInDatabase.Values - .Where(x => PermissionManagementOptions.DeletedPermissionGroups.Contains(x.Name)) + .Where(x => PermissionOptions.DeletedPermissionGroups.Contains(x.Name)) .ToArray() : Array.Empty(); @@ -176,10 +176,10 @@ public class StaticPermissionSaver : IStaticPermissionSaver, ITransientDependenc { await PermissionGroupRepository.DeleteManyAsync(deletedRecords); } - + return newRecords.Any() || changedRecords.Any() || deletedRecords.Any(); } - + private async Task UpdateChangedPermissionsAsync( IEnumerable permissionRecords) { @@ -209,23 +209,23 @@ public class StaticPermissionSaver : IStaticPermissionSaver, ITransientDependenc permissionRecordInDatabase.Patch(permissionRecord); changedRecords.Add(permissionRecordInDatabase); } - + /* Deleted */ var deletedRecords = new List(); - - if (PermissionManagementOptions.DeletedPermissions.Any()) + + if (PermissionOptions.DeletedPermissions.Any()) { deletedRecords.AddRange( permissionRecordsInDatabase.Values - .Where(x => PermissionManagementOptions.DeletedPermissions.Contains(x.Name)) + .Where(x => PermissionOptions.DeletedPermissions.Contains(x.Name)) ); } - if (PermissionManagementOptions.DeletedPermissionGroups.Any()) + if (PermissionOptions.DeletedPermissionGroups.Any()) { deletedRecords.AddIfNotContains( permissionRecordsInDatabase.Values - .Where(x => PermissionManagementOptions.DeletedPermissionGroups.Contains(x.GroupName)) + .Where(x => PermissionOptions.DeletedPermissionGroups.Contains(x.GroupName)) ); } @@ -243,7 +243,7 @@ public class StaticPermissionSaver : IStaticPermissionSaver, ITransientDependenc { await PermissionRepository.DeleteManyAsync(deletedRecords); } - + return newRecords.Any() || changedRecords.Any() || deletedRecords.Any(); } @@ -261,7 +261,7 @@ public class StaticPermissionSaver : IStaticPermissionSaver, ITransientDependenc { return $"{CacheOptions.KeyPrefix}_{ApplicationNameAccessor.ApplicationName}_AbpPermissionsHash"; } - + private string GetCommonStampCacheKey() { return $"{CacheOptions.KeyPrefix}_AbpInMemoryPermissionCacheStamp"; @@ -274,21 +274,21 @@ public class StaticPermissionSaver : IStaticPermissionSaver, ITransientDependenc IEnumerable deletedPermissions) { var stringBuilder = new StringBuilder(); - + stringBuilder.Append("PermissionGroupRecords:"); stringBuilder.AppendLine(JsonSerializer.Serialize(permissionGroupRecords)); - + stringBuilder.Append("PermissionRecords:"); stringBuilder.AppendLine(JsonSerializer.Serialize(permissionRecords)); - + stringBuilder.Append("DeletedPermissionGroups:"); stringBuilder.AppendLine(deletedPermissionGroups.JoinAsString(",")); - + stringBuilder.Append("DeletedPermission:"); stringBuilder.Append(deletedPermissions.JoinAsString(",")); - + return stringBuilder .ToString() .ToMd5(); } -} \ No newline at end of file +} diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDefinitionRecordRepository_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDefinitionRecordRepository_Tests.cs new file mode 100644 index 0000000000..20f9f7e21f --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDefinitionRecordRepository_Tests.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Volo.Abp.PermissionManagement; + +public abstract class PermissionDefinitionRecordRepository_Tests : PermissionTestBase +{ + protected IPermissionDefinitionRecordRepository PermissionDefinitionRecordRepository { get; set; } + + protected PermissionDefinitionRecordRepository_Tests() + { + PermissionDefinitionRecordRepository = GetRequiredService(); + } + + [Fact] + public async Task FindByNameAsync() + { + var permission = await PermissionDefinitionRecordRepository.FindByNameAsync("MyPermission1"); + permission.ShouldNotBeNull(); + permission.Name.ShouldBe("MyPermission1"); + + permission = await PermissionDefinitionRecordRepository.FindByNameAsync("MyPermission2"); + permission.ShouldNotBeNull(); + permission.Name.ShouldBe("MyPermission2"); + } +} diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/StaticPermissionSaver_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/StaticPermissionSaver_Tests.cs new file mode 100644 index 0000000000..28fa57b034 --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/StaticPermissionSaver_Tests.cs @@ -0,0 +1,13 @@ +namespace Volo.Abp.PermissionManagement; + +public class StaticPermissionSaver_Tests : PermissionTestBase +{ + private readonly IStaticPermissionSaver _saver; + + public StaticPermissionSaver_Tests() + { + _saver = GetRequiredService(); + } + + +} diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests.csproj b/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests.csproj index 034ee303da..2caab81eb6 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests.csproj +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests.csproj @@ -13,6 +13,7 @@ + diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo/Abp/PermissionManagement/EntityFrameworkCore/EFCorePermissionDefinitionRecordRepository_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo/Abp/PermissionManagement/EntityFrameworkCore/EFCorePermissionDefinitionRecordRepository_Tests.cs new file mode 100644 index 0000000000..5429d2e772 --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo/Abp/PermissionManagement/EntityFrameworkCore/EFCorePermissionDefinitionRecordRepository_Tests.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.PermissionManagement.EntityFrameworkCore; + +public class EFCorePermissionDefinitionRecordRepository_Tests : PermissionDefinitionRecordRepository_Tests +{ + +} diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo.Abp.PermissionManagement.MongoDB.Tests.csproj b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo.Abp.PermissionManagement.MongoDB.Tests.csproj index 93b14e484f..9a23915ee8 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo.Abp.PermissionManagement.MongoDB.Tests.csproj +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo.Abp.PermissionManagement.MongoDB.Tests.csproj @@ -13,6 +13,7 @@ + diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/MongoDbPermissionDefinitionRecordRepository_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/MongoDbPermissionDefinitionRecordRepository_Tests.cs new file mode 100644 index 0000000000..d43634cdb9 --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/MongoDbPermissionDefinitionRecordRepository_Tests.cs @@ -0,0 +1,9 @@ +using Xunit; + +namespace Volo.Abp.PermissionManagement.MongoDB; + +[Collection(MongoTestCollection.Name)] +public class MongoDbPermissionDefinitionRecordRepository_Tests : PermissionGrantRepository_Tests +{ + +} diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20220825012458_Initial.Designer.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20220909024045_Initial.Designer.cs similarity index 95% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20220825012458_Initial.Designer.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20220909024045_Initial.Designer.cs index 5347dc149d..df15aa6039 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20220825012458_Initial.Designer.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20220909024045_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations { [DbContext(typeof(MyProjectNameDbContext))] - [Migration("20220825012458_Initial")] + [Migration("20220909024045_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -288,6 +288,95 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations b.ToTable("AbpEntityPropertyChanges", (string)null); }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowedProviders") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DefaultValue") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("IsAvailableToHost") + .HasColumnType("bit"); + + b.Property("IsVisibleToClients") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ParentName") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ValueType") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("GroupName"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatures", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureGroupDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatureGroups", (string)null); + }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureValue", b => { b.Property("Id") diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20220825012458_Initial.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20220909024045_Initial.cs similarity index 94% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20220825012458_Initial.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20220909024045_Initial.cs index 13ecbb5be7..ca557a6dd9 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20220825012458_Initial.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20220909024045_Initial.cs @@ -63,6 +63,42 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations table.PrimaryKey("PK_AbpClaimTypes", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AbpFeatureGroups", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatureGroups", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpFeatures", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + GroupName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ParentName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + Description = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + DefaultValue = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + IsVisibleToClients = table.Column(type: "bit", nullable: false), + IsAvailableToHost = table.Column(type: "bit", nullable: false), + AllowedProviders = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ValueType = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatures", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpFeatureValues", columns: table => new @@ -712,6 +748,23 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations table: "AbpEntityPropertyChanges", column: "EntityChangeId"); + migrationBuilder.CreateIndex( + name: "IX_AbpFeatureGroups_Name", + table: "AbpFeatureGroups", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatures_GroupName", + table: "AbpFeatures", + column: "GroupName"); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatures_Name", + table: "AbpFeatures", + column: "Name", + unique: true); + migrationBuilder.CreateIndex( name: "IX_AbpFeatureValues_Name_ProviderName_ProviderKey", table: "AbpFeatureValues", @@ -889,6 +942,12 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations migrationBuilder.DropTable( name: "AbpEntityPropertyChanges"); + migrationBuilder.DropTable( + name: "AbpFeatureGroups"); + + migrationBuilder.DropTable( + name: "AbpFeatures"); + migrationBuilder.DropTable( name: "AbpFeatureValues"); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/MyProjectNameDbContextModelSnapshot.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/MyProjectNameDbContextModelSnapshot.cs index d6c92decad..f9724b5b3d 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/MyProjectNameDbContextModelSnapshot.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/MyProjectNameDbContextModelSnapshot.cs @@ -286,6 +286,95 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations b.ToTable("AbpEntityPropertyChanges", (string)null); }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowedProviders") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DefaultValue") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("IsAvailableToHost") + .HasColumnType("bit"); + + b.Property("IsVisibleToClients") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ParentName") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ValueType") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("GroupName"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatures", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureGroupDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatureGroups", (string)null); + }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureValue", b => { b.Property("Id") diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20220825012635_Initial.Designer.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20220909024045_Initial.Designer.cs similarity index 95% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20220825012635_Initial.Designer.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20220909024045_Initial.Designer.cs index 0c525e8471..a0c10f22eb 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20220825012635_Initial.Designer.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20220909024045_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Host.Migrations { [DbContext(typeof(MyProjectNameDbContext))] - [Migration("20220825012635_Initial")] + [Migration("20220909024045_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -288,6 +288,95 @@ namespace MyCompanyName.MyProjectName.Host.Migrations b.ToTable("AbpEntityPropertyChanges", (string)null); }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowedProviders") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DefaultValue") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("IsAvailableToHost") + .HasColumnType("bit"); + + b.Property("IsVisibleToClients") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ParentName") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ValueType") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("GroupName"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatures", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureGroupDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatureGroups", (string)null); + }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureValue", b => { b.Property("Id") diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20220825012635_Initial.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20220909024045_Initial.cs similarity index 94% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20220825012635_Initial.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20220909024045_Initial.cs index 8342507d18..f1520c0873 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20220825012635_Initial.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20220909024045_Initial.cs @@ -63,6 +63,42 @@ namespace MyCompanyName.MyProjectName.Host.Migrations table.PrimaryKey("PK_AbpClaimTypes", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AbpFeatureGroups", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatureGroups", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpFeatures", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + GroupName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ParentName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + Description = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + DefaultValue = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + IsVisibleToClients = table.Column(type: "bit", nullable: false), + IsAvailableToHost = table.Column(type: "bit", nullable: false), + AllowedProviders = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ValueType = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatures", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpFeatureValues", columns: table => new @@ -712,6 +748,23 @@ namespace MyCompanyName.MyProjectName.Host.Migrations table: "AbpEntityPropertyChanges", column: "EntityChangeId"); + migrationBuilder.CreateIndex( + name: "IX_AbpFeatureGroups_Name", + table: "AbpFeatureGroups", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatures_GroupName", + table: "AbpFeatures", + column: "GroupName"); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatures_Name", + table: "AbpFeatures", + column: "Name", + unique: true); + migrationBuilder.CreateIndex( name: "IX_AbpFeatureValues_Name_ProviderName_ProviderKey", table: "AbpFeatureValues", @@ -889,6 +942,12 @@ namespace MyCompanyName.MyProjectName.Host.Migrations migrationBuilder.DropTable( name: "AbpEntityPropertyChanges"); + migrationBuilder.DropTable( + name: "AbpFeatureGroups"); + + migrationBuilder.DropTable( + name: "AbpFeatures"); + migrationBuilder.DropTable( name: "AbpFeatureValues"); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/MyProjectNameDbContextModelSnapshot.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/MyProjectNameDbContextModelSnapshot.cs index 7360423342..48c632be62 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/MyProjectNameDbContextModelSnapshot.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/MyProjectNameDbContextModelSnapshot.cs @@ -286,6 +286,95 @@ namespace MyCompanyName.MyProjectName.Host.Migrations b.ToTable("AbpEntityPropertyChanges", (string)null); }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowedProviders") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DefaultValue") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("IsAvailableToHost") + .HasColumnType("bit"); + + b.Property("IsVisibleToClients") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ParentName") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ValueType") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("GroupName"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatures", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureGroupDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatureGroups", (string)null); + }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureValue", b => { b.Property("Id") diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20220825012450_Initial.Designer.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20220909024056_Initial.Designer.cs similarity index 95% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20220825012450_Initial.Designer.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20220909024056_Initial.Designer.cs index 94f77b9938..c059bb8caa 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20220825012450_Initial.Designer.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20220909024056_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Mvc.Migrations { [DbContext(typeof(MyProjectNameDbContext))] - [Migration("20220825012450_Initial")] + [Migration("20220909024056_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -288,6 +288,95 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations b.ToTable("AbpEntityPropertyChanges", (string)null); }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowedProviders") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DefaultValue") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("IsAvailableToHost") + .HasColumnType("bit"); + + b.Property("IsVisibleToClients") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ParentName") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ValueType") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("GroupName"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatures", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureGroupDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatureGroups", (string)null); + }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureValue", b => { b.Property("Id") diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20220825012450_Initial.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20220909024056_Initial.cs similarity index 94% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20220825012450_Initial.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20220909024056_Initial.cs index df71202963..d8f26faf2c 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20220825012450_Initial.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20220909024056_Initial.cs @@ -63,6 +63,42 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations table.PrimaryKey("PK_AbpClaimTypes", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AbpFeatureGroups", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatureGroups", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpFeatures", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + GroupName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ParentName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + Description = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + DefaultValue = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + IsVisibleToClients = table.Column(type: "bit", nullable: false), + IsAvailableToHost = table.Column(type: "bit", nullable: false), + AllowedProviders = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ValueType = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatures", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpFeatureValues", columns: table => new @@ -712,6 +748,23 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations table: "AbpEntityPropertyChanges", column: "EntityChangeId"); + migrationBuilder.CreateIndex( + name: "IX_AbpFeatureGroups_Name", + table: "AbpFeatureGroups", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatures_GroupName", + table: "AbpFeatures", + column: "GroupName"); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatures_Name", + table: "AbpFeatures", + column: "Name", + unique: true); + migrationBuilder.CreateIndex( name: "IX_AbpFeatureValues_Name_ProviderName_ProviderKey", table: "AbpFeatureValues", @@ -889,6 +942,12 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations migrationBuilder.DropTable( name: "AbpEntityPropertyChanges"); + migrationBuilder.DropTable( + name: "AbpFeatureGroups"); + + migrationBuilder.DropTable( + name: "AbpFeatures"); + migrationBuilder.DropTable( name: "AbpFeatureValues"); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/MyProjectNameDbContextModelSnapshot.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/MyProjectNameDbContextModelSnapshot.cs index 80d24ed8d2..688d6910a9 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/MyProjectNameDbContextModelSnapshot.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/MyProjectNameDbContextModelSnapshot.cs @@ -286,6 +286,95 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations b.ToTable("AbpEntityPropertyChanges", (string)null); }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowedProviders") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DefaultValue") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("IsAvailableToHost") + .HasColumnType("bit"); + + b.Property("IsVisibleToClients") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ParentName") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ValueType") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("GroupName"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatures", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureGroupDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatureGroups", (string)null); + }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureValue", b => { b.Property("Id") diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20220825012920_Initial.Designer.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20220909023853_Initial.Designer.cs similarity index 92% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20220825012920_Initial.Designer.cs rename to templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20220909023853_Initial.Designer.cs index 6f31dccc25..0e60d3c43b 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20220825012920_Initial.Designer.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20220909023853_Initial.Designer.cs @@ -12,8 +12,8 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Migrations { - [DbContext(typeof(AuthServerDbContext))] - [Migration("20220825012920_Initial")] + [DbContext(typeof(MyProjectNameDbContext))] + [Migration("20220909023853_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -288,6 +288,153 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpEntityPropertyChanges", (string)null); }); + modelBuilder.Entity("Volo.Abp.BackgroundJobs.BackgroundJobRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("IsAbandoned") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("JobArgs") + .IsRequired() + .HasMaxLength(1048576) + .HasColumnType("nvarchar(max)"); + + b.Property("JobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("LastTryTime") + .HasColumnType("datetime2"); + + b.Property("NextTryTime") + .HasColumnType("datetime2"); + + b.Property("Priority") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint") + .HasDefaultValue((byte)15); + + b.Property("TryCount") + .ValueGeneratedOnAdd() + .HasColumnType("smallint") + .HasDefaultValue((short)0); + + b.HasKey("Id"); + + b.HasIndex("IsAbandoned", "NextTryTime"); + + b.ToTable("AbpBackgroundJobs", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowedProviders") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DefaultValue") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("IsAvailableToHost") + .HasColumnType("bit"); + + b.Property("IsVisibleToClients") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ParentName") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ValueType") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("GroupName"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatures", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureGroupDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatureGroups", (string)null); + }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureValue", b => { b.Property("Id") @@ -324,7 +471,6 @@ namespace MyCompanyName.MyProjectName.Migrations modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b => { b.Property("Id") - .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); b.Property("ConcurrencyStamp") @@ -371,7 +517,6 @@ namespace MyCompanyName.MyProjectName.Migrations modelBuilder.Entity("Volo.Abp.Identity.IdentityLinkUser", b => { b.Property("Id") - .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); b.Property("SourceTenantId") @@ -398,7 +543,6 @@ namespace MyCompanyName.MyProjectName.Migrations modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b => { b.Property("Id") - .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); b.Property("ConcurrencyStamp") @@ -475,7 +619,6 @@ namespace MyCompanyName.MyProjectName.Migrations modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b => { b.Property("Id") - .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); b.Property("Action") @@ -550,7 +693,6 @@ namespace MyCompanyName.MyProjectName.Migrations modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => { b.Property("Id") - .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); b.Property("AccessFailedCount") @@ -834,7 +976,6 @@ namespace MyCompanyName.MyProjectName.Migrations modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => { b.Property("Id") - .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); b.Property("Code") @@ -1424,7 +1565,6 @@ namespace MyCompanyName.MyProjectName.Migrations modelBuilder.Entity("Volo.Abp.TenantManagement.Tenant", b => { b.Property("Id") - .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); b.Property("ConcurrencyStamp") diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20220825012920_Initial.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20220909023853_Initial.cs similarity index 91% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20220825012920_Initial.cs rename to templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20220909023853_Initial.cs index e2dfaa2f6f..979458acc5 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20220825012920_Initial.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20220909023853_Initial.cs @@ -43,6 +43,27 @@ namespace MyCompanyName.MyProjectName.Migrations table.PrimaryKey("PK_AbpAuditLogs", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AbpBackgroundJobs", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + JobName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + JobArgs = table.Column(type: "nvarchar(max)", maxLength: 1048576, nullable: false), + TryCount = table.Column(type: "smallint", nullable: false, defaultValue: (short)0), + CreationTime = table.Column(type: "datetime2", nullable: false), + NextTryTime = table.Column(type: "datetime2", nullable: false), + LastTryTime = table.Column(type: "datetime2", nullable: true), + IsAbandoned = table.Column(type: "bit", nullable: false, defaultValue: false), + Priority = table.Column(type: "tinyint", nullable: false, defaultValue: (byte)15), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpBackgroundJobs", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpClaimTypes", columns: table => new @@ -63,6 +84,42 @@ namespace MyCompanyName.MyProjectName.Migrations table.PrimaryKey("PK_AbpClaimTypes", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AbpFeatureGroups", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatureGroups", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpFeatures", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + GroupName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ParentName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + Description = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + DefaultValue = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + IsVisibleToClients = table.Column(type: "bit", nullable: false), + IsAvailableToHost = table.Column(type: "bit", nullable: false), + AllowedProviders = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ValueType = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatures", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpFeatureValues", columns: table => new @@ -697,6 +754,11 @@ namespace MyCompanyName.MyProjectName.Migrations table: "AbpAuditLogs", columns: new[] { "TenantId", "UserId", "ExecutionTime" }); + migrationBuilder.CreateIndex( + name: "IX_AbpBackgroundJobs_IsAbandoned_NextTryTime", + table: "AbpBackgroundJobs", + columns: new[] { "IsAbandoned", "NextTryTime" }); + migrationBuilder.CreateIndex( name: "IX_AbpEntityChanges_AuditLogId", table: "AbpEntityChanges", @@ -712,6 +774,23 @@ namespace MyCompanyName.MyProjectName.Migrations table: "AbpEntityPropertyChanges", column: "EntityChangeId"); + migrationBuilder.CreateIndex( + name: "IX_AbpFeatureGroups_Name", + table: "AbpFeatureGroups", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatures_GroupName", + table: "AbpFeatures", + column: "GroupName"); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatures_Name", + table: "AbpFeatures", + column: "Name", + unique: true); + migrationBuilder.CreateIndex( name: "IX_AbpFeatureValues_Name_ProviderName_ProviderKey", table: "AbpFeatureValues", @@ -883,12 +962,21 @@ namespace MyCompanyName.MyProjectName.Migrations migrationBuilder.DropTable( name: "AbpAuditLogActions"); + migrationBuilder.DropTable( + name: "AbpBackgroundJobs"); + migrationBuilder.DropTable( name: "AbpClaimTypes"); migrationBuilder.DropTable( name: "AbpEntityPropertyChanges"); + migrationBuilder.DropTable( + name: "AbpFeatureGroups"); + + migrationBuilder.DropTable( + name: "AbpFeatures"); + migrationBuilder.DropTable( name: "AbpFeatureValues"); diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/MyProjectNameDbContextModelSnapshot.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/MyProjectNameDbContextModelSnapshot.cs index ce8d020786..2f4dd59b75 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/MyProjectNameDbContextModelSnapshot.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/MyProjectNameDbContextModelSnapshot.cs @@ -344,6 +344,95 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpBackgroundJobs", (string)null); }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowedProviders") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DefaultValue") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("IsAvailableToHost") + .HasColumnType("bit"); + + b.Property("IsVisibleToClients") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ParentName") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ValueType") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("GroupName"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatures", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureGroupDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatureGroups", (string)null); + }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureValue", b => { b.Property("Id") diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20220825012254_Initial.Designer.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20220909024101_Initial.Designer.cs similarity index 95% rename from templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20220825012254_Initial.Designer.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20220909024101_Initial.Designer.cs index 41c49a2e4d..ecf317435c 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20220825012254_Initial.Designer.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20220909024101_Initial.Designer.cs @@ -12,8 +12,8 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Migrations { - [DbContext(typeof(MyProjectNameDbContext))] - [Migration("20220825012254_Initial")] + [DbContext(typeof(AuthServerDbContext))] + [Migration("20220909024101_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -288,62 +288,93 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpEntityPropertyChanges", (string)null); }); - modelBuilder.Entity("Volo.Abp.BackgroundJobs.BackgroundJobRecord", b => + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureDefinitionRecord", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)") - .HasColumnName("ConcurrencyStamp"); + b.Property("AllowedProviders") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); - b.Property("CreationTime") - .HasColumnType("datetime2") - .HasColumnName("CreationTime"); + b.Property("DefaultValue") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); b.Property("ExtraProperties") .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); - b.Property("IsAbandoned") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("JobArgs") + b.Property("GroupName") .IsRequired() - .HasMaxLength(1048576) - .HasColumnType("nvarchar(max)"); + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); - b.Property("JobName") + b.Property("IsAvailableToHost") + .HasColumnType("bit"); + + b.Property("IsVisibleToClients") + .HasColumnType("bit"); + + b.Property("Name") .IsRequired() .HasMaxLength(128) .HasColumnType("nvarchar(128)"); - b.Property("LastTryTime") - .HasColumnType("datetime2"); + b.Property("ParentName") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); - b.Property("NextTryTime") - .HasColumnType("datetime2"); + b.Property("ValueType") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); - b.Property("Priority") - .ValueGeneratedOnAdd() - .HasColumnType("tinyint") - .HasDefaultValue((byte)15); + b.HasKey("Id"); + + b.HasIndex("GroupName"); + + b.HasIndex("Name") + .IsUnique(); - b.Property("TryCount") + b.ToTable("AbpFeatures", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureGroupDefinitionRecord", b => + { + b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("smallint") - .HasDefaultValue((short)0); + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); b.HasKey("Id"); - b.HasIndex("IsAbandoned", "NextTryTime"); + b.HasIndex("Name") + .IsUnique(); - b.ToTable("AbpBackgroundJobs", (string)null); + b.ToTable("AbpFeatureGroups", (string)null); }); modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureValue", b => @@ -382,6 +413,7 @@ namespace MyCompanyName.MyProjectName.Migrations modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b => { b.Property("Id") + .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); b.Property("ConcurrencyStamp") @@ -428,6 +460,7 @@ namespace MyCompanyName.MyProjectName.Migrations modelBuilder.Entity("Volo.Abp.Identity.IdentityLinkUser", b => { b.Property("Id") + .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); b.Property("SourceTenantId") @@ -454,6 +487,7 @@ namespace MyCompanyName.MyProjectName.Migrations modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b => { b.Property("Id") + .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); b.Property("ConcurrencyStamp") @@ -530,6 +564,7 @@ namespace MyCompanyName.MyProjectName.Migrations modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b => { b.Property("Id") + .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); b.Property("Action") @@ -604,6 +639,7 @@ namespace MyCompanyName.MyProjectName.Migrations modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => { b.Property("Id") + .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); b.Property("AccessFailedCount") @@ -887,6 +923,7 @@ namespace MyCompanyName.MyProjectName.Migrations modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => { b.Property("Id") + .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); b.Property("Code") @@ -1476,6 +1513,7 @@ namespace MyCompanyName.MyProjectName.Migrations modelBuilder.Entity("Volo.Abp.TenantManagement.Tenant", b => { b.Property("Id") + .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); b.Property("ConcurrencyStamp") diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20220825012254_Initial.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20220909024101_Initial.cs similarity index 95% rename from templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20220825012254_Initial.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20220909024101_Initial.cs index 8ddcfae69e..db22e15401 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20220825012254_Initial.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20220909024101_Initial.cs @@ -44,44 +44,59 @@ namespace MyCompanyName.MyProjectName.Migrations }); migrationBuilder.CreateTable( - name: "AbpBackgroundJobs", + name: "AbpClaimTypes", columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), - JobName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - JobArgs = table.Column(type: "nvarchar(max)", maxLength: 1048576, nullable: false), - TryCount = table.Column(type: "smallint", nullable: false, defaultValue: (short)0), - CreationTime = table.Column(type: "datetime2", nullable: false), - NextTryTime = table.Column(type: "datetime2", nullable: false), - LastTryTime = table.Column(type: "datetime2", nullable: true), - IsAbandoned = table.Column(type: "bit", nullable: false, defaultValue: false), - Priority = table.Column(type: "tinyint", nullable: false, defaultValue: (byte)15), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + Required = table.Column(type: "bit", nullable: false), + IsStatic = table.Column(type: "bit", nullable: false), + Regex = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), + RegexDescription = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + Description = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ValueType = table.Column(type: "int", nullable: false), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) }, constraints: table => { - table.PrimaryKey("PK_AbpBackgroundJobs", x => x.Id); + table.PrimaryKey("PK_AbpClaimTypes", x => x.Id); }); migrationBuilder.CreateTable( - name: "AbpClaimTypes", + name: "AbpFeatureGroups", columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - Required = table.Column(type: "bit", nullable: false), - IsStatic = table.Column(type: "bit", nullable: false), - Regex = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), - RegexDescription = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatureGroups", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpFeatures", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + GroupName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ParentName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), Description = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ValueType = table.Column(type: "int", nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) + DefaultValue = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + IsVisibleToClients = table.Column(type: "bit", nullable: false), + IsAvailableToHost = table.Column(type: "bit", nullable: false), + AllowedProviders = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ValueType = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_AbpClaimTypes", x => x.Id); + table.PrimaryKey("PK_AbpFeatures", x => x.Id); }); migrationBuilder.CreateTable( @@ -718,11 +733,6 @@ namespace MyCompanyName.MyProjectName.Migrations table: "AbpAuditLogs", columns: new[] { "TenantId", "UserId", "ExecutionTime" }); - migrationBuilder.CreateIndex( - name: "IX_AbpBackgroundJobs_IsAbandoned_NextTryTime", - table: "AbpBackgroundJobs", - columns: new[] { "IsAbandoned", "NextTryTime" }); - migrationBuilder.CreateIndex( name: "IX_AbpEntityChanges_AuditLogId", table: "AbpEntityChanges", @@ -738,6 +748,23 @@ namespace MyCompanyName.MyProjectName.Migrations table: "AbpEntityPropertyChanges", column: "EntityChangeId"); + migrationBuilder.CreateIndex( + name: "IX_AbpFeatureGroups_Name", + table: "AbpFeatureGroups", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatures_GroupName", + table: "AbpFeatures", + column: "GroupName"); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatures_Name", + table: "AbpFeatures", + column: "Name", + unique: true); + migrationBuilder.CreateIndex( name: "IX_AbpFeatureValues_Name_ProviderName_ProviderKey", table: "AbpFeatureValues", @@ -909,15 +936,18 @@ namespace MyCompanyName.MyProjectName.Migrations migrationBuilder.DropTable( name: "AbpAuditLogActions"); - migrationBuilder.DropTable( - name: "AbpBackgroundJobs"); - migrationBuilder.DropTable( name: "AbpClaimTypes"); migrationBuilder.DropTable( name: "AbpEntityPropertyChanges"); + migrationBuilder.DropTable( + name: "AbpFeatureGroups"); + + migrationBuilder.DropTable( + name: "AbpFeatures"); + migrationBuilder.DropTable( name: "AbpFeatureValues"); diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/AuthServerDbContextModelSnapshot.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/AuthServerDbContextModelSnapshot.cs index 4ba9b7daaa..7c33ebf9a1 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/AuthServerDbContextModelSnapshot.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/AuthServerDbContextModelSnapshot.cs @@ -286,6 +286,95 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpEntityPropertyChanges", (string)null); }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowedProviders") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DefaultValue") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("IsAvailableToHost") + .HasColumnType("bit"); + + b.Property("IsVisibleToClients") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ParentName") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ValueType") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("GroupName"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatures", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureGroupDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatureGroups", (string)null); + }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureValue", b => { b.Property("Id") diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20220825013015_Initial.Designer.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20220909024110_Initial.Designer.cs similarity index 93% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20220825013015_Initial.Designer.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20220909024110_Initial.Designer.cs index 4a13126215..4212fbcf7c 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20220825013015_Initial.Designer.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20220909024110_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations { [DbContext(typeof(UnifiedDbContext))] - [Migration("20220825013015_Initial")] + [Migration("20220909024110_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -288,6 +288,95 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations b.ToTable("AbpEntityPropertyChanges", (string)null); }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowedProviders") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DefaultValue") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("IsAvailableToHost") + .HasColumnType("bit"); + + b.Property("IsVisibleToClients") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ParentName") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ValueType") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("GroupName"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatures", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureGroupDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatureGroups", (string)null); + }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureValue", b => { b.Property("Id") diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20220825013015_Initial.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20220909024110_Initial.cs similarity index 93% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20220825013015_Initial.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20220909024110_Initial.cs index be6fee4b31..3a13a1bdfd 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20220825013015_Initial.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20220909024110_Initial.cs @@ -63,6 +63,42 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations table.PrimaryKey("PK_AbpClaimTypes", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AbpFeatureGroups", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatureGroups", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpFeatures", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + GroupName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ParentName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + Description = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + DefaultValue = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + IsVisibleToClients = table.Column(type: "bit", nullable: false), + IsAvailableToHost = table.Column(type: "bit", nullable: false), + AllowedProviders = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ValueType = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatures", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpFeatureValues", columns: table => new @@ -579,6 +615,23 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations table: "AbpEntityPropertyChanges", column: "EntityChangeId"); + migrationBuilder.CreateIndex( + name: "IX_AbpFeatureGroups_Name", + table: "AbpFeatureGroups", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatures_GroupName", + table: "AbpFeatures", + column: "GroupName"); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatures_Name", + table: "AbpFeatures", + column: "Name", + unique: true); + migrationBuilder.CreateIndex( name: "IX_AbpFeatureValues_Name_ProviderName_ProviderKey", table: "AbpFeatureValues", @@ -726,6 +779,12 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations migrationBuilder.DropTable( name: "AbpEntityPropertyChanges"); + migrationBuilder.DropTable( + name: "AbpFeatureGroups"); + + migrationBuilder.DropTable( + name: "AbpFeatures"); + migrationBuilder.DropTable( name: "AbpFeatureValues"); diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/UnifiedDbContextModelSnapshot.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/UnifiedDbContextModelSnapshot.cs index 0ba27152ac..a28b93cced 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/UnifiedDbContextModelSnapshot.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/UnifiedDbContextModelSnapshot.cs @@ -286,6 +286,95 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations b.ToTable("AbpEntityPropertyChanges", (string)null); }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowedProviders") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DefaultValue") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("IsAvailableToHost") + .HasColumnType("bit"); + + b.Property("IsVisibleToClients") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ParentName") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ValueType") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("GroupName"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatures", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureGroupDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatureGroups", (string)null); + }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureValue", b => { b.Property("Id") diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20220825013032_Initial.Designer.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20220909024111_Initial.Designer.cs similarity index 93% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20220825013032_Initial.Designer.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20220909024111_Initial.Designer.cs index 68cf3fe47f..6262b656fe 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20220825013032_Initial.Designer.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20220909024111_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Migrations { [DbContext(typeof(UnifiedDbContext))] - [Migration("20220825013032_Initial")] + [Migration("20220909024111_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -288,6 +288,95 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpEntityPropertyChanges", (string)null); }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowedProviders") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DefaultValue") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("IsAvailableToHost") + .HasColumnType("bit"); + + b.Property("IsVisibleToClients") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ParentName") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ValueType") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("GroupName"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatures", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureGroupDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatureGroups", (string)null); + }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureValue", b => { b.Property("Id") diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20220825013032_Initial.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20220909024111_Initial.cs similarity index 93% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20220825013032_Initial.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20220909024111_Initial.cs index 7c0caa1baa..7d956f49d2 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20220825013032_Initial.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20220909024111_Initial.cs @@ -63,6 +63,42 @@ namespace MyCompanyName.MyProjectName.Migrations table.PrimaryKey("PK_AbpClaimTypes", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AbpFeatureGroups", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatureGroups", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpFeatures", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + GroupName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ParentName = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), + DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + Description = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + DefaultValue = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + IsVisibleToClients = table.Column(type: "bit", nullable: false), + IsAvailableToHost = table.Column(type: "bit", nullable: false), + AllowedProviders = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ValueType = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpFeatures", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AbpFeatureValues", columns: table => new @@ -579,6 +615,23 @@ namespace MyCompanyName.MyProjectName.Migrations table: "AbpEntityPropertyChanges", column: "EntityChangeId"); + migrationBuilder.CreateIndex( + name: "IX_AbpFeatureGroups_Name", + table: "AbpFeatureGroups", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatures_GroupName", + table: "AbpFeatures", + column: "GroupName"); + + migrationBuilder.CreateIndex( + name: "IX_AbpFeatures_Name", + table: "AbpFeatures", + column: "Name", + unique: true); + migrationBuilder.CreateIndex( name: "IX_AbpFeatureValues_Name_ProviderName_ProviderKey", table: "AbpFeatureValues", @@ -726,6 +779,12 @@ namespace MyCompanyName.MyProjectName.Migrations migrationBuilder.DropTable( name: "AbpEntityPropertyChanges"); + migrationBuilder.DropTable( + name: "AbpFeatureGroups"); + + migrationBuilder.DropTable( + name: "AbpFeatures"); + migrationBuilder.DropTable( name: "AbpFeatureValues"); diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs index cb1236c39b..4b7d49f237 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/UnifiedDbContextModelSnapshot.cs @@ -286,6 +286,95 @@ namespace MyCompanyName.MyProjectName.Migrations b.ToTable("AbpEntityPropertyChanges", (string)null); }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllowedProviders") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DefaultValue") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("IsAvailableToHost") + .HasColumnType("bit"); + + b.Property("IsVisibleToClients") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ParentName") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ValueType") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("GroupName"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatures", (string)null); + }); + + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureGroupDefinitionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("AbpFeatureGroups", (string)null); + }); + modelBuilder.Entity("Volo.Abp.FeatureManagement.FeatureValue", b => { b.Property("Id")