mirror of https://github.com/abpframework/abp.git
6 changed files with 374 additions and 7 deletions
@ -1,3 +1,354 @@ |
|||
# Features |
|||
|
|||
TODO |
|||
ABP Feature system is used to **enable**, **disable** or **change the behavior** of the application features **on runtime**. |
|||
|
|||
The runtime value for a feature is generally a `boolean` value, like `true` (enabled) or `false` (disabled). However, you can get/set **any kind** of value for feature. |
|||
|
|||
Feature system was originally designed to control the tenant features in a **[multi-tenant](Multi-Tenancy.md)** application. However, it is **extensible** and capable of determining the features by any condition. |
|||
|
|||
> The feature system is implemented with the [Volo.Abp.Features](https://www.nuget.org/packages/Volo.Abp.Features) NuGet package. Most of the times you don't need to manually [install it](https://abp.io/package-detail/Volo.Abp.Features) since it comes pre-installed with the [application startup template](Startup-Templates/Application.md). |
|||
|
|||
## Checking for the Features |
|||
|
|||
Before explaining to define features, let's see how to check a feature value in your application code. |
|||
|
|||
### RequiresFeature Attribute |
|||
|
|||
`[RequiresFeature]` attribute (defined in the `Volo.Abp.Features` namespace) is used to declaratively check if a feature is `true` (enabled) or not. It is a useful shortcut for the `boolean` features. |
|||
|
|||
**Example: Check if the "PDF Reporting" feature enabled** |
|||
|
|||
```csharp |
|||
public class ReportingAppService : ApplicationService, IReportingAppService |
|||
{ |
|||
[RequiresFeature("MyApp.PdfReporting")] |
|||
public async Task<PdfReportResultDto> GetPdfReportAsync() |
|||
{ |
|||
//TODO... |
|||
} |
|||
} |
|||
``` |
|||
|
|||
* `RequiresFeature(...)` simply gets a feature name to check if it is enabled or not. If not enabled, an authorization [exception](Exception-Handling.md) is thrown and a proper response is returned to the client side. |
|||
* `[RequiresFeature]` can be used for a **method** or a **class**. When you use it for a class, all the methods of that class require the given feature. |
|||
* `RequiresFeature` may get multiple feature names, like `[RequiresFeature("Feature1", "Feature2")]`. In this case ABP checks if any of the features enabled. Use `RequiresAll` option, like `[RequiresFeature("Feature1", "Feature2", RequiresAll = true)]` to force to check all of the features to be enabled. |
|||
* Multiple usage of `[RequiresFeature]` attribute is supported for a method or class. ABP check checks all of them in that case. |
|||
|
|||
> Feature name can be any arbitrary string. It should be unique for a feature. |
|||
|
|||
#### About the Interception |
|||
|
|||
ABP Framework uses the interception system to make the `[RequiresFeature]` attribute working. So, it can work with any class (application services, controllers...) that is injected from the [dependency injection](Dependency-Injection.md). |
|||
|
|||
However, there are **some rules should be followed** in order to make it working; |
|||
|
|||
* If you are **not injecting** the service over an interface (like `IMyService`), then the methods of the service must be `virtual`. Otherwise, [dynamic proxy / interception](Dynamic-Proxying-Interceptors.md) system can not work. |
|||
* Only `async` methods (methods returning a `Task` or `Task<T>`) are intercepted. |
|||
|
|||
> There is an exception for the **controller and razor page methods**. They **don't require** the following the rules above, since ABP Framework uses the action/page filters to implement the feature checking in this case. |
|||
|
|||
### IFeatureChecker Service |
|||
|
|||
`IFeatureChecker` allows to check a feature in your application code. |
|||
|
|||
#### IsEnabledAsync |
|||
|
|||
Returns `true` if the given feature is enabled. So, you can conditionally execute your business flow. |
|||
|
|||
**Example: Check if the "PDF Reporting" feature enabled** |
|||
|
|||
```csharp |
|||
public class ReportingAppService : ApplicationService, IReportingAppService |
|||
{ |
|||
private readonly IFeatureChecker _featureChecker; |
|||
|
|||
public ReportingAppService(IFeatureChecker featureChecker) |
|||
{ |
|||
_featureChecker = featureChecker; |
|||
} |
|||
|
|||
public async Task<PdfReportResultDto> GetPdfReportAsync() |
|||
{ |
|||
if (await _featureChecker.IsEnabledAsync("MyApp.PdfReporting")) |
|||
{ |
|||
//TODO... |
|||
} |
|||
else |
|||
{ |
|||
//TODO... |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
`IsEnabledAsync` has overloads to check multiple features in one method call. |
|||
|
|||
#### GetOrNullAsync |
|||
|
|||
Gets the current value for a feature. This method returns a `string`, so you store any kind of value inside it, by converting to or from `string`. |
|||
|
|||
**Example: Check the maximum product count allowed** |
|||
|
|||
```csharp |
|||
public class ProductController : AbpController |
|||
{ |
|||
private readonly IFeatureChecker _featureChecker; |
|||
|
|||
public ProductController(IFeatureChecker featureChecker) |
|||
{ |
|||
_featureChecker = featureChecker; |
|||
} |
|||
|
|||
public async Task<IActionResult> Create(CreateProductModel model) |
|||
{ |
|||
var currentProductCount = await GetCurrentProductCountFromDatabase(); |
|||
|
|||
//GET THE FEATURE VALUE |
|||
var maxProductCountLimit = |
|||
await _featureChecker.GetOrNullAsync("MyApp.MaxProductCount"); |
|||
|
|||
if (currentProductCount >= Convert.ToInt32(maxProductCountLimit)) |
|||
{ |
|||
throw new BusinessException( |
|||
"MyApp:ReachToMaxProductCountLimit", |
|||
$"You can not create more than {maxProductCountLimit} products!" |
|||
); |
|||
} |
|||
|
|||
//TODO: Create the product in the database... |
|||
} |
|||
|
|||
private async Task<int> GetCurrentProductCountFromDatabase() |
|||
{ |
|||
throw new System.NotImplementedException(); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
This example uses a numeric value as a feature limit product counts for a user/tenant in a SaaS application. |
|||
|
|||
Instead of manually converting the value to `int`, you can use the generic overload of the `GetAsync` method: |
|||
|
|||
```csharp |
|||
var maxProductCountLimit = await _featureChecker.GetAsync<int>("MyApp.MaxProductCount"); |
|||
``` |
|||
|
|||
#### Extension Methods |
|||
|
|||
There are some useful extension methods for the `IFeatureChecker` interface; |
|||
|
|||
* `Task<T> GetAsync<T>(string name, T defaultValue = default)`: Used to get a value of a feature with the given type `T`. Allows to specify a `defaultValue` that is returned when the feature value is `null`. |
|||
* `CheckEnabledAsync(string name)`: Checks if given feature is enabled. Throws an `AbpAuthorizationException` if the feature was not `true` (enabled). |
|||
|
|||
## Defining the Features |
|||
|
|||
A feature should be defined to be able to check it. |
|||
|
|||
### FeatureDefinitionProvider |
|||
|
|||
Create a class inheriting the `FeatureDefinitionProvider` to define permissions. |
|||
|
|||
**Example: Defining permissions** |
|||
|
|||
```csharp |
|||
using Volo.Abp.Features; |
|||
|
|||
namespace FeaturesDemo |
|||
{ |
|||
public class MyFeatureDefinitionProvider : FeatureDefinitionProvider |
|||
{ |
|||
public override void Define(IFeatureDefinitionContext context) |
|||
{ |
|||
var myGroup = context.AddGroup("MyApp"); |
|||
|
|||
myGroup.AddFeature("MyApp.PdfReporting", defaultValue: "false"); |
|||
myGroup.AddFeature("MyApp.MaxProductCount", defaultValue: "10"); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
> ABP automatically discovers this class and registers the features. No additional configuration required. |
|||
|
|||
* In the `Define` method, you first need to add a **feature group** for your application/module or get an existing group then add **features** to this group. |
|||
* First feature, named `MyApp.PdfReporting`, is a `boolean` feature with `false` as the default value. |
|||
* Second feature, named `MyApp.MaxProductCount`, is a numeric feature with `10` as the default value. |
|||
|
|||
### Other Feature Properties |
|||
|
|||
While these minimal definitions are enough to make the feature system working, you can specify the **optional properties** for the features; |
|||
|
|||
* `DisplayName`: A localizable string that will be used to show the feature name on the user interface. |
|||
* `Description`: A longer localizable text to describe the feature. |
|||
* `ValueType`: Type of the feature value. Can be a class implementing the `IStringValueType`. Built-in types: |
|||
* `ToggleStringValueType`: Used to define `true`/`false`, `on`/`off`, `enabled`/`disabled` style features. A checkbox is shown on the UI. |
|||
* `FreeTextStringValueType`: Used to define free text values. A textbox is shown on the UI. |
|||
* `SelectionStringValueType`: Used to force the value to be selected from a list. A dropdown list is shown on the UI. |
|||
* `IsVisibleToClients` (default: `true`): Set false to hide the value of this feature from clients (browsers). Sharing the value with the clients helps them to conditionally show/hide/change the UI parts based on the feature value. |
|||
* `Properties`: A dictionary to set/get arbitrary key-value pairs related to this feature. This can be a point for customization. |
|||
|
|||
So, based on these descriptions, it would be better to define these features as shown below: |
|||
|
|||
```csharp |
|||
using FeaturesDemo.Localization; |
|||
using Volo.Abp.Features; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Validation.StringValues; |
|||
|
|||
namespace FeaturesDemo |
|||
{ |
|||
public class MyFeatureDefinitionProvider : FeatureDefinitionProvider |
|||
{ |
|||
public override void Define(IFeatureDefinitionContext context) |
|||
{ |
|||
var myGroup = context.AddGroup("MyApp"); |
|||
|
|||
myGroup.AddFeature( |
|||
"MyApp.PdfReporting", |
|||
defaultValue: "false", |
|||
displayName: LocalizableString |
|||
.Create<FeaturesDemoResource>("PdfReporting"), |
|||
valueType: new ToggleStringValueType() |
|||
); |
|||
|
|||
myGroup.AddFeature( |
|||
"MyApp.MaxProductCount", |
|||
defaultValue: "10", |
|||
displayName: LocalizableString |
|||
.Create<FeaturesDemoResource>("MaxProductCount"), |
|||
valueType: new FreeTextStringValueType( |
|||
new NumericValueValidator(0, 1000000)) |
|||
); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
* `FeaturesDemoResource` is the project name in this example code. See the [localization document](Localization.md) for details about the localization system. |
|||
* First feature is set to `ToggleStringValueType`, while the second one is set to `FreeTextStringValueType` with a numeric validator that allows to the values from `0` to `1,000,000`. |
|||
|
|||
Remember to define the localization the keys in your localization file: |
|||
|
|||
````json |
|||
"PdfReporting": "PDF Reporting", |
|||
"MaxProductCount": "Maximum number of products" |
|||
```` |
|||
|
|||
See the [localization document](Localization.md) for details about the localization system. |
|||
|
|||
### Feature Management Modal |
|||
|
|||
The [application startup template](Startup-Templates/Application.md) comes with the [tenant management](Modules/Tenant-Management.md) and the [feature management](Modules/Feature-Management.md) modules pre-installed. |
|||
|
|||
Whenever you define a new feature, it will be available on the **feature management modal**. To open this modal, navigate to the **tenant management page** and select the `Features` action for a tenant (create a new tenant if there is no tenant yet): |
|||
|
|||
 |
|||
|
|||
This action opens a modal to manage the feature values for the selected tenant: |
|||
|
|||
 |
|||
|
|||
So, you can enable, disable and set values for a tenant. These values will be used whenever a user of this tenant uses the application. |
|||
|
|||
See the *Feature Management* section below to learn more about managing the features. |
|||
|
|||
### Child Features |
|||
|
|||
A feature may have child features. This is especially useful if you want to create a feature that is selectable only if another feature was enabled. |
|||
|
|||
**Example: Defining child features** |
|||
|
|||
```csharp |
|||
using FeaturesDemo.Localization; |
|||
using Volo.Abp.Features; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Validation.StringValues; |
|||
|
|||
namespace FeaturesDemo |
|||
{ |
|||
public class MyFeatureDefinitionProvider : FeatureDefinitionProvider |
|||
{ |
|||
public override void Define(IFeatureDefinitionContext context) |
|||
{ |
|||
var myGroup = context.AddGroup("MyApp"); |
|||
|
|||
var reportingFeature = myGroup.AddFeature( |
|||
"MyApp.Reporting", |
|||
defaultValue: "false", |
|||
displayName: LocalizableString |
|||
.Create<FeaturesDemoResource>("Reporting"), |
|||
valueType: new ToggleStringValueType() |
|||
); |
|||
|
|||
reportingFeature.CreateChild( |
|||
"MyApp.PdfReporting", |
|||
defaultValue: "false", |
|||
displayName: LocalizableString |
|||
.Create<FeaturesDemoResource>("PdfReporting"), |
|||
valueType: new ToggleStringValueType() |
|||
); |
|||
|
|||
reportingFeature.CreateChild( |
|||
"MyApp.ExcelReporting", |
|||
defaultValue: "false", |
|||
displayName: LocalizableString |
|||
.Create<FeaturesDemoResource>("ExcelReporting"), |
|||
valueType: new ToggleStringValueType() |
|||
); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
The example above defines a *Reporting* feature with two children: *PDF Reporting* and *Excel Reporting*. |
|||
|
|||
### Changing Features Definitions of a Depended Module |
|||
|
|||
A class deriving from the `FeatureDefinitionProvider` (just like the example above) can also get the existing permission definitions (defined by the depended [modules](Module-Development-Basics.md)) and change their definitions. |
|||
|
|||
**Example: Manipulate an existing feature definition** |
|||
|
|||
```csharp |
|||
var someGroup = context.GetGroupOrNull("SomeModule"); |
|||
var feature = someGroup.Features.FirstOrDefault(f => f.Name == "SomeFeature"); |
|||
if (feature != null) |
|||
{ |
|||
feature.Description = ... |
|||
feature.CreateChild(...); |
|||
} |
|||
``` |
|||
|
|||
## Check a Feature in the Client Side |
|||
|
|||
A feature value is available at the client side too, unless you set `IsVisibleToClients` to `false` on the feature definition. The feature values are exposed from the [Application Configuration API](API/Application-Configuration.md) and usable via some services on the UI. |
|||
|
|||
### ASP.NET Core MVC / Razor Pages UI |
|||
|
|||
Use `abp.features` API to get the feature values. |
|||
|
|||
**Example: Get feature values in the JavaScript code** |
|||
|
|||
````js |
|||
var isEnabled = abp.features.values["MyApp.ExcelReporting"] === "true"; |
|||
var count = abp.features.values["MyApp.MaxProductCount"]; |
|||
```` |
|||
|
|||
### Angular UI |
|||
|
|||
See the [features](Features.md) document for the Angular UI. |
|||
|
|||
## Feature Management |
|||
|
|||
TODO |
|||
|
|||
## Advanced Topics |
|||
|
|||
TODO |
|||
|
|||
### Feature Value Providers |
|||
|
|||
TODO |
|||
|
|||
### Feature Store |
|||
|
|||
`IFeatureStore` is the only interface that needs to be implemented to read the value of features from a persistence source, generally a database system. The Feature Management module implements it and pre-installed in the application startup template. See the [feature management module documentation](https://docs.abp.io/en/abp/latest/Modules/Feature-Management) for more information |
|||
@ -0,0 +1,3 @@ |
|||
# Angular UI: Features |
|||
|
|||
> This document explains how to get feature values in an Angular application. See the [Features document](../../Features.md) to learn the feature system. |
|||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 35 KiB |
Loading…
Reference in new issue