Implemented FeatureDefinitionManager

pull/859/head
Halil ibrahim Kalkan 7 years ago
parent 5d1b179383
commit 368479e51f

@ -226,7 +226,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.Cli
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.Contracts", "src\Volo.Abp.AspNetCore.Mvc.Contracts\Volo.Abp.AspNetCore.Mvc.Contracts.csproj", "{88F6D091-CA16-4B71-9499-8D5B8FA2E712}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Features", "src\Volo.Abp.Features\Volo.Abp.Features.csproj", "{01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Features", "src\Volo.Abp.Features\Volo.Abp.Features.csproj", "{01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Features.Tests", "test\Volo.Abp.Features.Tests\Volo.Abp.Features.Tests.csproj", "{575BEFA1-19C2-49B1-8D31-B5D4472328DE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -646,6 +648,10 @@ Global
{01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}.Release|Any CPU.Build.0 = Release|Any CPU
{575BEFA1-19C2-49B1-8D31-B5D4472328DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{575BEFA1-19C2-49B1-8D31-B5D4472328DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{575BEFA1-19C2-49B1-8D31-B5D4472328DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{575BEFA1-19C2-49B1-8D31-B5D4472328DE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -756,6 +762,7 @@ Global
{E803DDB8-81EA-454B-9A66-9C2941100B67} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{88F6D091-CA16-4B71-9499-8D5B8FA2E712} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{01E3D389-8872-4EB1-9D3D-13B6ED54DE0E} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{575BEFA1-19C2-49B1-8D31-B5D4472328DE} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

@ -16,6 +16,7 @@ namespace Volo.Abp.Authorization
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.OnRegistred(AuthorizationInterceptorRegistrar.RegisterIfNeeded);
//TODO: Auto Add Providers to PermissionOptions just like did in AbpFeaturesModule.AutoAddProviders
}
public override void ConfigureServices(ServiceConfigurationContext context)

@ -1,4 +1,7 @@
using Volo.Abp.Localization;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
@ -10,9 +13,27 @@ namespace Volo.Abp.Features
)]
public class AbpFeaturesModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
public override void PreConfigureServices(ServiceConfigurationContext context)
{
AutoAddProviders(context.Services);
}
private static void AutoAddProviders(IServiceCollection services)
{
var featureDefinitionProviders = new List<Type>();
services.OnRegistred(context =>
{
if (typeof(IFeatureDefinitionProvider).IsAssignableFrom(context.ImplementationType))
{
featureDefinitionProviders.Add(context.ImplementationType);
}
});
services.Configure<FeatureOptions>(options =>
{
options.DefinitionProviders.AddIfNotContains(featureDefinitionProviders);
});
}
}
}

@ -10,33 +10,37 @@ namespace Volo.Abp.Features
{
public class FeatureChecker : IFeatureChecker, ITransientDependency
{
protected IFeatureDefinitionManager FeatureDefinitionManager { get; }
protected Lazy<List<IFeatureValueProvider>> Providers { get; }
protected FeatureOptions Options { get; }
protected IServiceProvider ServiceProvider { get; }
protected IFeatureDefinitionManager FeatureDefinitionManager { get; }
protected List<IFeatureValueProvider> Providers => _providers.Value;
private readonly Lazy<List<IFeatureValueProvider>> _providers;
public FeatureChecker(
IOptions<FeatureOptions> options,
IServiceProvider serviceProvider,
IFeatureDefinitionManager featureDefinitionManager)
{
ServiceProvider = serviceProvider;
FeatureDefinitionManager = featureDefinitionManager;
Options = options.Value;
Providers = new Lazy<List<IFeatureValueProvider>>(
_providers = new Lazy<List<IFeatureValueProvider>>(
() => Options
.ValueProviders
.Select(type => serviceProvider.GetRequiredService(type) as IFeatureValueProvider)
.Select(type => ServiceProvider.GetRequiredService(type) as IFeatureValueProvider)
.ToList(),
true
);
}
public virtual async Task<string> GetOrNullAsync(string name)
{
var featureDefinition = FeatureDefinitionManager.Get(name);
var providers = Enumerable
.Reverse(Providers.Value);
.Reverse(Providers);
if (featureDefinition.AllowedProviders.Any())
{

@ -7,7 +7,10 @@ namespace Volo.Abp.Features
{
public static class FeatureCheckerExtensions
{
public static async Task<T> GetAsync<T>([NotNull] this IFeatureChecker featureChecker, [NotNull] string name, T defaultValue = default)
public static async Task<T> GetAsync<T>(
[NotNull] this IFeatureChecker featureChecker,
[NotNull] string name,
T defaultValue = default)
where T : struct
{
Check.NotNull(featureChecker, nameof(featureChecker));
@ -17,19 +20,26 @@ namespace Volo.Abp.Features
return value?.To<T>() ?? defaultValue;
}
public static string GetOrNull([NotNull] this IFeatureChecker featureChecker, [NotNull] string name)
public static string GetOrNull(
[NotNull] this IFeatureChecker featureChecker,
[NotNull] string name)
{
Check.NotNull(featureChecker, nameof(featureChecker));
return AsyncHelper.RunSync(() => featureChecker.GetOrNullAsync(name));
}
public static T Get<T>([NotNull] this IFeatureChecker featureChecker, [NotNull] string name, T defaultValue = default)
public static T Get<T>(
[NotNull] this IFeatureChecker featureChecker,
[NotNull] string name,
T defaultValue = default)
where T : struct
{
return AsyncHelper.RunSync(() => featureChecker.GetAsync(name, defaultValue));
}
public static bool IsEnabled([NotNull] this IFeatureChecker featureChecker, [NotNull] string name)
public static bool IsEnabled(
[NotNull] this IFeatureChecker featureChecker,
[NotNull] string name)
{
return AsyncHelper.RunSync(() => featureChecker.IsEnabledAsync(name));
}

@ -55,6 +55,20 @@ namespace Volo.Abp.Features
/// </summary>
public List<string> AllowedProviders { get; }
/// <summary>
/// Gets/sets a key-value on the <see cref="Properties"/>.
/// </summary>
/// <param name="name">Name of the property</param>
/// <returns>
/// Returns the value in the <see cref="Properties"/> dictionary by given <see cref="name"/>.
/// Returns null if given <see cref="name"/> is not present in the <see cref="Properties"/> dictionary.
/// </returns>
public object this[string name]
{
get => Properties.GetOrDefault(name);
set => Properties[name] = value;
}
/// <summary>
/// Can be used to get/set custom properties for this feature.
/// </summary>

@ -1,38 +1,51 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using Volo.Abp.Localization;
namespace Volo.Abp.Features
{
public class FeatureDefinitionContext : IFeatureDefinitionContext
{
protected Dictionary<string, FeatureDefinition> Features { get; }
internal Dictionary<string, FeatureGroupDefinition> Groups { get; }
public FeatureDefinitionContext(Dictionary<string, FeatureDefinition> features)
public FeatureDefinitionContext()
{
Features = features;
Groups = new Dictionary<string, FeatureGroupDefinition>();
}
public virtual FeatureDefinition GetOrNull(string name)
public FeatureGroupDefinition AddGroup(string name, ILocalizableString displayName = null)
{
return Features.GetOrDefault(name);
}
Check.NotNull(name, nameof(name));
public virtual IReadOnlyList<FeatureDefinition> GetAll()
{
return Features.Values.ToImmutableList();
if (Groups.ContainsKey(name))
{
throw new AbpException($"There is already an existing permission group with name: {name}");
}
return Groups[name] = new FeatureGroupDefinition(name, displayName);
}
public virtual void Add(params FeatureDefinition[] definitions)
public FeatureGroupDefinition GetGroupOrNull(string name)
{
if (definitions.IsNullOrEmpty())
Check.NotNull(name, nameof(name));
if (!Groups.ContainsKey(name))
{
return;
return null;
}
foreach (var definition in definitions)
return Groups[name];
}
public void RemoveGroup(string name)
{
Check.NotNull(name, nameof(name));
if (!Groups.ContainsKey(name))
{
Features[definition.Name] = definition;
throw new AbpException($"Undefined feature group: '{name}'.");
}
Groups.Remove(name);
}
}
}

@ -10,9 +10,11 @@ namespace Volo.Abp.Features
{
public class FeatureDefinitionManager : IFeatureDefinitionManager, ISingletonDependency
{
protected Lazy<List<IFeatureDefinitionProvider>> Providers { get; }
protected IDictionary<string, FeatureGroupDefinition> FeatureGroupDefinitions => _lazyFeatureGroupDefinitions.Value;
private readonly Lazy<Dictionary<string, FeatureGroupDefinition>> _lazyFeatureGroupDefinitions;
protected Lazy<IDictionary<string, FeatureDefinition>> FeatureDefinitions { get; }
protected IDictionary<string, FeatureDefinition> FeatureDefinitions => _lazyFeatureDefinitions.Value;
private readonly Lazy<Dictionary<string, FeatureDefinition>> _lazyFeatureDefinitions;
protected FeatureOptions Options { get; }
@ -25,8 +27,15 @@ namespace Volo.Abp.Features
_serviceProvider = serviceProvider;
Options = options.Value;
Providers = new Lazy<List<IFeatureDefinitionProvider>>(CreateFeatureProviders, true);
FeatureDefinitions = new Lazy<IDictionary<string, FeatureDefinition>>(CreateFeatureDefinitions, true);
_lazyFeatureDefinitions = new Lazy<Dictionary<string, FeatureDefinition>>(
CreateFeatureDefinitions,
isThreadSafe: true
);
_lazyFeatureGroupDefinitions = new Lazy<Dictionary<string, FeatureGroupDefinition>>(
CreateFeatureGroupDefinitions,
isThreadSafe:true
);
}
public virtual FeatureDefinition Get(string name)
@ -45,32 +54,64 @@ namespace Volo.Abp.Features
public virtual IReadOnlyList<FeatureDefinition> GetAll()
{
return FeatureDefinitions.Value.Values.ToImmutableList();
return FeatureDefinitions.Values.ToImmutableList();
}
public virtual FeatureDefinition GetOrNull(string name)
{
return FeatureDefinitions.Value.GetOrDefault(name);
return FeatureDefinitions.GetOrDefault(name);
}
protected virtual List<IFeatureDefinitionProvider> CreateFeatureProviders()
protected virtual Dictionary<string, FeatureDefinition> CreateFeatureDefinitions()
{
return Options
.DefinitionProviders
.Select(p => _serviceProvider.GetRequiredService(p) as IFeatureDefinitionProvider)
.ToList();
var features = new Dictionary<string, FeatureDefinition>();
foreach (var groupDefinition in FeatureGroupDefinitions.Values)
{
foreach (var feature in groupDefinition.Features)
{
AddFeatureToDictionaryRecursively(features, feature);
}
}
return features;
}
protected virtual IDictionary<string, FeatureDefinition> CreateFeatureDefinitions()
protected virtual void AddFeatureToDictionaryRecursively(
Dictionary<string, FeatureDefinition> features,
FeatureDefinition feature)
{
var features = new Dictionary<string, FeatureDefinition>();
if (features.ContainsKey(feature.Name))
{
throw new AbpException("Duplicate feature name: " + feature.Name);
}
features[feature.Name] = feature;
foreach (var provider in Providers.Value)
foreach (var child in feature.Children)
{
provider.Define(new FeatureDefinitionContext(features));
AddFeatureToDictionaryRecursively(features, child);
}
}
return features;
protected virtual Dictionary<string, FeatureGroupDefinition> CreateFeatureGroupDefinitions()
{
var context = new FeatureDefinitionContext();
using (var scope = _serviceProvider.CreateScope())
{
var providers = Options
.DefinitionProviders
.Select(p => scope.ServiceProvider.GetRequiredService(p) as IFeatureDefinitionProvider)
.ToList();
foreach (var provider in providers)
{
provider.Define(context);
}
}
return context.Groups;
}
}
}

@ -0,0 +1,108 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using Volo.Abp.Localization;
namespace Volo.Abp.Features
{
public class FeatureGroupDefinition
{
/// <summary>
/// Unique name of the group.
/// </summary>
public string Name { get; }
public Dictionary<string, object> Properties { get; }
public ILocalizableString DisplayName
{
get => _displayName;
set => _displayName = Check.NotNull(value, nameof(value));
}
private ILocalizableString _displayName;
public IReadOnlyList<FeatureDefinition> Features => _features.ToImmutableList();
private readonly List<FeatureDefinition> _features;
/// <summary>
/// Gets/sets a key-value on the <see cref="Properties"/>.
/// </summary>
/// <param name="name">Name of the property</param>
/// <returns>
/// Returns the value in the <see cref="Properties"/> dictionary by given <see cref="name"/>.
/// Returns null if given <see cref="name"/> is not present in the <see cref="Properties"/> dictionary.
/// </returns>
public object this[string name]
{
get => Properties.GetOrDefault(name);
set => Properties[name] = value;
}
protected internal FeatureGroupDefinition(
string name,
ILocalizableString displayName = null)
{
Name = name;
DisplayName = displayName ?? new FixedLocalizableString(Name);
Properties = new Dictionary<string, object>();
_features = new List<FeatureDefinition>();
}
public virtual FeatureDefinition AddFeature(
string name,
string defaultValue = null,
ILocalizableString displayName = null,
ILocalizableString description = null,
bool isVisibleToClients = true)
{
var feature = new FeatureDefinition(
name,
defaultValue,
displayName,
description,
isVisibleToClients
);
_features.Add(feature);
return feature;
}
public virtual List<FeatureDefinition> GetFeaturesWithChildren()
{
var features = new List<FeatureDefinition>();
foreach (var feature in _features)
{
AddFeatureToListRecursively(features, feature);
}
return features;
}
/// <summary>
/// Sets a property in the <see cref="Properties"/> dictionary.
/// This is a shortcut for nested calls on this object.
/// </summary>
public virtual FeatureGroupDefinition WithProperty(string key, object value)
{
Properties[key] = value;
return this;
}
private void AddFeatureToListRecursively(List<FeatureDefinition> features, FeatureDefinition feature)
{
features.Add(feature);
foreach (var child in feature.Children)
{
AddFeatureToListRecursively(features, child);
}
}
public override string ToString()
{
return $"[{nameof(FeatureGroupDefinition)} {Name}]";
}
}
}

@ -1,9 +1,14 @@
namespace Volo.Abp.Features
using JetBrains.Annotations;
using Volo.Abp.Localization;
namespace Volo.Abp.Features
{
public interface IFeatureDefinitionContext
{
FeatureDefinition GetOrNull(string name);
FeatureGroupDefinition AddGroup([NotNull] string name, ILocalizableString displayName = null);
void Add(params FeatureDefinition[] definitions);
FeatureGroupDefinition GetGroupOrNull(string name);
void RemoveGroup(string name);
}
}

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<LangVersion>latest</LangVersion>
<AssemblyName>Volo.Abp.Features.Tests</AssemblyName>
<PackageId>Volo.Abp.Features.Tests</PackageId>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.Features\Volo.Abp.Features.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
</ItemGroup>
</Project>

@ -0,0 +1,10 @@
namespace Volo.Abp.Features
{
public class AbpFeaturesTestBase : AbpIntegratedTest<AbpFeaturesTestModule>
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
options.UseAutofac();
}
}
}

@ -0,0 +1,18 @@
using Volo.Abp.Autofac;
using Volo.Abp.Modularity;
namespace Volo.Abp.Features
{
[DependsOn(
typeof(AbpFeaturesModule),
typeof(AbpTestBaseModule),
typeof(AbpAutofacModule)
)]
public class AbpFeaturesTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
}
}
}

@ -0,0 +1,35 @@
using Shouldly;
using Xunit;
namespace Volo.Abp.Features
{
public class FeatureDefinitionManager_Tests : AbpFeaturesTestBase
{
private readonly IFeatureDefinitionManager _featureDefinitionManager;
public FeatureDefinitionManager_Tests()
{
_featureDefinitionManager = GetRequiredService<IFeatureDefinitionManager>();
}
[Fact]
public void Should_Get_Defined_Features()
{
_featureDefinitionManager.GetOrNull("BooleanTestFeature1").ShouldNotBeNull();
_featureDefinitionManager.Get("BooleanTestFeature1").Name.ShouldBe("BooleanTestFeature1");
_featureDefinitionManager.GetOrNull("IntegerTestFeature1").ShouldNotBeNull();
_featureDefinitionManager.Get("IntegerTestFeature1").Name.ShouldBe("IntegerTestFeature1");
}
[Fact]
public void Should_Not_Get_Undefined_Features()
{
_featureDefinitionManager.GetOrNull("UndefinedFeature").ShouldBeNull();
Assert.Throws<AbpException>(() =>
{
_featureDefinitionManager.Get("UndefinedFeature");
});
}
}
}

@ -0,0 +1,12 @@
namespace Volo.Abp.Features
{
public class TestFeatureDefinitionProvider : FeatureDefinitionProvider
{
public override void Define(IFeatureDefinitionContext context)
{
var group = context.AddGroup("Test Group");
group.AddFeature("BooleanTestFeature1");
group.AddFeature("IntegerTestFeature1", defaultValue: "1");
}
}
}
Loading…
Cancel
Save