diff --git a/docs/en/Authorization.md b/docs/en/Authorization.md index 4b7502f437..eef4237039 100644 --- a/docs/en/Authorization.md +++ b/docs/en/Authorization.md @@ -80,6 +80,8 @@ namespace Acme.BookStore.Permissions > ABP automatically discovers this class. No additional configuration required! +> You typically define this class inside the `Application.Contracts` project of your [application](Startup-Templates/Application.md). The startup template already comes with an empty class named *YourProjectNamePermissionDefinitionProvider* that you can start with. + In the `Define` method, you first need to add a **permission group** or get an existing group then add **permissions** to this group. When you define a permission, it becomes usable in the ASP.NET Core authorization system as a **policy** name. It also becomes visible in the UI. See permissions dialog for a role: @@ -276,14 +278,24 @@ public async Task CreateAsync(CreateAuthorDto input) > Tip: Prefer to use the `Authorize` attribute wherever possible, since it is declarative & simple. Use `IAuthorizationService` if you need to conditionally check a permission and run a business code based on the permission check. -### Check a Permission in JavaScript +## Check a Permission in JavaScript + +You may need to check a policy/permission on the client side. + +### MVC UI -You may need to check a policy/permission on the client side. For ASP.NET Core MVC / Razor Pages applications, you can use the `abp.auth` API. Example: +For ASP.NET Core MVC / Razor Pages applications, you can use the `abp.auth` API. + +**Example: Check if a given permission has been granted for the current user** ```js abp.auth.isGranted('MyPermissionName'); ``` +### Angular UI + +See the [permission management document](UI/Angular/Permission-Management.md) for the Angular UI. + ## Permission Management Permission management is normally done by an admin user using the permission management modal: @@ -374,7 +386,7 @@ Configure(options => ### Permission Store -`IPermissionStore` is the only interface that needs to be implemented to read the value of permissions from a persistence source, generally a database system. Permission management module implements it. See the [permission management module documentation](Modules/Permission-Management.md) for more information +`IPermissionStore` is the only interface that needs to be implemented to read the value of permissions from a persistence source, generally a database system. The Permission Management module implements it and pre-installed in the application startup template. See the [permission management module documentation](Modules/Permission-Management.md) for more information ### AlwaysAllowAuthorizationService diff --git a/docs/en/Features.md b/docs/en/Features.md index 4b31ca16e9..1ab26a113a 100644 --- a/docs/en/Features.md +++ b/docs/en/Features.md @@ -1,3 +1,354 @@ # Features -TODO \ No newline at end of file +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. + +* 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("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 + +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 \ No newline at end of file diff --git a/docs/en/UI/Angular/Features.md b/docs/en/UI/Angular/Features.md new file mode 100644 index 0000000000..f749030a26 --- /dev/null +++ b/docs/en/UI/Angular/Features.md @@ -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. \ No newline at end of file diff --git a/docs/en/images/features-action.png b/docs/en/images/features-action.png new file mode 100644 index 0000000000..8283a95be9 Binary files /dev/null and b/docs/en/images/features-action.png differ diff --git a/docs/en/images/features-modal.png b/docs/en/images/features-modal.png new file mode 100644 index 0000000000..74ee8aabc3 Binary files /dev/null and b/docs/en/images/features-modal.png differ diff --git a/npm/ng-packs/packages/core/src/lib/models/config.ts b/npm/ng-packs/packages/core/src/lib/models/config.ts index be640a4dcf..cf4eb007c9 100644 --- a/npm/ng-packs/packages/core/src/lib/models/config.ts +++ b/npm/ng-packs/packages/core/src/lib/models/config.ts @@ -22,11 +22,12 @@ export namespace Config { logoUrl?: string; } - export interface ApiConfig { + export type ApiConfig = { [key: string]: string; - rootNamespace?: string; url: string; - } + } & Partial<{ + rootNamespace: string; + }>; export interface Apis { [key: string]: ApiConfig;