Browse Source

Add batch feature checking support with RequireFeaturesSimpleBatchStateChecker

pull/25276/head
maliming 3 weeks ago
parent
commit
c43237403c
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/AbpAspNetCoreMvcUiThemeSharedModule.cs
  2. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Toolbars/ToolbarManager.cs
  3. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj
  4. 15
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerBase.cs
  5. 27
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureSimpleStateCheckerExtensions.cs
  6. 6
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeaturesSimpleStateCheckerSerializerContributor.cs
  7. 5
      framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureChecker.cs
  8. 77
      framework/src/Volo.Abp.Features/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker.cs
  9. 23
      framework/src/Volo.Abp.Features/Volo/Abp/Features/RequireFeaturesSimpleBatchStateCheckerModel.cs
  10. 1
      framework/src/Volo.Abp.UI.Navigation/Volo.Abp.UI.Navigation.csproj
  11. 3
      framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/AbpUiNavigationModule.cs
  12. 2
      framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs
  13. 106
      framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker_Tests.cs
  14. 4
      modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer_Tests.cs

4
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/AbpAspNetCoreMvcUiThemeSharedModule.cs

@ -4,6 +4,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Packages;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
using Volo.Abp.Features;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
@ -12,7 +13,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared;
[DependsOn(
typeof(AbpAspNetCoreMvcUiBootstrapModule),
typeof(AbpAspNetCoreMvcUiPackagesModule),
typeof(AbpAspNetCoreMvcUiWidgetsModule)
typeof(AbpAspNetCoreMvcUiWidgetsModule),
typeof(AbpFeaturesModule)
)]
public class AbpAspNetCoreMvcUiThemeSharedModule : AbpModule
{

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Toolbars/ToolbarManager.cs

@ -7,6 +7,7 @@ using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Mvc.UI.Theming;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
using Volo.Abp.SimpleStateChecking;
namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars;
@ -37,6 +38,7 @@ public class ToolbarManager : IToolbarManager, ITransientDependency
using (var scope = ServiceProvider.CreateScope())
{
using (RequirePermissionsSimpleBatchStateChecker<ToolbarItem>.Use(new RequirePermissionsSimpleBatchStateChecker<ToolbarItem>()))
using (RequireFeaturesSimpleBatchStateChecker<ToolbarItem>.Use(new RequireFeaturesSimpleBatchStateChecker<ToolbarItem>()))
{
var context = new ToolbarConfigurationContext(ThemeManager.CurrentTheme, toolbar, scope.ServiceProvider);

1
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj

@ -30,6 +30,7 @@
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.AspNetCore.Mvc.UI.Bootstrap\Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.csproj" />
<ProjectReference Include="..\Volo.Abp.Features\Volo.Abp.Features.csproj" />
<ProjectReference Include="..\Volo.Abp.AspNetCore.Mvc.UI.Packages\Volo.Abp.AspNetCore.Mvc.UI.Packages.csproj" />
<ProjectReference Include="..\Volo.Abp.AspNetCore.Mvc.UI.Widgets\Volo.Abp.AspNetCore.Mvc.UI.Widgets.csproj" />
</ItemGroup>

15
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerBase.cs

@ -1,4 +1,5 @@
using System;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
@ -28,4 +29,16 @@ public abstract class FeatureCheckerBase : IFeatureChecker, ITransientDependency
);
}
}
public virtual async Task<Dictionary<string, bool>> IsEnabledAsync(string[] names)
{
var result = new Dictionary<string, bool>();
foreach (var name in names)
{
result[name] = await IsEnabledAsync(name);
}
return result;
}
}

27
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureSimpleStateCheckerExtensions.cs

