From 03f6dc6792b8dbf38a25058aeb0c34d6a3c10436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 9 Apr 2021 17:32:51 +0300 Subject: [PATCH 1/2] Allow to directly enable/disable features on the GlobalFeatureManager Also revised the document --- docs/en/Global-Features.md | 99 +++++++------------ .../GlobalFeatures/GlobalFeatureManager.cs | 28 +++++- 2 files changed, 62 insertions(+), 65 deletions(-) diff --git a/docs/en/Global-Features.md b/docs/en/Global-Features.md index cfbf3f98b7c..e0c7d892029 100644 --- a/docs/en/Global-Features.md +++ b/docs/en/Global-Features.md @@ -1,5 +1,9 @@ # Global Features -The purpose of the Global Feature System is to **add a module to your application but disable the features you don't want to use** (or enable only the ones you need). Notice that the features are not determined on runtime, you must select the features **on development time**. Because it will not create database tables, APIs and other stuff for unused features, which is not possible to change then on the runtime. +Global Feature system is used to enable/disable an application feature on development time. It is done on the development time, because some **services** (e.g. controllers) are removed from the application model and **database tables** are not created for the disabled features, which is not possible on runtime. + +Global Features system is especially useful if you want to develop a reusable application module with optional features. If the final application doesn't want to use some of the features, it can disable these features. + +> If you are looking for a system to enable/disable features based on current tenant or any other condition, please see the [Features](Features.md) document. ## Installation > This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually. @@ -12,73 +16,57 @@ Open a command line window in the folder of the project (.csproj file) and type abp add-package Volo.Abp.GlobalFeatures ``` -## Implementation - -Global Feature system aims module based feature management . A module has to have own Global Features itself. - -### Define a Global Feature +## Defining a Global Feature A feature class is something like that: ```csharp -[GlobalFeatureName(Name)] -public class PaymentFeature : GlobalFeature +[GlobalFeatureName("Shopping.Payment")] +public class PaymentFeature { - public const string Name = "Shopping.Payment"; - - public PaymentFeature(GlobalModuleFeatures module) : base(module) - { - } + } ``` -### Define Global Module Features +## Enable/Disable Global Features -All features of a module have to be defined in a Global Module Features class. +Use `GlobalFeatureManager.Instance` to enable/disable a global feature. ```csharp -public class GlobalShoppingFeatures : GlobalModuleFeatures -{ - public const string ModuleName = "Shopping"; +// Able to Enable/Disable with generic type parameter. +GlobalFeatureManager.Instance.Enable(); +GlobalFeatureManager.Instance.Disable(); - public GlobalShoppingFeatures(GlobalFeatureManager featureManager) : base(featureManager) - { - AddFeature(new PaymentFeature(this)); - // And more features... - } -} +// Also able to Enable/Disable with string feature name. +GlobalFeatureManager.Instance.Enable("Shopping.Payment"); +GlobalFeatureManager.Instance.Disable("Shopping.Payment"); ``` -## Usage +> Global Features are disabled unless they are explicitly enabled. -### Enable/Disable Features +### Where to Configure Global Features? -Global features are managed by modules. Module Features have to be added to Modules of GlobalFeatureManager. +Global Features have to be configured before application startup. Since the `GlobalFeatureManager.Instance` is a singleton object, one-time, static configuration is enough. It is suggested to enable/disable global features in `PreConfigureServices` method of your module. You can use the `OneTimeRunner` utility class to make sure it runs only once: ```csharp -// GerOrAdd might be useful to be sure module features are added. -var shoppingGlobalFeatures = GlobalFeatureManager.Instance.Modules - .GetOrAdd( - GlobalShoppingFeatures.ModuleName, - ()=> new GlobalShoppingFeatures(GlobalFeatureManager.Instance)); - -// Able to Enable/Disable with generic type parameter. -shoppingGlobalFeatures.Enable(); -shoppingGlobalFeatures.Disable(); - -// Also able to Enable/Disable with string feature name. -shoppingGlobalFeatures.Enable(PaymentFeature.Name); -shoppingGlobalFeatures.Disable("Shopping.Payment"); +private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); +public override void PreConfigureServices(ServiceConfigurationContext context) +{ + OneTimeRunner.Run(() => + { + GlobalFeatureManager.Instance.Enable(); + }); +} ``` -### Check if a feature is enabled +## Check for a Global Feature ```csharp GlobalFeatureManager.Instance.IsEnabled() GlobalFeatureManager.Instance.IsEnabled("Shopping.Payment") ``` -Both methods return `bool`. +Both methods return `bool`. So, you can write conditional logic as shown below: ```csharp if (GlobalFeatureManager.Instance.IsEnabled()) @@ -87,31 +75,20 @@ if (GlobalFeatureManager.Instance.IsEnabled()) } ``` -Beside the manual check, there is `[RequiresGlobalFeature]` attribute to check it declaratively for a controller or page. ABP returns 404 if the related feature was disabled. +### RequiresGlobalFeature Attribute + +Beside the manual check, there is `[RequiresGlobalFeature]` attribute to check it declaratively for a controller or page. ABP returns HTTP Response `404` if the related feature was disabled. ```csharp -[RequiresGlobalFeature(typeof(CommentsFeature))] +[RequiresGlobalFeature(typeof(PaymentFeature))] public class PaymentController : AbpController { - // ... -} -``` - -## When to configure Global Features? -Global Features have to be configured before application startup. So best place to configuring it is `PreConfigureServices` with **OneTimeRunner** to make sure it runs one time. -```csharp -private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); -public override void PreConfigureServices(ServiceConfigurationContext context) -{ - OneTimeRunner.Run(() => - { - GlobalFeatureManager.Instance.Modules.Foo().EnableAll(); - }); } ``` -## Features vs Global Features -[Features](Features.md) & [Global Features](Global-Features.md) are totally different systems. +## Grouping Features of a Module + +It is common to group global features of a module to allow the final application developer easily discover and configure the features. -Features are used to switch on/off application feature for each tenant. So Features, only hides disabled ones, but with Global Features, disabled features pretends like never existed in application. \ No newline at end of file +**TODO: Define a `SubscriptionFeature` and group as a E-Commerce module.** \ No newline at end of file diff --git a/framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalFeatureManager.cs b/framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalFeatureManager.cs index e0662857e1a..28e5c8e290f 100644 --- a/framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalFeatureManager.cs +++ b/framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalFeatureManager.cs @@ -18,7 +18,7 @@ public class GlobalFeatureManager protected HashSet EnabledFeatures { get; } - internal GlobalFeatureManager() + protected internal GlobalFeatureManager() { EnabledFeatures = new HashSet(); Configuration = new Dictionary(); @@ -27,7 +27,7 @@ internal GlobalFeatureManager() public virtual bool IsEnabled() { - return IsEnabled(GlobalFeatureNameAttribute.GetName()); + return IsEnabled(typeof(TFeature)); } public virtual bool IsEnabled([NotNull] Type featureType) @@ -39,13 +39,33 @@ public virtual bool IsEnabled(string featureName) { return EnabledFeatures.Contains(featureName); } + + public virtual void Enable() + { + Enable(typeof(TFeature)); + } + + public virtual void Enable([NotNull] Type featureType) + { + Enable(GlobalFeatureNameAttribute.GetName(featureType)); + } - protected internal void Enable(string featureName) + public virtual void Enable(string featureName) { EnabledFeatures.AddIfNotContains(featureName); } + + public virtual void Disable() + { + Disable(typeof(TFeature)); + } + + public virtual void Disable([NotNull] Type featureType) + { + Disable(GlobalFeatureNameAttribute.GetName(featureType)); + } - protected internal void Disable(string featureName) + public virtual void Disable(string featureName) { EnabledFeatures.Remove(featureName); } From da3f9e71a7457b3aecdaacb23a3cff26340838c7 Mon Sep 17 00:00:00 2001 From: enisn Date: Fri, 9 Apr 2021 18:32:59 +0300 Subject: [PATCH 2/2] Docs - Add Grouping Features of a Module to Global-Features.md --- docs/en/Global-Features.md | 54 +++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/docs/en/Global-Features.md b/docs/en/Global-Features.md index e0c7d892029..f4e52600600 100644 --- a/docs/en/Global-Features.md +++ b/docs/en/Global-Features.md @@ -91,4 +91,56 @@ public class PaymentController : AbpController It is common to group global features of a module to allow the final application developer easily discover and configure the features. -**TODO: Define a `SubscriptionFeature` and group as a E-Commerce module.** \ No newline at end of file +Following example shows how to group features of a module. + +```csharp +[GlobalFeatureName("Ecommerce.Subscription")] +public class SubscriptionFeature : GlobalFeature +{ + public SubscriptionFeature(GlobalModuleFeatures module) : base(module) + { + } +} +``` + +All features of a module have to be defined in a Global Module Features class. + +```csharp +public class GlobalEcommerceFeatures : GlobalModuleFeatures +{ + public const string ModuleName = "Ecommerce"; + + public SubscriptionFeature Subscription => GetFeature(); + + public GlobalEcommerceFeatures(GlobalFeatureManager featureManager) : base(featureManager) + { + // Added features will be used for EnableAll() & DisableAll() actions for this module. + AddFeature(new SubscriptionFeature(this)); + } +} +``` + +An extension method will be better to discover Global Module Features class + +```csharp +public static class GlobalModuleFeaturesDictionaryEcommerceExtensions +{ + public static GlobalEcommerceFeatures Ecommerce([NotNull] this GlobalModuleFeaturesDictionary modules) + { + Check.NotNull(modules, nameof(modules)); + + return modules + .GetOrAdd( + GlobalEcommerceFeatures.ModuleName, + _ => new GlobalEcommerceFeatures(modules.FeatureManager) + ) + as GlobalEcommerceFeatures; + } +``` + +Final usage will be like below: + +```csharp +GlobalFeatureManager.Instance.Modules.Ecommerce().Subscription.Enable(); +``` +