ABP Framework-Feature源码解析

目录

https://abp.io/docs/6.0/Features

Version

6.0.3

Package

Volo.Abp.Features


//独立模块
Volo.Abp.FeatureManagement.*

FeatureDefinitionProvider

内容定义

在每个module中都可以定义Feature,遵从如下示例格式

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");
    }
}

在AbpFeaturesModule中,基类FeatureDefinitionProvider在预初始化时会被扫描到。

[DependsOn(
    typeof(AbpLocalizationModule),
    typeof(AbpMultiTenancyModule),
    typeof(AbpValidationModule),
    typeof(AbpAuthorizationAbstractionsModule)
    )]
public class AbpFeaturesModule : AbpModule
{
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        context.Services.OnRegistred(FeatureInterceptorRegistrar.RegisterIfNeeded);
        AutoAddDefinitionProviders(context.Services);
    }
    
    //...
    
    private static void AutoAddDefinitionProviders(IServiceCollection services)
    {
        var definitionProviders = new List<Type>();


        // 收集所有继承自IFeatureDefinitionProvider的类
        services.OnRegistred(context =>
        {
            if (typeof(IFeatureDefinitionProvider).IsAssignableFrom(context.ImplementationType))
            {
                definitionProviders.Add(context.ImplementationType);
            }
        });


        // 将所有SettingDefinitionProvider保存到AbpFeatureOptions中
        services.Configure<AbpFeatureOptions>(options =>
        {
            options.DefinitionProviders.AddIfNotContains(definitionProviders);
        });
    }
}

如上需要注意AbpFeatureOptions,所有的FeatureDefinitionProvider最终汇总到其内部的集合中,同时查看该类可知也存储着不同值来源的FeatureValueProvider。

public class AbpFeatureOptions
{
    public ITypeList<IFeatureDefinitionProvider> DefinitionProviders { get; }


    public ITypeList<IFeatureValueProvider> ValueProviders { get; }


    public AbpFeatureOptions()
    {
        DefinitionProviders = new TypeList<IFeatureDefinitionProvider>();
        ValueProviders = new TypeList<IFeatureValueProvider>();
    }
}

该部分类图简要如下 204706395_1a3785d1-af81-4478-9be5-acd3e5e74eee

修改定义

当对于引用的模块内部已有的定义不太符合需要时,可以更改已有的Feature定义,注意需要在Module类中依赖引用的模块。

public class MyFeatureDefinitionProvider : FeatureDefinitionProvider
{
    public override void Define(IFeatureDefinitionContext context)
    {
        var myApp = context.GetGroupOrNull("MyApp");
        if (myApp != null)
        {
            myApp.
        }
    }
}

FeatureDefinitionContext

封装了功能分组和功能定义内容,衔接着FeatureDefinitionManager和FeatureDefinitionProvider。内部主要是在字典上做增删查操作。

public class FeatureDefinitionContext : IFeatureDefinitionContext
{
    internal Dictionary<string, FeatureGroupDefinition> Groups { get; }
    //...
    public FeatureGroupDefinition AddGroup(string name, ILocalizableString displayName = null)
    {
        //...
        return Groups[name] = new FeatureGroupDefinition(name, displayName);
    }


    public FeatureGroupDefinition GetGroupOrNull(string name)
    {
        //...
        return Groups[name];
    }


    public void RemoveGroup(string name)
    {
        //...
        Groups.Remove(name);
    }
}

FeatureDefinitionManager

对于众多的FeatureDefinitionProvider,在这之上封装了一层IFeatureDefinitionManager来管理所有的FeatureDefinitionProvider。

204707824_cad9ee91-b52f-4781-b410-66600d4badcb IFeatureDefinitionManager该部分源码简要如下,主要关注从DefinitionProviders转移定义到自身字典属性中。

