多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
## 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个功能的功能提供器。 ```csharp 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中注册它。如下所示: ```csharp 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:与功能相关的自定义任意类型的数据字典。 下面代码详细展示了如何使用上面所描述的属性 ``` csharp 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特性,如下所示: ```csharp [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 可以简单的检测,如果给出的功能是启用或者禁用。如下所示: ```csharp 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 获取当前功能的值并且转换为所需类型,如下所示: ```csharp 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** ```javascript var isEnabled = abp.features.isEnabled('SampleBooleanFeature'); ``` **getValue** ```javascript var value = abp.features.getValue('SampleNumericFeature'); ``` ### 4.5.5 功能管理 如果你需要定义一些功能,你可以注入和使用IFeatureManager。 ### 4.5.6 版本须知 ABP没有建立版本系统,因为这样一个系统需要数据库支持(存储版本,版本功能,租客版映射等等)。因此,版本系统被实现在module zero。使用它你会很容易的拥有自己的版本系统,或者你可以自己完全的实现一个。