4.5 ABP应用层 - 功能管理
4.5.1 简介
大多数的SaaS(多租户) 应用拥有多个版本并且这些版本的功能各不相同。因此,他们能为客户提供不同的价格和功能选项。
我们可以很容易的用ABP来实现这个功能管理系统。我们能定义一些功能,检查功能是否为租户开启。这个就像ABP的设计思想(例如权限和菜单设计)。
关于 IFeatureValueStore
我们可以利用 IFeatureValueStore获取功能值。在module-zero项目里面已有了全部实现,当然你也能用自己的方式来扩展它们。如果没有实现该接口,NullFeatureValueStore (默认实现)会被使用,它会为所有功能值返回null(默认值将会被用在这个案例中)。
4.5.2 功能类型
系统已有两个基本的功能类型。
1. Boolean 功能
可以是true或者false。利用这个类型我们可以为租户启用(enable)或者禁用(disable)某些功能。
2. Value 功能
可以为任意值。我们可以用它来存储并者取得一个字符串,我们也可以很容易的用数字作为字符串来存储。
例如,我们有一个任务管理系统,可以限制一个月内的任务创建数量的需求。那么我们可以根据需求创建两个版本系统;一个是每个月允许创建1000个任务,而另外一个是每个月允许创建5000个任务。所以,这个功能应该用Value类型来存储,而不是用简单的Boolean类型(true或者false)。
3. 定义功能
功能应该在检查之前被定义。模块可以定义它自己的功能,通过从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>();
4. 基本功能属性
定义功能至少需要两个属性:
Name:用来区分功能的唯一标识符(string类型);
Default value:默认值。当我们需要这个功能值的时候我们就会用到它,并且该值对当前租户是不可用的。
在这里,我们定义了一个叫做SampleBooleanFeature的boolean类型的功能,默认值是false(禁用)。
我们还定义了两个value类型的功能(SampleNumericFeature 是SampleBooleanFeature的子功能)。
建议:
创建字符串常量的功能名称并且我们可以在任何地方使用它,还可以有效避免功能名称输入错误(记忆力有时候不行咯)。
5. 其它功能属性
唯一标识(name)和默认值属性已经足够满足需求了,但这里还有一些其它的属性来满足更细粒化的控制需求。
Scope:枚举类型FeatureScopes的某个值 ,可以是Edition (如果该功能只对Edition 等级的用户开放),Tenant(如果该功能仅对Tenant等级的用户开放)或者All(All等级可以拥有Edition和Tenant等级的所有功能,但是租户的设置会覆盖版本的设置);默认设置是All级别。
DisplayName:本地化字符串,向用户展示功能名称。
Description:本地化字符串,向用户的详细描述该功能。
InputType:UI层的输入类型(控件类型:Checkbox, Combobox等)。
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使用,ABP提供这些选项让我们能够更便捷的来创建我们所需要的功能。
4.5.3 功能层次
正如上面示例所展示的,功能可以有子功能。父功能通常被定义为boolean类型的功能,如果父功能被启用,那么子功能将会是可用的。ABP不会强制这样使用,但是建议你这样使用。
4.5.4 功能检测
在系统中我们定义这些功能,是用来检测这些功能是否应该对每个用户(租户)启用(允许)或者禁用(阻止)。我们可以用不同的方法来检测它们。
2. 使用RequiresFeature特性
我们可以在类或者方法上面用RequiresFeature特性,如下所示:
[RequiresFeature("ExportToExcel")]
public async Task<FileDto> GetReportToExcel(...)
{
...
}
如果ExportToExcel功能为当前租户(从IAbpsession获取当前租户)启用,那么这个方法会被执行;如果没有被启用,那么将会自动的抛出一个AbpAuthorizationException 异常。
当然RequiresFeature特性应该使用的是boolean类型功能 ,否则你会得到一个异常。
不能用在一个私有方法上
不能用在一个静态方法上
不能用在一个非注入类的方法上(我们必须使用Dependency Injection).
还有:
可以用在任何public方法上,如果这个方法是通过扩展接口来实现的(就像Application Services扩展指定接口一样)
方法应该是virtual方法,如果被直接从类引用中调用(就像 ASP.NET MVC 或者 Web API Controllers)。
方法应该是virtual方法,如果它是一个protected.
3. 使用IFeatureChecker
我们可以注入和使用IFeatureChecker接口来手动的检测功能(它是被自动注入,并对Application Services,MVC和Web API Controllers 直接可用)。
4. IsEnabled
可以简单的检测,如果给出的功能是启用或者禁用。如下所示:
public async Task<FileDto> GetReportToExcel(...)
{
if (await FeatureChecker.IsEnabledAsync("ExportToExcel"))
{
throw new AbpAuthorizationException("You don't have this feature: ExportToExcel");
}
...
}
IsEnabledAsync 还有其他的方法也有同步版本。
当然,IsEnabled方法应该使用booean类型功能。否则,你会得到一个异常。
正如示例所示,如果你只是想检测功能并且抛出异常,你只需要使用CheckEnabled方法。
5. GetValue
获取当前功能的值并且转换为所需类型,如下所示:
var createdTaskCountInThisMonth = GetCreatedTaskCountInThisMonth();
if (createdTaskCountInThisMonth >= FeatureChecker.GetValue("MaxTaskCreationLimitPerMonth").To<int>())
{
throw new AbpAuthorizationException("You exceed task creation limit for this month, sorry :(");
}
6. 客户端
在客户端,我们能使用abp.features命名空间去取得当前功能的值。
isEnabled
var isEnabled = abp.features.isEnabled('SampleBooleanFeature');
getValue
var value = abp.features.getValue('SampleNumericFeature');
4.5.5 功能管理
如果你需要定义一些功能,你可以注入和使用IFeatureManager。
4.5.6 版本须知
ABP没有建立版本系统,因为这样一个系统需要数据库支持(存储版本,版本功能,租客版映射等等)。因此,版本系统被实现在module zero。使用它你会很容易的拥有自己的版本系统,或者你可以自己完全的实现一个。