public class FeatureDefinitionManager : IFeatureDefinitionManager, ISingletonDependency
{
    protected IDictionary<string, FeatureGroupDefinition> FeatureGroupDefinitions => _lazyFeatureGroupDefinitions.Value;
    protected IDictionary<string, FeatureDefinition> FeatureDefinitions => _lazyFeatureDefinitions.Value;
    protected AbpFeatureOptions Options { get; }
    //...
    
    public virtual IReadOnlyList<FeatureDefinition> GetAll()
    {
        return FeatureDefinitions.Values.ToImmutableList();
    }


    public virtual FeatureDefinition GetOrNull(string name)
    {
        return FeatureDefinitions.GetOrDefault(name);
    }


    public IReadOnlyList<FeatureGroupDefinition> GetGroups()
    {
        return FeatureGroupDefinitions.Values.ToImmutableList();
    }


    protected virtual Dictionary<string, FeatureDefinition> CreateFeatureDefinitions()
    {
        var features = new Dictionary<string, FeatureDefinition>();


        //循环遍历收集到的所有FeatureGroupDefinitions
        foreach (var groupDefinition in FeatureGroupDefinitions.Values)
        {
            //从FeatureGroupDefinitions中汇总FeatureDefinition到全局字典FeatureDefnitions中
            foreach (var feature in groupDefinition.Features)
            {
                AddFeatureToDictionaryRecursively(features, feature);
            }
        }


        return features;
    }


    protected virtual void AddFeatureToDictionaryRecursively(
        Dictionary<string, FeatureDefinition> features,
        FeatureDefinition feature)
    {
        if (features.ContainsKey(feature.Name))
        {
            throw new AbpException("Duplicate feature name: " + feature.Name);
        }


        features[feature.Name] = feature;


        //Feature拥有子Feature
        foreach (var child in feature.Children)
        {
            AddFeatureToDictionaryRecursively(features, child);
        }
    }


    protected virtual Dictionary<string, FeatureGroupDefinition> CreateFeatureGroupDefinitions()
    {
        var context = new FeatureDefinitionContext();


        using (var scope = _serviceScopeFactory.CreateScope())
        {
            var providers = Options
                .DefinitionProviders
                .Select(p => scope.ServiceProvider.GetRequiredService(p) as IFeatureDefinitionProvider)
                .ToList();
          
            //循环遍历所有FeatureDefinitionProvider,收集FeatureGroupDefinition
            foreach (var provider in providers)
            {
                provider.Define(context);
            }
        }


        return context.Groups;
    }
}
  1. 在注册阶段,扫描到所有的DefinitionProvider注册到AbpFeatureOptions.DefinitionProviders中。

  2. 在FeatureDefinitionManager中则读取所有的DefinitionProviders循环调用Definie将所有GroupDefinition保存到FeatureDefinitionManager自身字典属性中,该过程,使用FeatureDefinitionContext来完成承载。

var context = new FeatureDefinitionContext();
provider.Define(context);

Dictionary<string, FeatureGroupDefinition> groups,保存到FeatureDefinitionContext中,各个DefinitionProvider则需要接收一个FeatureDefinitionContext,因此从FeatureDefinitionManager到FeatureDefinitonProvider需要借助一个媒介FeatureDefinitionContext。

public class MyFeatureDefinitionProvider : FeatureDefinitionProvider
{
    public override void Define(IFeatureDefinitionContext context)
    {
        var group = context.AddGroup("Test Group");
        group.AddFeature("BooleanTestFeature1");
    }
}

FeatureDefinitionManager->FeatureDefinitionContext->FeatuureDefinitonProvider 3. 后续获取Feature定义则直接从自身属性FeatureDefnitions中获取。

简要概括该部分步骤为:

  • 各模块中完成FeatureDefinitionProvider定义。

  • 扫描所有FeatureDefinitionProvider存储到AbpFeatureOptions中。

  • 初始化FeatureDefinitionManager时,先从AbpFeatureOption中读取定义经过FeatureDefinitionContext转移到FeatureGroupDefinitions字典属性中。

  • 再遍历所有FeatureGroupDefinitions收集具体的FeatureDefinition保存到FeatureDefinitions字典属性中。

  • 读取Feature时,直接从FeatureDefinitions字典属性中读取。

