diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Aspects/AbpCrossCuttingConcerns.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Aspects/AbpCrossCuttingConcerns.cs
index 35aa6d0811..4c8033a0cf 100644
--- a/framework/src/Volo.Abp.Core/Volo/Abp/Aspects/AbpCrossCuttingConcerns.cs
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/Aspects/AbpCrossCuttingConcerns.cs
@@ -12,6 +12,7 @@ namespace Volo.Abp.Aspects
public const string Validation = "AbpValidation";
public const string UnitOfWork = "AbpUnitOfWork";
public const string Authorization = "AbpAuthorization";
+ public const string FeatureChecking = "AbpFeatureChecking";
public static void AddApplied(object obj, params string[] concerns)
{
diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj b/framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj
index 57f909667b..243c94e66a 100644
--- a/framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj
+++ b/framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj
@@ -17,6 +17,7 @@
+
diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs
index 84b609b99f..fcdc29c454 100644
--- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs
+++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs
@@ -2,6 +2,7 @@
using Volo.Abp.Application.Services;
using Volo.Abp.Authorization;
using Volo.Abp.Domain;
+using Volo.Abp.Features;
using Volo.Abp.Http;
using Volo.Abp.Http.Modeling;
using Volo.Abp.Modularity;
@@ -20,7 +21,8 @@ namespace Volo.Abp.Application
typeof(AbpValidationModule),
typeof(AbpAuthorizationModule),
typeof(AbpHttpAbstractionsModule),
- typeof(AbpSettingsModule)
+ typeof(AbpSettingsModule),
+ typeof(AbpFeaturesModule)
)]
public class AbpDddApplicationModule : AbpModule
{
@@ -30,8 +32,8 @@ namespace Volo.Abp.Application
{
options.IgnoredInterfaces.AddIfNotContains(typeof(IRemoteService));
options.IgnoredInterfaces.AddIfNotContains(typeof(IApplicationService));
- options.IgnoredInterfaces.AddIfNotContains(typeof(IUnitOfWorkEnabled)); //TODO: Move to it's own module if possible?
- options.IgnoredInterfaces.AddIfNotContains(typeof(IAuthorizationEnabled)); //TODO: Move to it's own module if possible?
+ options.IgnoredInterfaces.AddIfNotContains(typeof(IUnitOfWorkEnabled));
+ options.IgnoredInterfaces.AddIfNotContains(typeof(IAuthorizationEnabled));
});
}
}
diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs
index 63a749e259..b4f53aff0a 100644
--- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs
+++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs
@@ -9,6 +9,7 @@ using Volo.Abp.Aspects;
using Volo.Abp.Auditing;
using Volo.Abp.Authorization;
using Volo.Abp.DependencyInjection;
+using Volo.Abp.Features;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
@@ -51,6 +52,8 @@ namespace Volo.Abp.Application.Services
public IAuthorizationService AuthorizationService { get; set; }
+ public IFeatureChecker FeatureChecker { get; set; }
+
protected IUnitOfWork CurrentUnitOfWork => UnitOfWorkManager?.Current;
protected ILogger Logger => _lazyLogger.Value;
diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs
index 35da247562..9065651b3b 100644
--- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs
+++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs
@@ -15,6 +15,7 @@ namespace Volo.Abp.Features
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
+ context.Services.OnRegistred(FeatureInterceptorRegistrar.RegisterIfNeeded);
AutoAddDefinitionProviders(context.Services);
}
diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/DisableFeatureCheckAttribute.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/DisableFeatureCheckAttribute.cs
new file mode 100644
index 0000000000..194f0eaf90
--- /dev/null
+++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/DisableFeatureCheckAttribute.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Volo.Abp.Features
+{
+ [AttributeUsage(AttributeTargets.Method)]
+ public class DisableFeatureCheckAttribute : Attribute
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs
index c43f8f04de..9d7f608c4b 100644
--- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs
+++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs
@@ -1,6 +1,8 @@
using System;
+using System.Collections.Generic;
using System.Threading.Tasks;
using JetBrains.Annotations;
+using Volo.Abp.Authorization;
using Volo.Abp.Threading;
namespace Volo.Abp.Features
@@ -43,5 +45,99 @@ namespace Volo.Abp.Features
{
return AsyncHelper.RunSync(() => featureChecker.IsEnabledAsync(name));
}
+
+ public static async Task IsEnabledAsync(this IFeatureChecker featureChecker, bool requiresAll, params string[] featureNames)
+ {
+ if (featureNames.IsNullOrEmpty())
+ {
+ return true;
+ }
+
+ if (requiresAll)
+ {
+ foreach (var featureName in featureNames)
+ {
+ if (!(await featureChecker.IsEnabledAsync(featureName)))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ foreach (var featureName in featureNames)
+ {
+ if (await featureChecker.IsEnabledAsync(featureName))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static bool IsEnabled(this IFeatureChecker featureChecker, bool requiresAll, params string[] featureNames)
+ {
+ return AsyncHelper.RunSync(() => featureChecker.IsEnabledAsync(requiresAll, featureNames));
+ }
+
+ public static async Task CheckEnabledAsync(this IFeatureChecker featureChecker, string featureName)
+ {
+ if (!(await featureChecker.IsEnabledAsync(featureName)))
+ {
+ throw new AbpAuthorizationException("Feature is not enabled: " + featureName);
+ }
+ }
+
+ public static void CheckEnabled(this IFeatureChecker featureChecker, string featureName)
+ {
+ if (!featureChecker.IsEnabled(featureName))
+ {
+ throw new AbpAuthorizationException("Feature is not enabled: " + featureName);
+ }
+ }
+
+ public static async Task CheckEnabledAsync(this IFeatureChecker featureChecker, bool requiresAll, params string[] featureNames)
+ {
+ if (featureNames.IsNullOrEmpty())
+ {
+ return;
+ }
+
+ if (requiresAll)
+ {
+ foreach (var featureName in featureNames)
+ {
+ if (!(await featureChecker.IsEnabledAsync(featureName)))
+ {
+ throw new AbpAuthorizationException(
+ "Required features are not enabled. All of these features must be enabled: " +
+ string.Join(", ", featureNames)
+ );
+ }
+ }
+ }
+ else
+ {
+ foreach (var featureName in featureNames)
+ {
+ if (await featureChecker.IsEnabledAsync(featureName))
+ {
+ return;
+ }
+ }
+
+ throw new AbpAuthorizationException(
+ "Required features are not enabled. At least one of these features must be enabled: " +
+ string.Join(", ", featureNames)
+ );
+ }
+ }
+
+ public static void CheckEnabled(this IFeatureChecker featureChecker, bool requiresAll, params string[] featureNames)
+ {
+ AsyncHelper.RunSync(() => featureChecker.CheckEnabledAsync(requiresAll, featureNames));
+ }
}
}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptor.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptor.cs
new file mode 100644
index 0000000000..2f684d99ef
--- /dev/null
+++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptor.cs
@@ -0,0 +1,56 @@
+using System.Threading.Tasks;
+using Volo.Abp.Aspects;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.DynamicProxy;
+using Volo.Abp.Threading;
+
+namespace Volo.Abp.Features
+{
+ public class FeatureInterceptor : AbpInterceptor, ITransientDependency
+ {
+ private readonly IMethodInvocationFeatureCheckerService _methodInvocationAuthorizationService;
+
+ public FeatureInterceptor(
+ IMethodInvocationFeatureCheckerService methodInvocationAuthorizationService)
+ {
+ _methodInvocationAuthorizationService = methodInvocationAuthorizationService;
+ }
+
+ public override void Intercept(IAbpMethodInvocation invocation)
+ {
+ if (AbpCrossCuttingConcerns.IsApplied(
+ invocation.TargetObject,
+ AbpCrossCuttingConcerns.FeatureChecking))
+ {
+ invocation.Proceed();
+ return;
+ }
+
+ AsyncHelper.RunSync(() => CheckFeaturesAsync(invocation));
+ invocation.Proceed();
+ }
+
+ public override async Task InterceptAsync(IAbpMethodInvocation invocation)
+ {
+ if (AbpCrossCuttingConcerns.IsApplied(
+ invocation.TargetObject,
+ AbpCrossCuttingConcerns.FeatureChecking))
+ {
+ await invocation.ProceedAsync();
+ return;
+ }
+
+ AsyncHelper.RunSync(() => CheckFeaturesAsync(invocation));
+ await invocation.ProceedAsync();
+ }
+
+ protected virtual Task CheckFeaturesAsync(IAbpMethodInvocation invocation)
+ {
+ return _methodInvocationAuthorizationService.CheckAsync(
+ new MethodInvocationFeatureCheckerContext(
+ invocation.Method
+ )
+ );
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs
new file mode 100644
index 0000000000..4ad03c9aff
--- /dev/null
+++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.Features
+{
+ public static class FeatureInterceptorRegistrar
+ {
+ public static void RegisterIfNeeded(IOnServiceRegistredContext context)
+ {
+ if (ShouldIntercept(context.ImplementationType))
+ {
+ context.Interceptors.TryAdd();
+ }
+ }
+
+ private static bool ShouldIntercept(Type type)
+ {
+ return type.IsDefined(typeof(RequiresFeatureAttribute), true) ||
+ AnyMethodRequiresFeatureAttribute(type);
+ }
+
+ private static bool AnyMethodRequiresFeatureAttribute(Type implementationType)
+ {
+ return implementationType
+ .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
+ .Any(HasRequiresFeatureAttribute);
+ }
+
+ private static bool HasRequiresFeatureAttribute(MemberInfo methodInfo)
+ {
+ return methodInfo.IsDefined(typeof(RequiresFeatureAttribute), true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IMethodInvocationFeatureCheckerService.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IMethodInvocationFeatureCheckerService.cs
new file mode 100644
index 0000000000..7d6be55c47
--- /dev/null
+++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IMethodInvocationFeatureCheckerService.cs
@@ -0,0 +1,11 @@
+using System.Threading.Tasks;
+
+namespace Volo.Abp.Features
+{
+ public interface IMethodInvocationFeatureCheckerService
+ {
+ Task CheckAsync(
+ MethodInvocationFeatureCheckerContext context
+ );
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerContext.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerContext.cs
new file mode 100644
index 0000000000..81bbecfe68
--- /dev/null
+++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerContext.cs
@@ -0,0 +1,14 @@
+using System.Reflection;
+
+namespace Volo.Abp.Features
+{
+ public class MethodInvocationFeatureCheckerContext
+ {
+ public MethodInfo Method { get; }
+
+ public MethodInvocationFeatureCheckerContext(MethodInfo method)
+ {
+ Method = method;
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerService.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerService.cs
new file mode 100644
index 0000000000..28ac25fdd5
--- /dev/null
+++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerService.cs
@@ -0,0 +1,51 @@
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.Features
+{
+ public class MethodInvocationFeatureCheckerService : IMethodInvocationFeatureCheckerService, ITransientDependency
+ {
+ private readonly IFeatureChecker _featureChecker;
+
+ public MethodInvocationFeatureCheckerService(
+ IFeatureChecker featureChecker)
+ {
+ _featureChecker = featureChecker;
+ }
+
+ public async Task CheckAsync(MethodInvocationFeatureCheckerContext context)
+ {
+ if (IsFeatureCheckDisabled(context))
+ {
+ return;
+ }
+
+ foreach (var requiresFeatureAttribute in GetRequiredFeatureAttributes(context))
+ {
+ await _featureChecker.CheckEnabledAsync(requiresFeatureAttribute.RequiresAll, requiresFeatureAttribute.Features);
+ }
+ }
+
+ protected virtual bool IsFeatureCheckDisabled(MethodInvocationFeatureCheckerContext context)
+ {
+ return context.Method
+ .GetCustomAttributes(true)
+ .OfType()
+ .Any();
+ }
+
+ protected virtual RequiresFeatureAttribute[] GetRequiredFeatureAttributes(MethodInvocationFeatureCheckerContext context)
+ {
+ var classAttributes = context.Method.DeclaringType
+ .GetCustomAttributes(true)
+ .OfType();
+
+ var methodAttributes = context.Method
+ .GetCustomAttributes(true)
+ .OfType();
+
+ return classAttributes.Union(methodAttributes).ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/RequiresFeatureAttribute.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/RequiresFeatureAttribute.cs
new file mode 100644
index 0000000000..2f5b62c128
--- /dev/null
+++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/RequiresFeatureAttribute.cs
@@ -0,0 +1,33 @@
+using System;
+
+namespace Volo.Abp.Features
+{
+ ///
+ /// This attribute can be used on a class/method to declare that given class/method is available
+ /// only if required feature(s) are enabled.
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+ public class RequiresFeatureAttribute : Attribute
+ {
+ ///
+ /// A list of features to be checked if they are enabled.
+ ///
+ public string[] Features { get; }
+
+ ///
+ /// If this property is set to true, all of the must be enabled.
+ /// If it's false, at least one of the must be enabled.
+ /// Default: false.
+ ///
+ public bool RequiresAll { get; set; }
+
+ ///
+ /// Creates a new instance of class.
+ ///
+ /// A list of features to be checked if they are enabled
+ public RequiresFeatureAttribute(params string[] features)
+ {
+ Features = features ?? Array.Empty();
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationException.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Authorization/AbpAuthorizationException.cs
similarity index 100%
rename from framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationException.cs
rename to framework/src/Volo.Abp.Security/Volo/Abp/Authorization/AbpAuthorizationException.cs
diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/ClassFeatureTestService.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/ClassFeatureTestService.cs
new file mode 100644
index 0000000000..425f69337f
--- /dev/null
+++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/ClassFeatureTestService.cs
@@ -0,0 +1,23 @@
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.Features
+{
+ [RequiresFeature("BooleanTestFeature1")]
+ public class ClassFeatureTestService : ITransientDependency
+ {
+ /* Since this class is used with the class reference,
+ * need to virtual keywords, otherwise dynamic proxy can not work.
+ */
+
+ [RequiresFeature("BooleanTestFeature2")]
+ public virtual int Feature2()
+ {
+ return 42;
+ }
+
+ public virtual void NoAdditionalFeature()
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureChecker_Tests.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureChecker_Tests.cs
index bafbb25811..7ea01268a0 100644
--- a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureChecker_Tests.cs
+++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureChecker_Tests.cs
@@ -5,7 +5,7 @@ using Xunit;
namespace Volo.Abp.Features
{
- public class FeatureChecker_Tests : AbpFeaturesTestBase
+ public class FeatureChecker_Tests : FeatureTestBase
{
private readonly IFeatureChecker _featureChecker;
private readonly ICurrentTenant _currentTenant;
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 beb94ec283..5a4bbc9ea1 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
@@ -3,7 +3,7 @@ using Xunit;
namespace Volo.Abp.Features
{
- public class FeatureDefinitionManager_Tests : AbpFeaturesTestBase
+ public class FeatureDefinitionManager_Tests : FeatureTestBase
{
private readonly IFeatureDefinitionManager _featureDefinitionManager;
diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureInterceptor_Tests.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureInterceptor_Tests.cs
new file mode 100644
index 0000000000..159e2ff241
--- /dev/null
+++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureInterceptor_Tests.cs
@@ -0,0 +1,76 @@
+using Shouldly;
+using System;
+using System.Threading.Tasks;
+using Volo.Abp.Authorization;
+using Volo.Abp.MultiTenancy;
+using Xunit;
+
+namespace Volo.Abp.Features
+{
+ public class FeatureInterceptor_Tests : FeatureTestBase
+ {
+ private readonly ClassFeatureTestService _classFeatureTestService;
+ private readonly IMethodFeatureTestService _methodFeatureTestService;
+ private readonly ICurrentTenant _currentTenant;
+
+ public FeatureInterceptor_Tests()
+ {
+ _classFeatureTestService = GetRequiredService();
+ _methodFeatureTestService = GetRequiredService();
+ _currentTenant = GetRequiredService();
+ }
+
+ [Theory]
+ [InlineData(null)] //Features were not enabled for null tenantid
+ [InlineData(TestFeatureStore.Tenant2IdValue)] //Features were not enabled for Tenant 2
+ public async Task Should_Not_Allow_To_Method_Calls_If_Related_Features_Were_Not_Enabled(string tenantIdValue)
+ {
+ using (_currentTenant.Change(ParseNullableGuid(tenantIdValue)))
+ {
+ Assert.Throws(() =>
+ {
+ _classFeatureTestService.NoAdditionalFeature();
+ });
+
+ Assert.Throws(() =>
+ {
+ _classFeatureTestService.Feature2();
+ });
+
+ await Assert.ThrowsAsync(async () =>
+ {
+ await _methodFeatureTestService.Feature1Async();
+ });
+ }
+ }
+
+ [Fact]
+ public async Task Should_Allow_To_Method_Calls_If_Related_Features_Were_Enabled()
+ {
+ //Features were enabled for Tenant 1
+ using (_currentTenant.Change(TestFeatureStore.Tenant1Id))
+ {
+ _classFeatureTestService.NoAdditionalFeature();
+ _classFeatureTestService.Feature2().ShouldBe(42);
+ (await _methodFeatureTestService.Feature1Async()).ShouldBe(42);
+ }
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData(TestFeatureStore.Tenant1IdValue)]
+ [InlineData(TestFeatureStore.Tenant2IdValue)]
+ public async Task Should_Allow_To_Method_Calls_For_Those_Have_No_RequiresFeature_Attributes(string tenantIdValue)
+ {
+ using (_currentTenant.Change(ParseNullableGuid(tenantIdValue)))
+ {
+ await _methodFeatureTestService.NonFeatureAsync();
+ }
+ }
+
+ private static Guid? ParseNullableGuid(string tenantIdValue)
+ {
+ return tenantIdValue == null ? (Guid?)null : new Guid(tenantIdValue);
+ }
+ }
+}
diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/AbpFeaturesTestBase.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureTestBase.cs
similarity index 71%
rename from framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/AbpFeaturesTestBase.cs
rename to framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureTestBase.cs
index a3c9170798..b12aa9f526 100644
--- a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/AbpFeaturesTestBase.cs
+++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureTestBase.cs
@@ -1,6 +1,6 @@
namespace Volo.Abp.Features
{
- public class AbpFeaturesTestBase : AbpIntegratedTest
+ public class FeatureTestBase : AbpIntegratedTest
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/IMethodFeatureTestService.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/IMethodFeatureTestService.cs
new file mode 100644
index 0000000000..7bc16b7e04
--- /dev/null
+++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/IMethodFeatureTestService.cs
@@ -0,0 +1,11 @@
+using System.Threading.Tasks;
+
+namespace Volo.Abp.Features
+{
+ public interface IMethodFeatureTestService
+ {
+ Task Feature1Async();
+
+ Task NonFeatureAsync();
+ }
+}
\ No newline at end of file
diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/MethodFeatureTestService.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/MethodFeatureTestService.cs
new file mode 100644
index 0000000000..e627e0c5f1
--- /dev/null
+++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/MethodFeatureTestService.cs
@@ -0,0 +1,23 @@
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.Features
+{
+ public class MethodFeatureTestService : ITransientDependency, IMethodFeatureTestService
+ {
+ /* Since this class is used over an interface (IMethodFeatureTestService),
+ * no need to virtual keywords, dynamic proxy can work.
+ */
+
+ [RequiresFeature("BooleanTestFeature1")]
+ public Task Feature1Async()
+ {
+ return Task.FromResult(42);
+ }
+
+ public Task NonFeatureAsync()
+ {
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureDefinitionProvider.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureDefinitionProvider.cs
index aceb39b6f4..0554120309 100644
--- a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureDefinitionProvider.cs
+++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureDefinitionProvider.cs
@@ -6,6 +6,7 @@
{
var group = context.AddGroup("Test Group");
group.AddFeature("BooleanTestFeature1");
+ group.AddFeature("BooleanTestFeature2");
group.AddFeature("IntegerTestFeature1", defaultValue: "1");
}
}
diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureStore.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureStore.cs
index 2d68b75fda..87fef33a8d 100644
--- a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureStore.cs
+++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureStore.cs
@@ -8,8 +8,11 @@ namespace Volo.Abp.Features
{
public class TestFeatureStore : IFeatureStore, ISingletonDependency
{
- public static Guid Tenant1Id = new Guid("f460fcf7-f944-469a-967b-3b2463323dfe");
- public static Guid Tenant2Id = new Guid("e10428ad-4608-4c34-a304-6f82502156f2");
+ public const string Tenant1IdValue = "f460fcf7-f944-469a-967b-3b2463323dfe";
+ public const string Tenant2IdValue = "e10428ad-4608-4c34-a304-6f82502156f2";
+
+ public static Guid Tenant1Id = new Guid(Tenant1IdValue);
+ public static Guid Tenant2Id = new Guid(Tenant2IdValue);
private readonly List _settingRecords;
@@ -18,6 +21,7 @@ namespace Volo.Abp.Features
_settingRecords = new List
{
new SettingRecord("BooleanTestFeature1", TenantFeatureValueProvider.ProviderName, Tenant1Id.ToString(), "true"),
+ new SettingRecord("BooleanTestFeature2", TenantFeatureValueProvider.ProviderName, Tenant1Id.ToString(), "true"),
new SettingRecord("IntegerTestFeature1", TenantFeatureValueProvider.ProviderName, Tenant2Id.ToString(), "34")
};
}