斗爷

导航

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的模板框架没有内置版系统,因为这种系统需要一个数据库(存储版本,版本的功能,房客版映射等等…)。因此,在零模块中实现了版本系统。您可以使用它轻松地拥有一个版本系统,或者您可以实现所有您自己。

posted on 2017-12-09 09:55  斗哥哥  阅读(2186)  评论(0编辑  收藏  举报