FeatureValueProvider

为了从各处数据源获取Feature定义对应的值,Abp设计了FeatureValueProvider,一个定义的Feature,可以有多个ValueProvider来源,数据库,appsetting.json,文件等各种源头。

204708934_1ad30bf3-1874-4def-a802-1bd3051a6ad0 在AbpFeatureModule中已提前注册好了几个默认的ValueProvider,对于自定义的ValueProvider也需要在开发的module中采用相同方式手动注册到ValueProviders集合中。

[DependsOn(
    typeof(AbpLocalizationModule),
    typeof(AbpMultiTenancyModule),
    typeof(AbpValidationModule),
    typeof(AbpAuthorizationAbstractionsModule)
    )]
public class AbpFeaturesModule : AbpModule
{
    //...
    
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        context.Services.Configure<AbpFeatureOptions>(options =>
        {
            options.ValueProviders.Add<DefaultValueFeatureValueProvider>();
            options.ValueProviders.Add<EditionFeatureValueProvider>();
            options.ValueProviders.Add<TenantFeatureValueProvider>();
        });


        //...
    }


    //...
}

默认的几个ValueProvider各自作用如下,

  • DefaultValueFeatureValueProvider,从Feature定义中获取默认值,源头是定义本身。

  • Edition/TenantFeatureValueProvider,分别从版本/租户角度取Feature,最终取的逻辑又移交给IFeatureStore。以租户为例,取得当前租户Id,依赖FeatureStore获取到Feature的具体值。

public class TenantFeatureValueProvider : FeatureValueProvider
{
    public const string ProviderName = "T";


    public override string Name => ProviderName;


    protected ICurrentTenant CurrentTenant { get; }


    public TenantFeatureValueProvider(IFeatureStore featureStore, ICurrentTenant currentTenant)
        : base(featureStore)
    {
        CurrentTenant = currentTenant;
    }


    public override async Task<string> GetOrNullAsync(FeatureDefinition feature)
    {
        return await FeatureStore.GetOrNullAsync(feature.Name, Name, CurrentTenant.Id?.ToString());
    }
}

对于ValueProvider,需要对每一个Provider都有唯一的ProviderName,以示区分。如上多个ValueProvider,当在一个ValueProvider中得到值后则不会进入下一个ValueProvider中,如果有优先级的需要,则要考虑注册时的优先级设置。

public class FeatureChecker : FeatureCheckerBase
{
    //...
    protected virtual async Task<string> GetOrNullValueFromProvidersAsync(
        IEnumerable<IFeatureValueProvider> providers,
        FeatureDefinition feature)
    {
        foreach (var provider in providers)
        {
            //取到有效值后直接返回
            var value = await provider.GetOrNullAsync(feature);
            if (value != null)
            {
                return value;
            }
        }


        return null;
    }
}

FeatureStore

部分FeatureValueProvider完成了获取值,但是实际的功能依赖于FeatureStore中。

204709922_9998a7e6-3d60-420f-b0b8-59990ee689ac 在FeatureManagement模块中实现了FeatureStore,也没有实现具体的取值逻辑,而是依赖FeatureManagementStore。

public class FeatureStore : IFeatureStore, ITransientDependency
{
    protected IFeatureManagementStore FeatureManagementStore { get; }


    public FeatureStore(IFeatureManagementStore featureManagementStore)
    {
        FeatureManagementStore = featureManagementStore;
    }


    public virtual Task<string> GetOrNullAsync(string name, string providerName, string providerKey)
    {
        return FeatureManagementStore.GetOrNullAsync(name, providerName, providerKey);
    }
}

此处FeatureStore算是一个Wrapper,FeatureManagementStore承载了对功能的管理职责,而FeatureStore只承担读取职责,从设计上,分开成两个,服务两套系统确实合理。 FeatureStore->FeatureManagementStore->FeatureRepository->FeatureTable

