本篇目錄
介紹###
大多數的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中了。使用它你可以輕松地擁有一個版本系統,要不然你可以自己實現。