@ -1,4 +1,4 @@
using JetBrains.Annotations;
using JetBrains.Annotations;
using Volo.Abp.SimpleStateChecking;
namespace Volo.Abp.Features;
@ -10,7 +10,7 @@ public static class FeatureSimpleStateCheckerExtensions
params string[] features)
where TState : IHasSimpleStateCheckers<TState>
{
state.RequireFeatures(true, features);
state.RequireFeatures(requiresAll: true, batchCheck: true, features);
return state;
}
@ -19,11 +19,32 @@ public static class FeatureSimpleStateCheckerExtensions
bool requiresAll,
params string[] features)
where TState : IHasSimpleStateCheckers<TState>
{
state.RequireFeatures(requiresAll: requiresAll, batchCheck: true, features);
return state;
}
public static TState RequireFeatures<TState>(
[NotNull] this TState state,
bool requiresAll,
bool batchCheck,
params string[] features)
where TState : IHasSimpleStateCheckers<TState>
{
Check.NotNull(state, nameof(state));
Check.NotNullOrEmpty(features, nameof(features));
state.StateCheckers.Add(new RequireFeaturesSimpleStateChecker<TState>(requiresAll, features));
if (batchCheck)
{
RequireFeaturesSimpleBatchStateChecker<TState>.Current.AddCheckModels(
new RequireFeaturesSimpleBatchStateCheckerModel<TState>(state, features, requiresAll));
state.StateCheckers.Add(RequireFeaturesSimpleBatchStateChecker<TState>.Current);
}
else
{
state.StateCheckers.Add(new RequireFeaturesSimpleStateChecker<TState>(requiresAll, features));
}
return state;
}
}

6
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeaturesSimpleStateCheckerSerializerContributor.cs

@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using System.Text.Json.Nodes;
using Volo.Abp.DependencyInjection;
using Volo.Abp.SimpleStateChecking;
@ -10,7 +10,7 @@ public class FeaturesSimpleStateCheckerSerializerContributor :
ISingletonDependency
{
public const string CheckerShortName = "F";
public string? SerializeToJson<TState>(ISimpleStateChecker<TState> checker)
where TState : IHasSimpleStateCheckers<TState>
{
@ -53,4 +53,4 @@ public class FeaturesSimpleStateCheckerSerializerContributor :
nameArray.Select(x => x!.ToString()).ToArray()
);
}
}
}

5
framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureChecker.cs

@ -1,4 +1,5 @@
using JetBrains.Annotations;
using System.Collections.Generic;
using JetBrains.Annotations;
using System.Threading.Tasks;
namespace Volo.Abp.Features;
@ -8,4 +9,6 @@ public interface IFeatureChecker
Task<string?> GetOrNullAsync([NotNull] string name);
Task<bool> IsEnabledAsync(string name);
Task<Dictionary<string, bool>> IsEnabledAsync([NotNull] string[] names);
}

77
framework/src/Volo.Abp.Features/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker.cs

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.SimpleStateChecking;
namespace Volo.Abp.Features;
public class RequireFeaturesSimpleBatchStateChecker<TState> : SimpleBatchStateCheckerBase<TState>
where TState : IHasSimpleStateCheckers<TState>
{
public static RequireFeaturesSimpleBatchStateChecker<TState> Current => _current.Value!;
private static readonly AsyncLocal<RequireFeaturesSimpleBatchStateChecker<TState>> _current = new();
private readonly List<RequireFeaturesSimpleBatchStateCheckerModel<TState>> _models;
static RequireFeaturesSimpleBatchStateChecker()
{
_current.Value = new RequireFeaturesSimpleBatchStateChecker<TState>();
}
public RequireFeaturesSimpleBatchStateChecker()
{
_models = new List<RequireFeaturesSimpleBatchStateCheckerModel<TState>>();
}
public RequireFeaturesSimpleBatchStateChecker<TState> AddCheckModels(
params RequireFeaturesSimpleBatchStateCheckerModel<TState>[] models)
{
Check.NotNullOrEmpty(models, nameof(models));
_models.AddRange(models);
return this;
}
public static IDisposable Use(RequireFeaturesSimpleBatchStateChecker<TState> checker)
{
var previousValue = Current;
_current.Value = checker;
return new DisposeAction(() => _current.Value = previousValue);
}
public override async Task<SimpleStateCheckerResult<TState>> IsEnabledAsync(
SimpleBatchStateCheckerContext<TState> context)
{
var featureChecker = context.ServiceProvider.GetRequiredService<IFeatureChecker>();
var result = new SimpleStateCheckerResult<TState>(context.States);
var relevantModels = _models
.Where(x => context.States.Any(s => s.Equals(x.State)))
.ToList();
var features = relevantModels.SelectMany(x => x.FeatureNames).Distinct().ToArray();
var featureValues = await featureChecker.IsEnabledAsync(features);
foreach (var state in context.States)
{
var model = relevantModels.FirstOrDefault(x => x.State.Equals(state));
if (model != null)
{
if (model.RequiresAll)
{
result[state] = model.FeatureNames.All(x => featureValues.TryGetValue(x, out var v) && v);
}
else
{
result[state] = model.FeatureNames.Any(x => featureValues.TryGetValue(x, out var v) && v);
}
}
}
return result;
}
}

