mirror of https://github.com/abpframework/abp.git
19 changed files with 568 additions and 13 deletions
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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)); |
|||
} |
|||
} |
|||
} |
|||
@ -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}]"; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public abstract class FeatureDefinitionProvider : IFeatureDefinitionProvider, ISingletonDependency |
|||
{ |
|||
public abstract void Define(IFeatureDefinitionContext context); |
|||
} |
|||
} |
|||
@ -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>(); |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public interface IFeatureDefinitionContext |
|||
{ |
|||
FeatureDefinition GetOrNull(string name); |
|||
|
|||
void Add(params FeatureDefinition[] definitions); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public interface IFeatureDefinitionProvider |
|||
{ |
|||
void Define(IFeatureDefinitionContext context); |
|||
} |
|||
} |
|||
@ -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 |
|||
); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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()); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue