From df780227f5f0486ae0c56d5ae26c326922fd8631 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Sun, 6 Sep 2020 21:05:22 +0300 Subject: [PATCH 01/10] fix: use an intersection type for strict null checks --- npm/ng-packs/packages/core/src/lib/models/config.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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; From dfd2baee7f8ad0fef68eff134014447084555c2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 7 Sep 2020 17:25:44 +0300 Subject: [PATCH 02/10] Stared to document the feature system --- docs/en/Features.md | 46 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/docs/en/Features.md b/docs/en/Features.md index 4b31ca16e9..d5262c6cdb 100644 --- a/docs/en/Features.md +++ b/docs/en/Features.md @@ -1,3 +1,47 @@ # 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**. + +Feature system was originally designed to control the tenant features in a **multi-tenant** application. However, it is **extensible** and capable of determining 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 starting to explain how to define features, let's see how to check a feature in your application code. + +### RequiresFeature Attribute + +`[RequiresFeature]` attribute (defined in the `Volo.Abp.Features` namespace) is used to declaratively check if a feature is enabled or not. + +**Example: Check if the current user/tenant has "PDF Reporting" feature enabled** + +```csharp +public class ReportingAppService : ApplicationService, IReportingAppService +{ + public async Task GetCsvReportAsync() + { + throw new System.NotImplementedException(); + } + + [RequiresFeature("MyApp.PdfReporting")] + public async Task GetPdfReportAsync() + { + throw new System.NotImplementedException(); + } +} +``` + +* `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 +* `RequiresFeature` may get multiple feature names, like `[RequiresFeature("Feature1", "Feature2")]`. In this case ABP checks if current user/tenant has any of the features enabled. Use `[RequiresFeature("Feature1", "Feature2", RequiresAll = true)]` to force to allow only if all of the features are enabled. +* Multiple usage of `[RequiresFeature]` attribute is enabled, so it checks all of them. + +#### About the Interception + +ABP Framework uses the interception system to make the `[RequiresFeature]` attribute working. So, it can work with any class 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. From 735a0c2db68c054ad456afe4f6231317191bb5d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 7 Sep 2020 18:11:10 +0300 Subject: [PATCH 03/10] Update Features.md --- docs/en/Features.md | 115 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 103 insertions(+), 12 deletions(-) diff --git a/docs/en/Features.md b/docs/en/Features.md index d5262c6cdb..2c859d643f 100644 --- a/docs/en/Features.md +++ b/docs/en/Features.md @@ -2,6 +2,8 @@ ABP Feature system is used to **enable**, **disable** or **change the behavior** of the application features **on runtime**. +The runtime value for a feature can simply be a `boolean`, like enabled (`true`) or disabled (`false`). However, you can store **any kind** of runtime value for feature. + Feature system was originally designed to control the tenant features in a **multi-tenant** application. However, it is **extensible** and capable of determining 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). @@ -12,36 +14,125 @@ Before starting to explain how to define features, let's see how to check a feat ### RequiresFeature Attribute -`[RequiresFeature]` attribute (defined in the `Volo.Abp.Features` namespace) is used to declaratively check if a feature is enabled or not. +`[RequiresFeature]` attribute (defined in the `Volo.Abp.Features` namespace) is used to declaratively check if a feature is enabled or not. It is a useful shortcut for the `boolean` features. -**Example: Check if the current user/tenant has "PDF Reporting" feature enabled** +**Example: Check if the "PDF Reporting" feature enabled** ```csharp public class ReportingAppService : ApplicationService, IReportingAppService { - public async Task GetCsvReportAsync() - { - throw new System.NotImplementedException(); - } - [RequiresFeature("MyApp.PdfReporting")] public async Task GetPdfReportAsync() { - throw new System.NotImplementedException(); + //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 -* `RequiresFeature` may get multiple feature names, like `[RequiresFeature("Feature1", "Feature2")]`. In this case ABP checks if current user/tenant has any of the features enabled. Use `[RequiresFeature("Feature1", "Feature2", RequiresAll = true)]` to force to allow only if all of the features are enabled. -* Multiple usage of `[RequiresFeature]` attribute is enabled, so it checks all of them. +* `[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 that is injected from the [dependency injection](Dependency-Injection.md). +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 controllers and razor pages. They don't require the following rules since ABP Framework uses 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(); + } +} +``` + +In this way, you can create numeric limits in your SaaS application depending on the current user or tenant. + +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 + +TODO \ No newline at end of file From 460686a22d003e0475a62402d3f56b5e015e819f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 7 Sep 2020 20:46:43 +0300 Subject: [PATCH 04/10] Added empty sections to the features document --- docs/en/Features.md | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/docs/en/Features.md b/docs/en/Features.md index 2c859d643f..82d1790b9e 100644 --- a/docs/en/Features.md +++ b/docs/en/Features.md @@ -2,19 +2,19 @@ ABP Feature system is used to **enable**, **disable** or **change the behavior** of the application features **on runtime**. -The runtime value for a feature can simply be a `boolean`, like enabled (`true`) or disabled (`false`). However, you can store **any kind** of runtime value for feature. +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** application. However, it is **extensible** and capable of determining features by any condition. +Feature system was originally designed to control the tenant features in a **multi-tenant** 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 starting to explain how to define features, let's see how to check a feature in your application code. +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 enabled or not. It is a useful shortcut for the `boolean` features. +`[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** @@ -42,10 +42,10 @@ ABP Framework uses the interception system to make the `[RequiresFeature]` attri 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). +* 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 controllers and razor pages. They don't require the following rules since ABP Framework uses action/page filters to implement the feature checking in this case. +> 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 @@ -125,7 +125,7 @@ public class ProductController : AbpController } ``` -In this way, you can create numeric limits in your SaaS application depending on the current user or tenant. +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: @@ -135,4 +135,27 @@ var maxProductCountLimit = await _featureChecker.GetAsync("MyApp.MaxProduct #### 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 + +TODO + +## Feature Management + +TODO + +## Advanced Topics + +TODO + +### Feature Value Providers + +TODO + +### Feature Store + TODO \ No newline at end of file From 60a54bc9f126cad6ce7f3a15200cf0e0c9f95c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 7 Sep 2020 21:37:55 +0300 Subject: [PATCH 05/10] Added section: Defining the Features. --- docs/en/Features.md | 125 ++++++++++++++++++++++++++++- docs/en/images/features-action.png | Bin 0 -> 16742 bytes docs/en/images/features-modal.png | Bin 0 -> 35412 bytes 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 docs/en/images/features-action.png create mode 100644 docs/en/images/features-modal.png diff --git a/docs/en/Features.md b/docs/en/Features.md index 82d1790b9e..e125f16f08 100644 --- a/docs/en/Features.md +++ b/docs/en/Features.md @@ -4,7 +4,7 @@ ABP Feature system is used to **enable**, **disable** or **change the behavior** 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** application. However, it is **extensible** and capable of determining the features by any condition. +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). @@ -142,6 +142,129 @@ There are some useful extension methods for the `IFeatureChecker` interface; ## 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. + +TODO + +### Changing Features Definitions of a Depended Module + +TODO + +### Check a Permission in the Client Side + TODO ## Feature Management diff --git a/docs/en/images/features-action.png b/docs/en/images/features-action.png new file mode 100644 index 0000000000000000000000000000000000000000..8283a95be946232016b65a63f47c211424c6ba54 GIT binary patch literal 16742 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�UfiA=i< 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 literal 0 HcmV?d00001 diff --git a/docs/en/images/features-modal.png b/docs/en/images/features-modal.png new file mode 100644 index 0000000000000000000000000000000000000000..74ee8aabc33189a420f3205ddc0c90215e5767c9 GIT binary patch literal 35412 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 Q7XuJ@y85}Sb4q9e0P>=`F8}}l literal 0 HcmV?d00001 From 502ac9eea5d1dc769826a828fba72fd8e2ca6070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 7 Sep 2020 21:49:04 +0300 Subject: [PATCH 06/10] added example: Defining child features --- docs/en/Features.md | 47 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/docs/en/Features.md b/docs/en/Features.md index e125f16f08..963f15a9b7 100644 --- a/docs/en/Features.md +++ b/docs/en/Features.md @@ -222,7 +222,6 @@ namespace FeaturesDemo } } } - ``` * `FeaturesDemoResource` is the project name in this example code. See the [localization document](Localization.md) for details about the localization system. @@ -257,7 +256,51 @@ See the *Feature Management* section below to learn more about managing the feat 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. -TODO +**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 From 36f8bb65a98aa26cfe48b4735534b9629da1ea83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 7 Sep 2020 21:58:30 +0300 Subject: [PATCH 07/10] Add link to the angular UI doc for the permissions and revise some sections. --- docs/en/Authorization.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) 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 From 8e45c9a240b9930eede375fc0470de51ee5b4673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 7 Sep 2020 21:58:42 +0300 Subject: [PATCH 08/10] Add Feature Store section. --- docs/en/Features.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/Features.md b/docs/en/Features.md index 963f15a9b7..35ba5a0de0 100644 --- a/docs/en/Features.md +++ b/docs/en/Features.md @@ -306,7 +306,7 @@ The example above defines a *Reporting* feature with two children: *PDF Reportin TODO -### Check a Permission in the Client Side +## Check a Permission in the Client Side TODO @@ -324,4 +324,4 @@ TODO ### Feature Store -TODO \ No newline at end of file +`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 From 0bae2564c127e359247e5ec87ba510eaf6f311c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 8 Sep 2020 10:27:01 +0300 Subject: [PATCH 09/10] Create Angular UI features page --- docs/en/UI/Angular/Features.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docs/en/UI/Angular/Features.md 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 From 00c5114b1e3ec8d52b4bdc0243bfdf34c6f6f14e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 8 Sep 2020 10:29:04 +0300 Subject: [PATCH 10/10] Added sections to the features document --- docs/en/Features.md | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/docs/en/Features.md b/docs/en/Features.md index 35ba5a0de0..1ab26a113a 100644 --- a/docs/en/Features.md +++ b/docs/en/Features.md @@ -304,11 +304,38 @@ The example above defines a *Reporting* feature with two children: *PDF Reportin ### Changing Features Definitions of a Depended Module -TODO +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. -## Check a Permission in the Client Side +**Example: Manipulate an existing feature definition** -TODO +```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