23
framework/src/Volo.Abp.Features/Volo/Abp/Features/RequireFeaturesSimpleBatchStateCheckerModel.cs

@ -0,0 +1,23 @@
using Volo.Abp.SimpleStateChecking;
namespace Volo.Abp.Features;
public class RequireFeaturesSimpleBatchStateCheckerModel<TState>
where TState : IHasSimpleStateCheckers<TState>
{
public TState State { get; }
public string[] FeatureNames { get; }
public bool RequiresAll { get; }
public RequireFeaturesSimpleBatchStateCheckerModel(TState state, string[] featureNames, bool requiresAll = true)
{
Check.NotNull(state, nameof(state));
Check.NotNullOrEmpty(featureNames, nameof(featureNames));
State = state;
FeatureNames = featureNames;
RequiresAll = requiresAll;
}
}

1
framework/src/Volo.Abp.UI.Navigation/Volo.Abp.UI.Navigation.csproj

@ -23,6 +23,7 @@
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Authorization\Volo.Abp.Authorization.csproj" />
<ProjectReference Include="..\Volo.Abp.Features\Volo.Abp.Features.csproj" />
<ProjectReference Include="..\Volo.Abp.UI\Volo.Abp.UI.csproj" />
<ProjectReference Include="..\Volo.Abp.MultiTenancy\Volo.Abp.MultiTenancy.csproj" />
</ItemGroup>

3
framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/AbpUiNavigationModule.cs

@ -1,4 +1,5 @@
using Volo.Abp.Authorization;
using Volo.Abp.Features;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
@ -7,7 +8,7 @@ using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.UI.Navigation;
[DependsOn(typeof(AbpUiModule), typeof(AbpAuthorizationModule), typeof(AbpMultiTenancyModule))]
[DependsOn(typeof(AbpUiModule), typeof(AbpAuthorizationModule), typeof(AbpFeaturesModule), typeof(AbpMultiTenancyModule))]
public class AbpUiNavigationModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)

2
framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs

@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
using Volo.Abp.SimpleStateChecking;
namespace Volo.Abp.UI.Navigation;
@ -82,6 +83,7 @@ public class MenuManager : IMenuManager, ITransientDependency
using (var scope = ServiceScopeFactory.CreateScope())
{
using (RequirePermissionsSimpleBatchStateChecker<ApplicationMenuItem>.Use(new RequirePermissionsSimpleBatchStateChecker<ApplicationMenuItem>()))
using (RequireFeaturesSimpleBatchStateChecker<ApplicationMenuItem>.Use(new RequireFeaturesSimpleBatchStateChecker<ApplicationMenuItem>()))
{
var context = new MenuConfigurationContext(menu, scope.ServiceProvider);

106
framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker_Tests.cs

@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.MultiTenancy;
using Volo.Abp.SimpleStateChecking;
using Xunit;
namespace Volo.Abp.Features;
public class RequireFeaturesSimpleBatchStateChecker_Tests : FeatureTestBase
{
private readonly ISimpleStateCheckerManager<MyStateEntity> _simpleStateCheckerManager;
private readonly ICurrentTenant _currentTenant;
public RequireFeaturesSimpleBatchStateChecker_Tests()
{
_simpleStateCheckerManager = GetRequiredService<ISimpleStateCheckerManager<MyStateEntity>>();
_currentTenant = GetRequiredService<ICurrentTenant>();
}
[Fact]
public void Switch_Current_Checker_Test()
{
var checker = RequireFeaturesSimpleBatchStateChecker<MyStateEntity2>.Current;
checker.ShouldNotBeNull();
RequireFeaturesSimpleBatchStateChecker<MyStateEntity2> checker2 = null;
using (RequireFeaturesSimpleBatchStateChecker<MyStateEntity2>.Use(new RequireFeaturesSimpleBatchStateChecker<MyStateEntity2>()))
{
checker2 = RequireFeaturesSimpleBatchStateChecker<MyStateEntity2>.Current;
checker2.ShouldNotBeNull();
checker2.ShouldNotBe(checker);
}
checker2.ShouldNotBeNull();
checker2.ShouldNotBe(checker);
}
[Fact]
public async Task RequireFeaturesSimpleBatchStateChecker_Test()
{
// Tenant1: BooleanTestFeature1=true, BooleanTestFeature2=true
// Tenant2: no boolean features set → false
using (_currentTenant.Change(TestFeatureStore.Tenant1Id))
{
var myStateEntities = new MyStateEntity[]
{
new MyStateEntity().RequireFeatures(requiresAll: true, batchCheck: true, "BooleanTestFeature1"),
new MyStateEntity().RequireFeatures(requiresAll: true, batchCheck: true, "BooleanTestFeature2"),
new MyStateEntity().RequireFeatures(requiresAll: true, batchCheck: true, "BooleanTestFeature1", "BooleanTestFeature2"),
new MyStateEntity().RequireFeatures(requiresAll: true, batchCheck: true, "BooleanTestFeature1", "BooleanTestFeature2"),
};
var result = await _simpleStateCheckerManager.IsEnabledAsync(myStateEntities);
result.Count.ShouldBe(myStateEntities.Length);
result[myStateEntities[0]].ShouldBeTrue();
result[myStateEntities[1]].ShouldBeTrue();
result[myStateEntities[2]].ShouldBeTrue();
result[myStateEntities[3]].ShouldBeTrue();
}
using (_currentTenant.Change(TestFeatureStore.Tenant2Id))
{
var myStateEntities = new MyStateEntity[]
{
new MyStateEntity().RequireFeatures(requiresAll: true, batchCheck: true, "BooleanTestFeature1"),
new MyStateEntity().RequireFeatures(requiresAll: true, batchCheck: true, "BooleanTestFeature2"),
new MyStateEntity().RequireFeatures(requiresAll: true, batchCheck: true, "BooleanTestFeature1", "BooleanTestFeature2"),
new MyStateEntity().RequireFeatures(requiresAll: false, batchCheck: true, "BooleanTestFeature1", "BooleanTestFeature2"),
};
var result = await _simpleStateCheckerManager.IsEnabledAsync(myStateEntities);
result.Count.ShouldBe(myStateEntities.Length);
result[myStateEntities[0]].ShouldBeFalse();
result[myStateEntities[1]].ShouldBeFalse();
result[myStateEntities[2]].ShouldBeFalse();
result[myStateEntities[3]].ShouldBeFalse();
}
}
class MyStateEntity : IHasSimpleStateCheckers<MyStateEntity>
{
public List<ISimpleStateChecker<MyStateEntity>> StateCheckers { get; }
public MyStateEntity()
{
StateCheckers = new List<ISimpleStateChecker<MyStateEntity>>();
}
}
class MyStateEntity2 : IHasSimpleStateCheckers<MyStateEntity2>
{
public List<ISimpleStateChecker<MyStateEntity2>> StateCheckers { get; }
public MyStateEntity2()
{
StateCheckers = new List<ISimpleStateChecker<MyStateEntity2>>();
}
}
}

4
modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer_Tests.cs

@ -57,7 +57,7 @@ public class PermissionDefinitionSerializer_Tests : PermissionTestBase
.WithProperty("CustomProperty2", "CustomValue2")
.RequireAuthenticated() //For for testing, not so meaningful
.RequireGlobalFeatures("GlobalFeature1", "GlobalFeature2")
.RequireFeatures("Feature1", "Feature2")
.RequireFeatures(requiresAll: true, batchCheck: false, "Feature1", "Feature2")
.RequirePermissions(requiresAll: false, batchCheck: false,"Permission2", "Permission3");
// Act
@ -96,7 +96,7 @@ public class PermissionDefinitionSerializer_Tests : PermissionTestBase
.WithProperty("CustomProperty2", "CustomValue2")
.RequireAuthenticated() //For for testing, not so meaningful
.RequireGlobalFeatures("GlobalFeature1", "GlobalFeature2")
.RequireFeatures("Feature1", "Feature2")
.RequireFeatures(requiresAll: true, batchCheck: false, "Feature1", "Feature2")
.RequirePermissions(requiresAll: false, batchCheck: false,"Permission2", "Permission3");
// Act

Loading…
Cancel
Save