功能特性
ABP功能系统用于在运行时 启用、禁用或改变应用程序功能的行为。
功能的运行时值通常是一个布尔值,例如 true(启用)或 false(禁用)。然而,您可以为功能获取/设置任意类型的值。
功能系统最初设计用于控制 多租户 应用程序中的租户功能。但它具有可扩展性,能够根据任意条件确定功能。
功能系统通过Volo.Abp.Features NuGet包实现。大多数情况下您无需手动 安装它 ,因为它已预装在 应用程序启动模板 中。
检查功能
在说明如何定义功能之前,先看看如何在应用程序代码中检查功能值。
RequiresFeature特性
[RequiresFeature] 特性(定义在 Volo.Abp.Features 命名空间中)用于声明式检查功能是否为 true(启用)。这是检查布尔功能的便捷方式。
示例:检查“PDF报表”功能是否启用
public class ReportingAppService : ApplicationService, IReportingAppService
{
[RequiresFeature("MyApp.PdfReporting")]
public async Task<PdfReportResultDto> GetPdfReportAsync()
{
//待实现...
}
}
RequiresFeature(...)只需传入功能名称以检查是否启用。如果未启用,将抛出授权异常并向客户端返回适当响应。[RequiresFeature]可用于方法或类。用于类时,该类的所有方法都需要指定功能。RequiresFeature可接受多个功能名称,如[RequiresFeature("Feature1", "Feature2")]。此时ABP检查是否有任一功能启用。使用RequiresAll选项,如[RequiresFeature("Feature1", "Feature2", RequiresAll = true)]强制检查所有功能是否启用。- 方法或类支持多次使用
[RequiresFeature]特性,ABP会检查所有指定功能。
功能名称可以是任意字符串,应保证唯一性。
关于拦截
ABP使用拦截系统使[RequiresFeature]特性生效。因此,它适用于通过依赖注入注入的任何类(应用服务、控制器等)。
但需遵循一些规则才能正常工作:
- 如果未通过接口(如
IMyService)注入服务,则服务方法必须为virtual。否则动态代理/拦截系统无法工作。 - 仅拦截
async方法(返回Task或Task<T>的方法)。
控制器和Razor页面方法例外。它们不需要遵循上述规则,因为ABP在此情况下使用操作/页面过滤器实现功能检查。
IFeatureChecker服务
IFeatureChecker允许在应用程序代码中检查功能。
IsEnabledAsync
如果给定功能启用则返回true,可根据条件执行业务流程。
示例:检查“PDF报表”功能是否启用
public class ReportingAppService : ApplicationService, IReportingAppService
{
private readonly IFeatureChecker _featureChecker;
public ReportingAppService(IFeatureChecker featureChecker)
{
_featureChecker = featureChecker;
}
public async Task<PdfReportResultDto> GetPdfReportAsync()
{
if (await _featureChecker.IsEnabledAsync("MyApp.PdfReporting"))
{
//待实现...
}
else
{
//待实现...
}
}
}
IsEnabledAsync有重载方法可一次调用检查多个功能。
GetOrNullAsync
获取功能的当前值。此方法返回string,因此可通过与string转换存储任意类型的值。
示例:检查允许的最大产品数量
public class ProductController : AbpController
{
private readonly IFeatureChecker _featureChecker;
public ProductController(IFeatureChecker featureChecker)
{
_featureChecker = featureChecker;
}
public async Task<IActionResult> Create(CreateProductModel model)
{
var currentProductCount = await GetCurrentProductCountFromDatabase();
//获取功能值
var maxProductCountLimit =
await _featureChecker.GetOrNullAsync("MyApp.MaxProductCount");
if (currentProductCount >= Convert.ToInt32(maxProductCountLimit))
{
throw new BusinessException(
"MyApp:ReachToMaxProductCountLimit",
$"您最多只能创建{maxProductCountLimit}个产品!"
);
}
//待实现:在数据库中创建产品...
}
private async Task<int> GetCurrentProductCountFromDatabase()
{
throw new System.NotImplementedException();
}
}
此示例在SaaS应用中使用数值功能限制用户/租户的产品数量。
除了手动转换为int,可使用GetAsync方法的泛型重载:
var maxProductCountLimit = await _featureChecker.GetAsync<int>("MyApp.MaxProductCount");
扩展方法
IFeatureChecker接口有一些有用的扩展方法:
Task<T> GetAsync<T>(string name, T defaultValue = default):用于获取指定类型T的功能值。可指定defaultValue,当功能值为null时返回。CheckEnabledAsync(string name):检查给定功能是否启用。如果功能不为true(启用),抛出AbpAuthorizationException。
定义功能
需定义功能才能进行检查。
FeatureDefinitionProvider
创建继承FeatureDefinitionProvider的类来定义功能。
示例:定义功能
using Volo.Abp.Features;
namespace FeaturesDemo
{
public class MyFeatureDefinitionProvider : FeatureDefinitionProvider
{
public override void Define(IFeatureDefinitionContext context)
{
var myGroup = context.AddGroup("MyApp");
myGroup.AddFeature(
"MyApp.PdfReporting",
defaultValue: "false"
);
myGroup.AddFeature(
"MyApp.MaxProductCount",
defaultValue: "10",
valueType: new FreeTextStringValueType(new NumericValueValidator())
);
}
}
}
ABP自动发现此类并注册功能,无需额外配置。
此类通常创建在解决方案的
Application.Contracts项目中。
- 在
Define方法中,首先为应用/模块添加功能组或获取现有组,然后向该组添加功能。 - 第一个功能名为
MyApp.PdfReporting,是默认值为false的布尔功能。 - 第二个功能名为
MyApp.MaxProductCount,是默认值为10的数值功能。
如果当前用户/租户未设置其他值,则使用默认值。
其他功能属性
虽然这些最小定义足以使功能系统工作,但可为功能指定可选属性:
DisplayName:用于在用户界面显示功能名称的可本地化字符串。Description:描述功能的较长可本地化文本。ValueType:功能值类型。可以是实现IStringValueType的类。内置类型:ToggleStringValueType:用于定义true/false、on/off、enabled/disabled类功能。UI显示复选框。FreeTextStringValueType:用于定义自由文本值。UI显示文本框。SelectionStringValueType:用于强制从列表中选择值。UI显示下拉列表。
IsVisibleToClients(默认:true):设为false可对客户端(浏览器)隐藏此功能值。与客户端共享值有助于其根据功能值条件性显示/隐藏/更改UI部分。Properties:用于设置/获取与此功能相关的任意键值对的字典。可作为自定义点。
因此,基于这些描述,最好按以下方式定义功能:
using FeaturesDemo.Localization;
using Volo.Abp.Features;
using Volo.Abp.Localization;
using Volo.Abp.Validation.StringValues;
namespace FeaturesDemo
{
public class MyFeatureDefinitionProvider : FeatureDefinitionProvider
{
public override void Define(IFeatureDefinitionContext context)
{
var myGroup = context.AddGroup("MyApp");
myGroup.AddFeature(
"MyApp.PdfReporting",
defaultValue: "false",
displayName: LocalizableString
.Create<FeaturesDemoResource>("PdfReporting"),
valueType: new ToggleStringValueType()
);
myGroup.AddFeature(
"MyApp.MaxProductCount",
defaultValue: "10",
displayName: LocalizableString
.Create<FeaturesDemoResource>("MaxProductCount"),
valueType: new FreeTextStringValueType(
new NumericValueValidator(0, 1000000))
);
}
}
}
- 示例代码中
FeaturesDemoResource为项目名称。有关本地化系统详情参见本地化文档。 - 第一个功能设为
ToggleStringValueType,第二个设为带数值验证器的FreeTextStringValueType,允许0到1,000,000的值。
记得在本地化文件中定义本地化键:
"PdfReporting": "PDF报表",
"MaxProductCount": "最大产品数量"
有关本地化系统详情参见本地化文档。
功能管理模态框
定义新功能后,它将出现在功能管理模态框中。要打开此模态框,导航至租户管理页面并为租户选择 Features 操作(如果没有租户则先创建新租户):
此操作打开一个模态框,用于管理所选租户的功能值:
因此,您可以为租户启用、禁用和设置值。当此租户的用户使用应用程序时,将使用这些值。
有关管理功能的更多信息,参见下面的功能管理部分。
子功能
功能可以有子功能。这在希望创建仅当另一功能启用时才可选的功能时特别有用。
示例:定义子功能
using FeaturesDemo.Localization;
using Volo.Abp.Features;
using Volo.Abp.Localization;
using Volo.Abp.Validation.StringValues;
namespace FeaturesDemo
{
public class MyFeatureDefinitionProvider : FeatureDefinitionProvider
{
public override void Define(IFeatureDefinitionContext context)
{
var myGroup = context.AddGroup("MyApp");
var reportingFeature = myGroup.AddFeature(
"MyApp.Reporting",
defaultValue: "false",
displayName: LocalizableString
.Create<FeaturesDemoResource>("Reporting"),
valueType: new ToggleStringValueType()
);
reportingFeature.CreateChild(
"MyApp.PdfReporting",
defaultValue: "false",
displayName: LocalizableString
.Create<FeaturesDemoResource>("PdfReporting"),
valueType: new ToggleStringValueType()
);
reportingFeature.CreateChild(
"MyApp.ExcelReporting",
defaultValue: "false",
displayName: LocalizableString
.Create<FeaturesDemoResource>("ExcelReporting"),
valueType: new ToggleStringValueType()
);
}
}
}
以上示例定义了一个报表功能,带有两个子功能:PDF报表和Excel报表。
更改依赖模块的功能定义
派生自FeatureDefinitionProvider的类(如上例)也可获取现有功能定义(由依赖模块定义)并更改其定义。
示例:操作现有功能定义
var someGroup = context.GetGroupOrNull("SomeModule");
var feature = someGroup.Features.FirstOrDefault(f => f.Name == "SomeFeature");
if (feature != null)
{
feature.Description = ...
feature.CreateChild(...);
}
在客户端检查功能
除非在功能定义中将IsVisibleToClients设为false,否则功能值在客户端也可用。功能值通过应用程序配置API暴露,并可通过UI上的某些服务使用。
参见以下文档了解如何在不同UI类型中检查功能:
Blazor应用程序可使用与上述相同的IFeatureChecker服务。
功能管理
功能管理通常由管理员用户使用功能管理模态框完成:
此模态框在相关实体(如多租户应用中的租户)上可用。要打开它,导航至租户管理页面(对于多租户应用),点击租户左侧的操作按钮并选择功能操作。
如需通过代码管理功能,注入IFeatureManager服务。
示例:为租户启用PDF报表
public class MyService : ITransientDependency
{
private readonly IFeatureManager _featureManager;
public MyService(IFeatureManager featureManager)
{
_featureManager = featureManager;
}
public async Task EnablePdfReporting(Guid tenantId)
{
await _featureManager.SetForTenantAsync(
tenantId,
"MyApp.PdfReporting",
true.ToString()
);
}
}
IFeatureManager由功能管理模块定义。它预装在应用程序启动模板中。更多信息参见功能管理模块文档。
高级主题
功能值提供程序
功能系统可扩展。任何派生自FeatureValueProvider(或实现IFeatureValueProvider)的类都可为功能系统做贡献。值提供程序负责获取给定功能的当前值。
功能值提供程序按顺序执行。如果其中一个返回非空值,则使用此功能值,不执行其他提供程序。
有三个预定义的值提供程序,按给定顺序执行:
TenantFeatureValueProvider尝试获取是否为当前租户显式设置了功能值。EditionFeatureValueProvider尝试获取当前版本的功能值。版本ID从当前主体身份(ICurrentPrincipalAccessor)通过声明名editionid(定义为AbpClaimTypes.EditionId的常量)获取。租户管理模块未实现版本。您可以自行实现或考虑使用ABP Commercial的SaaS模块。DefaultValueFeatureValueProvider获取功能的默认值。
可通过继承FeatureValueProvider编写自己的提供程序。
示例:为具有“User_Type”声明值为“SystemAdmin”的用户启用所有功能
using System.Threading.Tasks;
using Volo.Abp.Features;
using Volo.Abp.Security.Claims;
using Volo.Abp.Validation.StringValues;
namespace FeaturesDemo
{
public class SystemAdminFeatureValueProvider : FeatureValueProvider
{
public override string Name => "SA";
private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor;
public SystemAdminFeatureValueProvider(
IFeatureStore featureStore,
ICurrentPrincipalAccessor currentPrincipalAccessor)
: base(featureStore)
{
_currentPrincipalAccessor = currentPrincipalAccessor;
}
public override Task<string> GetOrNullAsync(FeatureDefinition feature)
{
if (feature.ValueType is ToggleStringValueType &&
_currentPrincipalAccessor.Principal?.FindFirst("User_Type")?.Value == "SystemAdmin")
{
return Task.FromResult("true");
}
return Task.FromResult<string>(null);
}
}
}
如果提供程序返回null,则执行下一个提供程序。
定义提供程序后,应将其添加到AbpFeatureOptions中,如下所示:
Configure<AbpFeatureOptions>(options =>
{
options.ValueProviders.Add<SystemAdminFeatureValueProvider>();
});
在模块类的ConfigureServices中使用此代码。
功能存储
IFeatureStore是唯一需要实现的接口,用于从持久化源(通常是数据库系统)读取功能值。功能管理模块实现了它,并预装在应用程序启动模板中。更多信息参见功能管理模块文档
抠丁客




