Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to directly enable/disable features on the GlobalFeatureManager #8561

Merged
merged 2 commits into from
Apr 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 84 additions & 55 deletions docs/en/Global-Features.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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<PaymentFeature>();
GlobalFeatureManager.Instance.Disable<PaymentFeature>();

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<PaymentFeature>();
shoppingGlobalFeatures.Disable<PaymentFeature>();

// 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<PaymentFeature>();
});
}
```

### Check if a feature is enabled
## Check for a Global Feature

```csharp
GlobalFeatureManager.Instance.IsEnabled<PaymentFeature>()
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<PaymentFeature>())
Expand All @@ -87,31 +75,72 @@ if (GlobalFeatureManager.Instance.IsEnabled<PaymentFeature>())
}
```

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.
## 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.

Following example shows how to group features of a module.

```csharp
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
public override void PreConfigureServices(ServiceConfigurationContext context)
[GlobalFeatureName("Ecommerce.Subscription")]
public class SubscriptionFeature : GlobalFeature
{
OneTimeRunner.Run(() =>
public SubscriptionFeature(GlobalModuleFeatures module) : base(module)
{
GlobalFeatureManager.Instance.Modules.Foo().EnableAll();
});
}
}
```

## Features vs Global Features
[Features](Features.md) & [Global Features](Global-Features.md) are totally different systems.
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<SubscriptionFeature>();

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();
```

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.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class GlobalFeatureManager

protected HashSet<string> EnabledFeatures { get; }

internal GlobalFeatureManager()
protected internal GlobalFeatureManager()
{
EnabledFeatures = new HashSet<string>();
Configuration = new Dictionary<object, object>();
Expand All @@ -27,7 +27,7 @@ internal GlobalFeatureManager()

public virtual bool IsEnabled<TFeature>()
{
return IsEnabled(GlobalFeatureNameAttribute.GetName<TFeature>());
return IsEnabled(typeof(TFeature));
}

public virtual bool IsEnabled([NotNull] Type featureType)
Expand All @@ -39,13 +39,33 @@ public virtual bool IsEnabled(string featureName)
{
return EnabledFeatures.Contains(featureName);
}

public virtual void Enable<TFeature>()
{
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<TFeature>()
{
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);
}
Expand Down