ABP理论学习之功能管理

返回总目录


本篇目录

介绍

大多数的Saas(多租户)应用都有不同 功能版本(包)。因此,他们可以给租户(客户)提供不同的 价格和功能选项

ABP提供了功能系统使得这个更简单。我们可以 定义功能,然后检查某个功能是否对一个租户 开启了,最后将功能系统 集成到其他的ABP概念中(如权限和菜单)。

关于IFeatureValueStore

功能系统使用了IFeatureValueStore来获得功能的值。虽然你可以用自己的方式实现该接口,但是它已经完全实现在了 module-zero项目中。如果没有实现该接口,那么默认会使用NullFeatureValueStore对所有的功能返回null(此时使用默认的功能值)。

功能类型

有两种基本功能类型。

布尔功能

可以是"true"或"false"。这种类型的功能(对于一个版本或者一个租户)可能是开启的或者 关闭的

值功能

可以是任意值。虽然它是以字符串存储和检索的,但是数值也可以轻松地存储为字符串。

比如,我们的应用程序可能是一个任务管理应用,我们可能在一个月内对于创建任务会有限制。假如说我们有两个不同的版本:一个版本每个月允许创建1000个任务,但是另一个每个月允许创建5000个任务。因此,这个功能应该存储为值,而不是简单的true或false。

定义功能

在检查功能之前应该先定义功能。一个模块可以通过从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");
    }
}

创建功能提供者之后,我们应该在模块的PreInitialize方法中注册,如下所示:

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

基本功能属性

一个功能的定义至少要求两个属性:

  • Name:识别该功能唯一的名字(字符串)。
  • DefaultValue:默认值。当我们需要该功能的值时会用到该属性,而且对于当前的租户不可用。

上面的代码样例中,我们定义了一个名为"SampleBooleanFeature"布尔功能,它的默认值是"false"(不可用)。我们也定义了两个值功能(SampleNumericFeature定义为SampleBooleanFeature的孩子)。

提示:为功能名称创建一个常量字符串,然后在任何地方使用时会防止拼写失误。

其他功能属性

虽然对于ABP来说一个唯一的名称和默认值属性已经足够了,但是对于细节的控制还有许多其他的功能属性。

  • Scope:FeatureScope枚举值之一。它可以是Edition(如果只为版本级别设置该功能), Tenant(如果只为租户级别设置该功能),或者 All(如果为版本和租户都可以设置该功能,这种情况下,租户的设置会覆盖版本的设置)。默认值是All。
  • DisplayName:给用户显示该功能名称的本地化字符串。
  • Description:给用户显示该功能细节描述的本地化字符串。
  • InputType:该功能的UI输入类型。
  • Attributes:任意的自定义键值对字典,可以和该功能关联起来。

让我们看一下该功能的细节定义:

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);
    }
}

注意:ABP没有使用这里的InputType。当为功能创建输入时,应用程序会使用它们。ABP只是提供了这些选项使得它更容易。

功能层次

正如样例功能提供者所示,一个功能可以有子功能。一个父母功能一般定义为 布尔功能。只有父母功能可用时,孩子功能才可用。ABP不强制这样做,但是建议这样做。

检查功能

使用RequireFeature特性

我们可以为方法或类使用RequiredFeature特性,如下所示:

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

该方法只有在"ExportToExcel"功能对当前租户开启时才会执行(当前租户从IAbpSession中获得)。如果没有开启该功能,那么就会自动抛出 AbpAuthorizationException

当然,RequiresFeature特性应该用于布尔类型功能。否则,你会得到异常。

RequiresFeature特性注意点
ABP对于功能检查使用了强大的动态方法拦截(interception)。因此,为方法使用RequiresFeature特性时有一些限制条件:

  • 不能用于私有方法。
  • 不能用于静态方法。
  • 不能用于非注入类的方法(我们必须使用DI)。

此外,

  • 如果该方法是通过一个接口(如应用服务通过接口调用)调用的,那么我们可以将它用于任何 public的方法。
  • 如果一个方法直接从类的引用调用(如MVC或Web API控制器),那么它应该是virtual的。
  • 如果一个方法是protected,那么该方法应该是 virtual

使用IFeatureChecker

我们可以注入并使用IFeatureChecker来手动检查一个功能(对于应用服务,MVC和Web API控制器,它会自动注入而且直接可以使用)。

IsEnabled

用于简单地检查给定的功能是否开启。例子:

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

IsEnabledAsync和其他方法都有同步版本。

当然,IsEnabled方法应该用于布尔类型功能。否则可能会抛异常。

如果你只想检查一个功能,然后抛出例子中的异常,那么你只需要使用CheckEnabled方法就行了。

GetValue

用于获得值类型功能的当前值,例子:

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

FeatureChecker方法也有对于特定租户的重载,不仅仅只对于当前的租户。

客户端

在客户端,我们使用abp.features命名空间来获得该功能的当前值。

isEnabled

var isEnabled = abp.features.isEnabled('SampleBooleanFeature');

getValue

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

功能管理者

如果需要定义功能,可以注入并使用IFeatureManager

版本说明

ABP没有内置的版本系统,因为这么个系统要求数据库(存储版本,版本功能,租户-版本映射等等)。因此,版本系统实现在了module-zero中了。使用它你可以轻松地拥有一个版本系统,要不然你可以自己实现。

posted @ 2016-01-02 22:26 tkbSimplest 阅读(...) 评论(...) 编辑 收藏