Browse Source

Initial feature system implemented

pull/856/head
Halil ibrahim Kalkan 8 years ago
parent
commit
0589bbf389
  1. 12
      framework/src/Volo.Abp.Core/Volo/Abp/NameValue.cs
  2. 86
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureChecker.cs
  3. 37
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs
  4. 150
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs
  5. 38
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionContext.cs
  6. 76
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs
  7. 9
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionProvider.cs
  8. 17
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureOptions.cs
  9. 19
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureValue.cs
  10. 19
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureValueProvider.cs
  11. 12
      framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureChecker.cs
  12. 9
      framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionContext.cs
  13. 15
      framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs
  14. 7
      framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionProvider.cs
  15. 14
      framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureStore.cs
  16. 12
      framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureValueProvider.cs
  17. 22
      framework/src/Volo.Abp.Features/Volo/Abp/Features/NullFeatureStore.cs
  18. 25
      framework/src/Volo.Abp.Features/Volo/Abp/Features/TenantFeatureValueProvider.cs
  19. 2
      framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingProvider.cs

12
framework/src/Volo.Abp.Core/Volo/Abp/NameValue.cs

@ -8,17 +8,11 @@ namespace Volo.Abp
[Serializable]
public class NameValue : NameValue<string>
{
/// <summary>
/// Creates a new <see cref="NameValue"/>.
/// </summary>
public NameValue()
{
}
/// <summary>
/// Creates a new <see cref="NameValue"/>.
/// </summary>
public NameValue(string name, string value)
{
Name = name;
@ -42,17 +36,11 @@ namespace Volo.Abp
/// </summary>
public T Value { get; set; }
/// <summary>
/// Creates a new <see cref="NameValue"/>.
/// </summary>
public NameValue()
{
}
/// <summary>
/// Creates a new <see cref="NameValue"/>.
/// </summary>
public NameValue(string name, T value)
{
Name = name;

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

@ -0,0 +1,86 @@
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 FeatureChecker : IFeatureChecker, ITransientDependency
{
protected IFeatureDefinitionManager FeatureDefinitionManager { get; }
protected Lazy<List<IFeatureValueProvider>> Providers { get; }
protected FeatureOptions Options { get; }
public FeatureChecker(
IOptions<FeatureOptions> options,
IServiceProvider serviceProvider,
IFeatureDefinitionManager featureDefinitionManager)
{
FeatureDefinitionManager = featureDefinitionManager;
Options = options.Value;
Providers = new Lazy<List<IFeatureValueProvider>>(
() => Options
.ValueProviders
.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);
if (featureDefinition.AllowedProviders.Any())
{
providers = providers.Where(p => featureDefinition.AllowedProviders.Contains(p.Name));
}
return await GetOrNullValueFromProvidersAsync(providers, featureDefinition);
}
public async Task<bool> IsEnabledAsync(string name)
{
var value = await GetOrNullAsync(name);
if (value == null)
{
return false;
}
try
{
return bool.Parse(value);
}
catch (Exception ex)
{
throw new AbpException(
$"The value '{value}' for the feature '{name}' should be a boolean, but was not!",
ex
);
}
}
protected virtual async Task<string> GetOrNullValueFromProvidersAsync(
IEnumerable<IFeatureValueProvider> providers,
FeatureDefinition feature)
{
foreach (var provider in providers)
{
var value = await provider.GetOrNullAsync(feature);
if (value != null)
{
return value;
}
}
return null;
}
}
}

37
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs

@ -0,0 +1,37 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Volo.Abp.Threading;
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)
where T : struct
{
Check.NotNull(featureChecker, nameof(featureChecker));
Check.NotNull(name, nameof(name));
var value = await featureChecker.GetOrNullAsync(name);
return value?.To<T>() ?? defaultValue;
}
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)
where T : struct
{
return AsyncHelper.RunSync(() => featureChecker.GetAsync(name, defaultValue));
}
public static bool IsEnabled([NotNull] this IFeatureChecker featureChecker, [NotNull] string name)
{
return AsyncHelper.RunSync(() => featureChecker.IsEnabledAsync(name));
}
}
}

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

@ -0,0 +1,150 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using JetBrains.Annotations;
using Volo.Abp.Localization;
namespace Volo.Abp.Features
{
public class FeatureDefinition
{
/// <summary>
/// Unique name of the feature.
/// </summary>
[NotNull]
public string Name { get; }
[NotNull]
public ILocalizableString DisplayName
{
get => _displayName;
set => _displayName = Check.NotNull(value, nameof(value));
}
private ILocalizableString _displayName;
[CanBeNull]
public ILocalizableString Description { get; set; }
/// <summary>
/// Parent of this feature, if one exists.
/// If set, this feature can be enabled only if the parent is enabled.
/// </summary>
public FeatureDefinition Parent { get; private set; }
/// <summary>
/// List of child features.
/// </summary>
public IReadOnlyList<FeatureDefinition> Children => _children.ToImmutableList();
private readonly List<FeatureDefinition> _children;
/// <summary>
/// Default value of the feature.
/// </summary>
[CanBeNull]
public string DefaultValue { get; set; }
/// <summary>
/// Can clients see this feature and it's value.
/// Default: true.
/// </summary>
public bool IsVisibleToClients { get; set; }
/// <summary>
/// A list of allowed providers to get/set value of this feature.
/// An empty list indicates that all providers are allowed.
/// </summary>
public List<string> AllowedProviders { get; }
/// <summary>
/// Can be used to get/set custom properties for this feature.
/// </summary>
[NotNull]
public Dictionary<string, object> Properties { get; }
//TODO: Implement input type like old ABP!
public FeatureDefinition(
string name,
string defaultValue = null,
ILocalizableString displayName = null,
ILocalizableString description = null,
bool isVisibleToClients = true)
{
Name = name;
DefaultValue = defaultValue;
IsVisibleToClients = isVisibleToClients;
DisplayName = displayName ?? new FixedLocalizableString(name);
Description = description;
Properties = new Dictionary<string, object>();
AllowedProviders = new List<string>();
_children = new List<FeatureDefinition>();
}
/// <summary>
/// Sets a property in the <see cref="Properties"/> dictionary.
/// This is a shortcut for nested calls on this object.
/// </summary>
public virtual FeatureDefinition WithProperty(string key, object value)
{
Properties[key] = value;
return this;
}
/// <summary>
/// Sets a property in the <see cref="Properties"/> dictionary.
/// This is a shortcut for nested calls on this object.
/// </summary>
public virtual FeatureDefinition WithProviders(params string[] providers)
{
if (!providers.IsNullOrEmpty())
{
AllowedProviders.AddRange(providers);
}
return this;
}
/// <summary>
/// Adds a child feature.
/// </summary>
/// <returns>Returns a newly created child feature</returns>
public FeatureDefinition CreateChild(
string name,
string defaultValue = null,
ILocalizableString displayName = null,
ILocalizableString description = null,
bool isVisibleToClients = true)
{
var feature = new FeatureDefinition(
name,
defaultValue,
displayName,
description,
isVisibleToClients)
{
Parent = this
};
_children.Add(feature);
return feature;
}
public void RemoveChild(string name)
{
var featureToRemove = _children.FirstOrDefault(f => f.Name == name);
if (featureToRemove == null)
{
throw new AbpException($"Could not find a feature named '{name}' in the Children of this feature '{Name}'.");
}
featureToRemove.Parent = null;
_children.Remove(featureToRemove);
}
public override string ToString()
{
return $"[{nameof(FeatureDefinition)}: {Name}]";
}
}
}

38
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionContext.cs

@ -0,0 +1,38 @@
using System.Collections.Generic;
using System.Collections.Immutable;
namespace Volo.Abp.Features
{
public class FeatureDefinitionContext : IFeatureDefinitionContext
{
protected Dictionary<string, FeatureDefinition> Features { get; }
public FeatureDefinitionContext(Dictionary<string, FeatureDefinition> features)
{
Features = features;
}
public virtual FeatureDefinition GetOrNull(string name)
{
return Features.GetOrDefault(name);
}
public virtual IReadOnlyList<FeatureDefinition> GetAll()
{
return Features.Values.ToImmutableList();
}
public virtual void Add(params FeatureDefinition[] definitions)
{
if (definitions.IsNullOrEmpty())
{
return;
}
foreach (var definition in definitions)
{
Features[definition.Name] = definition;
}
}
}
}

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

@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Features
{
public class FeatureDefinitionManager : IFeatureDefinitionManager, ISingletonDependency
{
protected Lazy<List<IFeatureDefinitionProvider>> Providers { get; }
protected Lazy<IDictionary<string, FeatureDefinition>> FeatureDefinitions { get; }
protected FeatureOptions Options { get; }
private readonly IServiceProvider _serviceProvider;
public FeatureDefinitionManager(
IOptions<FeatureOptions> options,
IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
Options = options.Value;
Providers = new Lazy<List<IFeatureDefinitionProvider>>(CreateFeatureProviders, true);
FeatureDefinitions = new Lazy<IDictionary<string, FeatureDefinition>>(CreateFeatureDefinitions, true);
}
public virtual FeatureDefinition Get(string name)
{
Check.NotNull(name, nameof(name));
var feature = GetOrNull(name);
if (feature == null)
{
throw new AbpException("Undefined feature: " + name);
}
return feature;
}
public virtual IReadOnlyList<FeatureDefinition> GetAll()
{
return FeatureDefinitions.Value.Values.ToImmutableList();
}
public virtual FeatureDefinition GetOrNull(string name)
{
return FeatureDefinitions.Value.GetOrDefault(name);
}
protected virtual List<IFeatureDefinitionProvider> CreateFeatureProviders()
{
return Options
.DefinitionProviders
.Select(p => _serviceProvider.GetRequiredService(p) as IFeatureDefinitionProvider)
.ToList();
}
protected virtual IDictionary<string, FeatureDefinition> CreateFeatureDefinitions()
{
var features = new Dictionary<string, FeatureDefinition>();
foreach (var provider in Providers.Value)
{
provider.Define(new FeatureDefinitionContext(features));
}
return features;
}
}
}

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

@ -0,0 +1,9 @@
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Features
{
public abstract class FeatureDefinitionProvider : IFeatureDefinitionProvider, ISingletonDependency
{
public abstract void Define(IFeatureDefinitionContext context);
}
}

17
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureOptions.cs

@ -0,0 +1,17 @@
using Volo.Abp.Collections;
namespace Volo.Abp.Features
{
public class FeatureOptions
{
public ITypeList<IFeatureDefinitionProvider> DefinitionProviders { get; }
public ITypeList<IFeatureValueProvider> ValueProviders { get; }
public FeatureOptions()
{
DefinitionProviders = new TypeList<IFeatureDefinitionProvider>();
ValueProviders = new TypeList<IFeatureValueProvider>();
}
}
}

19
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureValue.cs

@ -0,0 +1,19 @@
using System;
namespace Volo.Abp.Features
{
[Serializable]
public class FeatureValue : NameValue
{
public FeatureValue()
{
}
public FeatureValue(string name, string value)
{
Name = name;
Value = value;
}
}
}

19
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureValueProvider.cs

@ -0,0 +1,19 @@
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Features
{
public abstract class FeatureValueProvider : IFeatureValueProvider, ISingletonDependency
{
public abstract string Name { get; }
protected IFeatureStore FeatureStore { get; }
protected FeatureValueProvider(IFeatureStore featureStore)
{
FeatureStore = featureStore;
}
public abstract Task<string> GetOrNullAsync(FeatureDefinition feature);
}
}

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

@ -0,0 +1,12 @@
using JetBrains.Annotations;
using System.Threading.Tasks;
namespace Volo.Abp.Features
{
public interface IFeatureChecker
{
Task<string> GetOrNullAsync([NotNull] string name);
Task<bool> IsEnabledAsync(string name);
}
}

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

@ -0,0 +1,9 @@
namespace Volo.Abp.Features
{
public interface IFeatureDefinitionContext
{
FeatureDefinition GetOrNull(string name);
void Add(params FeatureDefinition[] definitions);
}
}

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

@ -0,0 +1,15 @@
using System.Collections.Generic;
using JetBrains.Annotations;
namespace Volo.Abp.Features
{
public interface IFeatureDefinitionManager
{
[NotNull]
FeatureDefinition Get([NotNull] string name);
IReadOnlyList<FeatureDefinition> GetAll();
FeatureDefinition GetOrNull(string name);
}
}

7
framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionProvider.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.Features
{
public interface IFeatureDefinitionProvider
{
void Define(IFeatureDefinitionContext context);
}
}

14
framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureStore.cs

@ -0,0 +1,14 @@
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.Features
{
public interface IFeatureStore
{
Task<string> GetOrNullAsync(
[NotNull] string name,
[CanBeNull] string providerName,
[CanBeNull] string providerKey
);
}
}

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

@ -0,0 +1,12 @@
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.Features
{
public interface IFeatureValueProvider
{
string Name { get; }
Task<string> GetOrNullAsync([NotNull] FeatureDefinition feature);
}
}

22
framework/src/Volo.Abp.Features/Volo/Abp/Features/NullFeatureStore.cs

@ -0,0 +1,22 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Features
{
public class NullFeatureStore : IFeatureStore, ISingletonDependency
{
public ILogger<NullFeatureStore> Logger { get; set; }
public NullFeatureStore()
{
Logger = NullLogger<NullFeatureStore>.Instance;
}
public Task<string> GetOrNullAsync(string name, string providerName, string providerKey)
{
return Task.FromResult((string) null);
}
}
}

25
framework/src/Volo.Abp.Features/Volo/Abp/Features/TenantFeatureValueProvider.cs

@ -0,0 +1,25 @@
using System.Threading.Tasks;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.Features
{
public class TenantFeatureValueProvider : FeatureValueProvider
{
public const string ProviderName = "Tenant";
public override string Name => ProviderName;
protected ICurrentTenant CurrentTenant { get; }
public TenantFeatureValueProvider(IFeatureStore featureStore, ICurrentTenant currentTenant)
: base(featureStore)
{
CurrentTenant = currentTenant;
}
public override async Task<string> GetOrNullAsync(FeatureDefinition feature)
{
return await FeatureStore.GetOrNullAsync(feature.Name, Name, CurrentTenant.Id?.ToString());
}
}
}

2
framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingProvider.cs

@ -29,7 +29,7 @@ namespace Volo.Abp.Settings
Providers = new Lazy<List<ISettingValueProvider>>(
() => Options
.ValueProviders
.Select(c => serviceProvider.GetRequiredService(c) as ISettingValueProvider)
.Select(type => serviceProvider.GetRequiredService(type) as ISettingValueProvider)
.ToList(),
true
);

Loading…
Cancel
Save