# Features 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 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`) 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 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 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 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("MyApp.MaxProductCount"); ``` #### Extension Methods There are some useful extension methods for the `IFeatureChecker` interface; * `Task GetAsync(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. > This class is generally created in the `Application.Contracts` project of your solution. * 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. Default value is used if there is no other value set for the current user/tenant. ### 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("PdfReporting"), valueType: new ToggleStringValueType() ); myGroup.AddFeature( "MyApp.MaxProductCount", defaultValue: "10", displayName: LocalizableString .Create("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): ![features-action](images/features-action.png) This action opens a modal to manage the feature values for the selected tenant: ![features-modal](images/features-modal.png) 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("Reporting"), valueType: new ToggleStringValueType() ); reportingFeature.CreateChild( "MyApp.PdfReporting", defaultValue: "false", displayName: LocalizableString .Create("PdfReporting"), valueType: new ToggleStringValueType() ); reportingFeature.CreateChild( "MyApp.ExcelReporting", defaultValue: "false", displayName: LocalizableString .Create("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 Feature management is normally done by an admin user using the feature management modal: ![features-modal](images/features-modal.png) This modal is available on the related entities, like tenants in a multi-tenant application. To open it, navigate to the **Tenant Management** page (for a multi-tenant application), click to the **Actions** button left to the Tenant and select the **Features** action. If you need to manage features by code, inject the `IFeatureManager` service. **Example: Enable PDF reporting for a tenant** ```csharp public class MyService : ITransientDependency { private readonly IFeatureManager _featureManager; public MyService(IFeatureManager featureManager) { _featureManager = featureManager; } public async Task EnablePdfReporting(Guid tenantId) { await _featureManager.SetForTenantAsync( tenantId, "MyApp.PdfReporting", true.ToString() ); } } ``` `IFeatureManager` is defined by the Feature Management module. It comes pre-installed with the application startup template. See the [feature management module documentation](Modules/Feature-Management.md) for more information. ## Advanced Topics ### Feature Value Providers Feature system is extensible. Any class derived from `FeatureValueProvider` (or implements `IFeatureValueProvider`) can contribute to the feature system. A value provider is responsible to **obtain the current value** of a given feature. Feature value providers are **executed one by one**. If one of them return a non-null value, then this feature value is used and the other providers are not executed. There are three pre-defined value providers, executed by the given order: * `TenantFeatureValueProvider` tries to get if the feature value is explicitly set for the **current tenant**. * `EditionFeatureValueProvider` tries to get the feature value for the current edition. Edition Id is obtained from the current principal identity (`ICurrentPrincipalAccessor`) with the claim name `editionid` (a constant defined as`AbpClaimTypes.EditionId`). Editions are not implemented for the [tenant management](Modules/Tenant-Management.md) module. You can implement it yourself or consider to use the [SaaS module](https://commercial.abp.io/modules/Volo.Saas) of the ABP Commercial. * `DefaultValueFeatureValueProvider` gets the default value of the feature. You can write your own provider by inheriting the `FeatureValueProvider`. **Example: Enable all features for a user with "SystemAdmin" as a "User_Type" claim value** ```csharp using System.Threading.Tasks; using Volo.Abp.Features; using Volo.Abp.Security.Claims; using Volo.Abp.Validation.StringValues; namespace FeaturesDemo { public class SystemAdminFeatureValueProvider : FeatureValueProvider { public override string Name => "SA"; private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor; public SystemAdminFeatureValueProvider( IFeatureStore featureStore, ICurrentPrincipalAccessor currentPrincipalAccessor) : base(featureStore) { _currentPrincipalAccessor = currentPrincipalAccessor; } public override Task GetOrNullAsync(FeatureDefinition feature) { if (feature.ValueType is ToggleStringValueType && _currentPrincipalAccessor.Principal?.FindFirst("User_Type")?.Value == "SystemAdmin") { return Task.FromResult("true"); } return null; } } } ``` If a provider returns `null`, then the next provider is executed. Once a provider is defined, it should be added to the `AbpFeatureOptions` as shown below: ```csharp Configure(options => { options.ValueProviders.Add(); }); ``` Use this code inside the `ConfigureServices` of your [module](Module-Development-Basics.md) class. ### 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