在FeatureManagementStore中最终依赖仓储从数据库表中获取Feature值。

public class FeatureManagementStore : IFeatureManagementStore, ITransientDependency
{
    //...
    public virtual async Task<string> GetOrNullAsync(string name, string providerName, string providerKey)
    {
        // ...
        (await FeatureValueRepository.GetListAsync(providerName, providerKey))
            .ToDictionary(s => s.Name, s => s.Value)
        // ...
    }
    //...
}

此处可以参照FeatureStore,扩展业务自身想要实现的功能,比如自定义Feature表,而不用依赖Abp生成的Feature表。

FeatureChecker

Feature定义好了,其值配置好了,剩下的则是在代码中通过定义,取到值。在业务代码中,直接使用IFeatureChecker来检查是否拥有特定Feature。

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();


        //GET THE FEATURE VALUE
        var maxProductCountLimit = await _featureChecker.GetOrNullAsync("MyApp.MaxProductCount");


        if (currentProductCount >= Convert.ToInt32(maxProductCountLimit))
        {
            throw new BusinessException(
                "MyApp:ReachToMaxProductCountLimit",
                $"You can not create more than {maxProductCountLimit} products!"
            );
        }


        //TODO: Create the product in the database...
    }


    private async Task<int> GetCurrentProductCountFromDatabase()
    {
        throw new System.NotImplementedException();
    }
}

Abp定义了IFeatureChecker,传入一个或多个Feature定义名,返回对应的一组值。 204711212_de027ae1-8ff6-4717-973a-b70b78fc5241 如下简要解析源码,以GetOrNull方法为例。

public class FeatureProvider : FeatureCheckerBase
{
    protected AbpFeatureOptions Options { get; }
    protected IFeatureDefinitionManager FeatureDefinitionManager{ get; }
    protected List<IFeatureValueProvider> Providers { get; }


    public FeatureChecker(
        IOptions<AbpFeatureOptions> options,
        IServiceProvider serviceProvider,
        IFeatureDefinitionManager featureDefinitionManager)
    {
        Options = options.Value;
        FeatureDefinitionManager = featureDefinitionManager;
        Providers = new Lazy<List<IFeatureValueProvider>>(
            () => Options
                .ValueProviders
                .Select(type => ServiceProvider.GetRequiredService(type) as IFeatureValueProvider)
                .ToList(),
            true
        );
    }


    public virtual async Task<string> GetOrNullAsync(string name)
    {
        // 按照name获取FeatureDefinition
        var featureDefinition = FeatureDefinitionManager.Get(name);


        // 拿到所有ValueProviders
        var providers = Enumerable.Reverse(Providers);
        if (featureDefinition.AllowedProviders.Any())
        {
            providers = providers.Where(p => featureDefinition.AllowedProviders.Contains(p.Name));
        }


        return await GetOrNullValueFromProvidersAsync(providers, featureDefinition);
    }


    protected virtual async Task<string> GetOrNullValueFromProvidersAsync(
        IEnumerable<IFeatureValueProvider> providers,
        FeatureDefinition feature)
    {
        //循环遍历找到有效值直接返回
        foreach (var provider in providers)
        {
            var value = await provider.GetOrNullAsync(feature);
            if (value != null)
            {
                return value;
            }
        }


        return null;
    }
}

获取Feature值,核心步骤如下:

  • 在获取值时,先判定是否有该定义

  • 再循环从ValueProvider中获取到对应值

MethodInvocationFeatureCheckerService

除了直接使用IFeatureChecker去检查是否满足要求,还可以采用一些AOP方式拦截,方便重复性的检查代码。为作用于方法层面检查是否满足要求,ABP在FeatureChecker上还封装了一层针对于MethodInvocation的服务,目标明确。

204712457_c5b2903a-317f-4ecf-a508-2d625834d483 在其中首先判断方法上是否有相应的特性标识(DisableFeatureCheckAttribute或RequiresFeatureAttribute),如果没有则忽视检查过程,如果有,则再调用FeatureChecker获取到实际值,从而判断是否能够有权限进入方法中。

public class MethodInvocationFeatureCheckerService : IMethodInvocationFeatureCheckerService, ITransientDependency
{
    private readonly IFeatureChecker _featureChecker;


    public MethodInvocationFeatureCheckerService(
        IFeatureChecker featureChecker)
    {
        _featureChecker = featureChecker;
    }


    public async Task CheckAsync(MethodInvocationFeatureCheckerContext context)
    {
        // 方法上标注了DisableFeatureCheckAttribute则直接返回
        if (IsFeatureCheckDisabled(context))
        {
            return;
        }


        //循环检查RequiresFeatureAttribute中标注的Feature
        foreach (var requiresFeatureAttribute in GetRequiredFeatureAttributes(context.Method))
        {
            await _featureChecker.CheckEnabledAsync(requiresFeatureAttribute.RequiresAll, requiresFeatureAttribute.Features);
        }
    }


    protected virtual bool IsFeatureCheckDisabled(MethodInvocationFeatureCheckerContext context)
    {
        return context.Method
            .GetCustomAttributes(true)
            .OfType<DisableFeatureCheckAttribute>()
            .Any();
    }


    protected virtual IEnumerable<RequiresFeatureAttribute> GetRequiredFeatureAttributes(MethodInfo methodInfo)
    {
        var attributes = methodInfo
            .GetCustomAttributes(true)
            .OfType<RequiresFeatureAttribute>();


        if (methodInfo.IsPublic)
        {
            attributes = attributes
                .Union(
                    methodInfo.DeclaringType
                        .GetCustomAttributes(true)
                        .OfType<RequiresFeatureAttribute>()
                );
        }


        return attributes;
    }
}

而在AOP拦截方面,面对不同层级的不同方法上有两类方式。

  • 对于常规方法或类上,如AppSerivce等,采用FeatureInterceptor

  • 对于Controller/Method或者Page,采用Filter来处理。

204713825_a9378276-aec0-422e-90c5-2b665920019b

FeatureInterceptor

在方法上标记RequiresFeature特性,例如

public class ReportingAppService : ApplicationService, IReportingAppService
{
    [RequiresFeature("MyApp.PdfReporting")]
    public async Task<PdfReportResultDto> GetPdfReportAsync()
    {
        //TODO...
    }
}

但想要让特性生效,需要先通过ABP实现的拦截器进行注册拦截,再调用IMethodInvocationFeatureCheckerService来调用服务。

public class FeatureInterceptor : AbpInterceptor, ITransientDependency
{
    //...
    public override async Task InterceptAsync(IAbpMethodInvocation invocation)
    {
        //横切关注点特别判断,绕开Controller/Page中的Method
        if (AbpCrossCuttingConcerns.IsApplied(invocation.TargetObject, AbpCrossCuttingConcerns.FeatureChecking))
        {
            await invocation.ProceedAsync();
            return;
        }


        await CheckFeaturesAsync(invocation);
        await invocation.ProceedAsync();
    }


    protected virtual async Task CheckFeaturesAsync(IAbpMethodInvocation invocation)
    {
        using (var scope = _serviceScopeFactory.CreateScope())
        {
            //调用服务执行检查
            await scope.ServiceProvider.GetRequiredService<IMethodInvocationFeatureCheckerService>().CheckAsync(
                new MethodInvocationFeatureCheckerContext(
                    invocation.Method
                )
            );
        }
    }
}

如上存在一个判断逻辑,当判断存在了AbpCrossCuttingConcerns.FeatureChecking,则可认定是Controller/Method或者Page/Method,因此不再ABP的拦截器中处理,则直接进入方法中,避免在Filter中执行过一次检查,又进入到ABP拦截器中再进行一次检查。

if (AbpCrossCuttingConcerns.IsApplied(invocation.TargetObject, AbpCrossCuttingConcerns.FeatureChecking))
{
    await invocation.ProceedAsync();
    return;
}

AbpFeatureActionFilter

对于Controller/Method中,同样是借助AOP实现,只是不由ABP的拦截器管理,而是借助Asp.Net Core提供的Filter来拦截,再调用IMethodInvocationFeatureCheckerService服务。

结合FeatureInterceptor中的源码解释,在Filter中,在进入方法前,先给该方法标记了一个横切关注点AbpCrossCuttingConcerns.FeatureChecking,然后调用检查服务执行检查。

public class AbpFeatureActionFilter : IAsyncActionFilter, ITransientDependency
{
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        if (!context.ActionDescriptor.IsControllerAction())
        {
            await next();
            return;
        }


        var methodInfo = context.ActionDescriptor.GetMethodInfo();


        using (AbpCrossCuttingConcerns.Applying(context.Controller, AbpCrossCuttingConcerns.FeatureChecking))
        {
            var methodInvocationFeatureCheckerService = context.GetRequiredService<IMethodInvocationFeatureCheckerService>();
            await methodInvocationFeatureCheckerService.CheckAsync(new MethodInvocationFeatureCheckerContext(methodInfo));


            await next();
        }
    }
}

AbpFeaturePageFilter

与上相同,利用Filter,拦截后调用IMethodInvocationFeatureCheckerService服务。同样也是先在方法上设置一个横切关注点标识,再调用检查服务执行检查。

public class AbpFeaturePageFilter : IAsyncPageFilter, ITransientDependency
{
    public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
    {
        return Task.CompletedTask;
    }


    public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
    {
        if (context.HandlerMethod == null || !context.ActionDescriptor.IsPageAction())
        {
            await next();
            return;
        }


        var methodInfo = context.HandlerMethod.MethodInfo;


        using (AbpCrossCuttingConcerns.Applying(context.HandlerInstance, AbpCrossCuttingConcerns.FeatureChecking))
        {
            var methodInvocationFeatureCheckerService = context.GetRequiredService<IMethodInvocationFeatureCheckerService>();
            await methodInvocationFeatureCheckerService.CheckAsync(new MethodInvocationFeatureCheckerContext(methodInfo));


            await next();
        }
    }
}

类似FeatureChecking的横切关注点标识还有一些。

public static class AbpCrossCuttingConcerns
{
    public const string Auditing = "AbpAuditing";
    public const string UnitOfWork = "AbpUnitOfWork";
    public const string FeatureChecking = "AbpFeatureChecking";
    public const string GlobalFeatureChecking = "AbpGlobalFeatureChecking";
    
    //...
}

FeatureManagement

该模块为独立模块,管理Feature值,其内部实现了FeatureStore的整套逻辑。如无特殊需求,建议直接使用上该模块。

扩展

Feature部分的扩展点很多,甚至于可以完全绕开一些已有的功能设计,此处提及几种扩展。

  1. 可以扩展FeatureDefinitionContext,现有的功能定义存储在字典中,如果有特殊场景需要想换种实现方式,可以自定义一个FeatureDefinitionContext,但需要注意重写FeatureDefinitionManager中CreateFeatureGroupDefinitions方法(其内部直接实例化了FeatureDefinitionContext类)。

  2. 对于FeatureDefinitionManager可以按照需要扩展,可以不再依赖AbpFeatureOptions获取到所有的FeatureDefinitionProvider。

  3. 对于FeatureStore可以按照需要扩展,其中实现具体的取值逻辑,取值源头等,不限于关系型数据库表,可从Db,Redis,文件等多种方式,总归使用FeatureStore隔离了具体的取值逻辑。

  4. 对于FeatureChecker,可以完全改写检查逻辑。

  5. 可以参照RequiresFeature设计符合业务需要的特性,继承IMethodInvocationFeatureCheckerService实现自定义的方法检查逻辑。

  6. 还可自定义拦截器,移除默认的FeatureInterceptor,实现自定义的拦截器。

2024-07-05,望技术有成后能回来看见自己的脚步。