ABP框架系列之二十七:(Feature-Management-特征管理)


Introduction

Most SaaS (multi-tenant) applications have editions (packages) those have different features. Thus, they can provide different price and feature options to thier tenants (customers).

ASP.NET Boilerplate provides a feature system to make it easier. We can define features, check if a feature is enabled for a tenant and integrate feature system to other ASP.NET Boilerplate concepts (like authorizationand navigation).

大多數SaaS(多租戶)應用程序都有版本(包),它們具有不同的特征。因此,他們可以為租戶(客戶)提供不同的價格和功能選項。

ASP.NET樣板提供了一個更容易的功能系統。我們可以定義特征,檢查是否啟用了一個租客,集成特征系統和其他ASP.NET框架的概念(如授權、導航)。

About IFeatureValueStore

Feature system uses IFeatureValueStore to get values of features. While you can implement it in your own way, it's fully implemented in module-zero project. If it's not implemented, NullFeatureValueStore is used which returns null for all features (Default feature values are used in this case).

特征系統采用ifeaturevaluestore得到特征值。可以以自己的方式實現它,它完全在module-zero項目中實現。如果不實施,nullfeaturevaluestore使用返回的所有功能(默認為特征值,在這種情況下使用)。

Feature Types

There are two fundamental feature types.

有兩個基本的特征類型

Boolean Feature

Can be "true" or "false". This type of a feature can be enabled or disabled (for an edition or for a tenant).

Value Feature

Can be an arbitrary value. While it's stored and retrieved a string, numbers also can be stored as strings.

For example, our application may be a task management application and we may have a limit for creating tasks in a month. Say that we have two different edition/package; one allows creating 1,000 tasks per month, while other allows creating 5,000 tasks per month. So, this feature should be stored as value, not simply true/false.

可以是任意值。在存儲和檢索一個字符串時,數字也可以作為字符串存儲。

例如,我們的應用程序可能是一個任務管理應用程序,我們可能有一個月內創建任務的限制。說我們有兩個不同的版本/包;一個允許每月創建1000個任務,而另一個允許每月創建5000個任務。因此,這個特性應該存儲為值,而不是簡單的true / false。

Defining Features

A feature should be defined before checking. A module can define it's own features by deriving from FeatureProvider class. Here, a very simple feature provider that defines 3 features:

在檢查之前應該定義一個特征。一個模塊可以定義它自己的特征,來源featureprovider類。這里,定義了3個非常簡單的特征:

public class AppFeatureProvider : FeatureProvider
{
    public override void SetFeatures(IFeatureDefinitionContext context)
    {
        var sampleBooleanFeature = context.Create("SampleBooleanFeature", defaultValue: "false");
        sampleBooleanFeature.CreateChildFeature("SampleNumericFeature", defaultValue: "10");
        context.Create("SampleSelectionFeature", defaultValue: "B");
    }
}

After creating a feature provider, we should register it in our module's PreInitialize method as shown below:

Configuration.Features.Providers.Add<AppFeatureProvider>();

Basic Feature Properties

A feature definition requires two properties at least:

  • Name: A unique name (as string) to identify the feature.
  • Default value: A default value. This is used when we need the value of the feature and it's not available for current tenant.

Here, we defined a boolean feature named "SampleBooleanFeature" which's default value is "false" (not enabled). We also defined two value features (SampleNumericFeature is defined as child of SampleBooleanFeature).

Tip: Create a const string for a feature name and use it everywhere to prevent typing errors.

特征定義至少需要兩個屬性:

名稱:用於標識特征的唯一名稱(作為字符串)。
默認值:默認值。當我們需要這個特性的值時,它就被使用了,對於當前的租戶來說它是不可用的。
在這里,我們定義一個布爾特征命名為“samplebooleanfeature”的默認值為“假”(不啟用)。我們還定義了兩值特征(SampleNumericFeature定義為SampleBooleanFeature子類)。

提示:為一個t特征名創建一個常量字符串,並在任何地方使用它來防止鍵入錯誤。

Other Feature Properties

While unique name and default value properties are required, there are some optional properties for a detailed control.

  • Scope: A value in FeatureScopes enum. It can be Edition (if this feature can be set only for edition level), Tenant (if this feature can be set only for tenant level) or All (if this feature can be set for editions and tenants, where tenant setting overrides it's edition's setting). Default value is All.
  • DisplayName: A localizable string to show the feature's name to users.
  • Description: A localizable string to show the feature's detailed description to users.
  • InputType: A UI input type for the feature. This can be defined, then can be used while creating an automatic feature screen.
  • Attributes: An arbitrary custom dictionary of key-value pairs those can be related to the feature.
  • 范圍:featurescopes枚舉值。它可以是版本(如果這個功能只能設置為版本級別),租戶(如果這個功能只能為租戶級別設置)或者全部(如果這個功能可以為版本和租戶設置,租戶設置覆蓋它的版本設置)。默認值為全部。
    用戶名:一個本地化字符串顯示特征的名字給用戶。
    描述:一個本地化字符串顯示特征的詳細描述用戶。
    輸入類型:一個用戶界面輸入型特征。這可以定義,然后可以在創建自動功能屏幕時使用。
    屬性:鍵值對的任意自定義字典,它們可以與特性相關。

Let's see more detailed definitions for the features above:

public class AppFeatureProvider : FeatureProvider
{
    public override void SetFeatures(IFeatureDefinitionContext context)
    {
        var sampleBooleanFeature = context.Create(
            AppFeatures.SampleBooleanFeature,
            defaultValue: "false",
            displayName: L("Sample boolean feature"),
            inputType: new CheckboxInputType()
            );

        sampleBooleanFeature.CreateChildFeature(
            AppFeatures.SampleNumericFeature,
            defaultValue: "10",
            displayName: L("Sample numeric feature"),
            inputType: new SingleLineStringInputType(new NumericValueValidator(1, 1000000))
            );

        context.Create(
            AppFeatures.SampleSelectionFeature,
            defaultValue: "B",
            displayName: L("Sample selection feature"),
            inputType: new ComboboxInputType(
                new StaticLocalizableComboboxItemSource(
                    new LocalizableComboboxItem("A", L("Selection A")),
                    new LocalizableComboboxItem("B", L("Selection B")),
                    new LocalizableComboboxItem("C", L("Selection C"))
                    )
                )
            );
    }

    private static ILocalizableString L(string name)
    {
        return new LocalizableString(name, AbpZeroTemplateConsts.LocalizationSourceName);
    }
}

Note that: Input type definitions are not used by ASP.NET Boilerplate. They can be used by applications while creating inputs for features. ASP.NET Boilerplate just provides infrastructure to make it easier.

注意:輸入類型定義不使用ASP.NET框架。它們可以為應用程序使用,同時為特征創建輸入。ASP.NET的框架只是更容易提供基礎設施。

Feature Hierarchy(特征層次

As shown in the sample feature providers, a feature can have child features. A Parent feature is generally defined as boolean feature. Child features will be available only if the parent enabled. ASP.NET Boilerplate does notenforces but suggests this. Applications should take care of it.

如示例特性提供程序所示,一個特性可以具有子特性。父特性通常被定義為布爾特性。只有當父級啟用時,子功能才可用。ASP.NET框架不強制執行單建議這樣。應用程序應該注意它。

Checking Features

We define a feature to check it's value in the application to allow or block some application features per tenant. There are different ways of checking it.

我們定義了一個特性來檢查應用程序中的值,允許或阻止每個租戶的一些應用程序功能。有不同的檢查方法。

Using RequiresFeature Attribute

We can use RequiredFeature attribute for a method or a class as shown below:

[RequiresFeature("ExportToExcel")]
public async Task<FileDto> GetReportToExcel(...)
{
    ...
}

This method is executed only if "ExportToExcel" feature is enabled for the current tenant (current tenant is obtained from IAbpSession). If it's not enabled, an AbpAuthorizationException is thrown automatically.

Surely, RequiresFeature attribute should be used for boolean type features. Otherwise, you may get exceptions.

RequiresFeature attribute notes

ASP.NET Boilerplate uses power of dynamic method interception for feature checking. So, there is some restrictions for the methods use RequiresFeature attribute.

ASP.NET框架采用特征檢測方法動態攔截能力。因此在使用RequiresFeature特性的時候有一些約束。

  • Can not use it for private methods.
  • Can not use it for static methods.
  • Can not use it for methods of a non-injected class (We must use dependency injection).

Also,

  • Can use it for any public method if the method is called over an interface (like Application Services used over interface).
  • A method should be virtual if it's called directly from class reference (like ASP.NET MVC or Web API Controllers).
  • A method should be virtual if it's protected.

Using IFeatureChecker

We can inject and use IFeatureChecker to check a feature manually (it's automatically injected and directly usable for application services, MVC and Web API controllers).

我們可以注入使用ifeaturechecker檢查功能手動(這是自動注入和直接使用的應用服務,MVC和Web API控制器)。

IsEnabled

Used to simply check if given feature is enabled or not. Example:

public async Task<FileDto> GetReportToExcel(...)
{
    if (await FeatureChecker.IsEnabledAsync("ExportToExcel"))
    {
        throw new AbpAuthorizationException("You don't have this feature: ExportToExcel");
    }

    ...
}

IsEnabledAsync and other methods have also sync versions.

Surely, IsEnabled method should be used for boolean type features. Otherwise, you may get exceptions.

If you just want to check a feature and throw exception as shown in the example, you can just use CheckEnabled method.

GetValue

Used to get current value of a feature for value type features. Example:

var createdTaskCountInThisMonth = GetCreatedTaskCountInThisMonth();
if (createdTaskCountInThisMonth >= FeatureChecker.GetValue("MaxTaskCreationLimitPerMonth").To<int>())
{
    throw new AbpAuthorizationException("You exceed task creation limit for this month, sorry :(");
}

FeatureChecker methods have also overrides to work features for a specified tenantId, not only for the current tenantId.

Client Side

In the client side (javascript), we can use abp.features namespace to get current values of the features.

isEnabled
var isEnabled = abp.features.isEnabled('SampleBooleanFeature');
getValue
var value = abp.features.getValue('SampleNumericFeature');

Feature Manager

If you need to definitions of features, you can inject and use IFeatureManager.

A Note For Editions

ASP.NET Boilerplate framework has not a built-in edition system because such a system requires a database (to store editions, edition features, tenant-edition mappings and so on...). Therefore, edition system is implemented in module zero. You can use it to easily have an edition system, or you can implement all yourself.

ASP.NET的模板框架沒有內置版系統,因為這種系統需要一個數據庫(存儲版本,版本的功能,房客版映射等等…)。因此,在零模塊中實現了版本系統。您可以使用它輕松地擁有一個版本系統,或者您可以實現所有您自己。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM