项目

功能特性

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方法(返回TaskTask<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/falseon/offenabled/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,允许01,000,000的值。

记得在本地化文件中定义本地化键:

"PdfReporting": "PDF报表",
"MaxProductCount": "最大产品数量"

有关本地化系统详情参见本地化文档

功能管理模态框

应用程序启动模板 预装了 租户管理功能管理 模块。

定义新功能后,它将出现在功能管理模态框中。要打开此模态框,导航至租户管理页面并为租户选择 Features 操作(如果没有租户则先创建新租户):

features-action

此操作打开一个模态框,用于管理所选租户的功能值:

features-modal

因此,您可以为租户启用、禁用和设置值。当此租户的用户使用应用程序时,将使用这些值。

有关管理功能的更多信息,参见下面的功能管理部分。

子功能

功能可以有子功能。这在希望创建仅当另一功能启用时才可选的功能时特别有用。

示例:定义子功能

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服务。

功能管理

功能管理通常由管理员用户使用功能管理模态框完成:

features-modal

此模态框在相关实体(如多租户应用中的租户)上可用。要打开它,导航至租户管理页面(对于多租户应用),点击租户左侧的操作按钮并选择功能操作。

如需通过代码管理功能,注入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是唯一需要实现的接口,用于从持久化源(通常是数据库系统)读取功能值。功能管理模块实现了它,并预装在应用程序启动模板中。更多信息参见功能管理模块文档

在本文档中