ABP Framework-Setting源码解析

目录

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

Version

6.0.3

Package

Volo.Abp.Settings


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

SettingDefinitionProvider

内容定义

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

public class YourSettingDefinitionProvider : SettingDefinitionProvider
{
    public override void Define(ISettingDefinitionContext context)
    {
        context.Add(
            new SettingDefinition("Smtp.Host", "127.0.0.1"),
            new SettingDefinition("Smtp.Port", "25"),
            new SettingDefinition("Smtp.UserName"),
            new SettingDefinition("Smtp.Password", isEncrypted: true),
            new SettingDefinition("Smtp.EnableSsl", "false")
        );
    }
}

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

[DependsOn(
    typeof(AbpLocalizationAbstractionsModule),
    typeof(AbpSecurityModule),
    typeof(AbpMultiTenancyModule)
    )]
public class AbpSettingsModule : AbpModule
{
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        AutoAddDefinitionProviders(context.Services);
    }


    //...


    private static void AutoAddDefinitionProviders(IServiceCollection services)
    {
        var definitionProviders = new List<Type>();


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


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

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

public class AbpSettingOptions
{
    public ITypeList<ISettingDefinitionProvider> DefinitionProviders { get; }


    public ITypeList<ISettingValueProvider> ValueProviders { get; }


    public AbpSettingOptions()
    {
        DefinitionProviders = new TypeList<ISettingDefinitionProvider>();
        ValueProviders = new TypeList<ISettingValueProvider>();
    }
}

该部分类图简要如下 204617502_e9405f5e-a9ce-4452-9289-38b76caa10dd

修改定义

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

public class MySettingDefinitionProvider : SettingDefinitionProvider
{
    public override void Define(ISettingDefinitionContext context)
    {
        var smtpPassword = context.GetOrNull("Abp.Mailing.Smtp.Password");
        if (smtpPassword != null)
        {
            smtpPassword.IsEncrypted = "mail.mydomain.com";
        }
    }
}

SettingDefinitionContext

封装了Setting定义内容,衔接着SettingDefinitionManager和SettingDefinitionProvider。内部主要是在字典上做增删查操作。

public class SettingDefinitionContext : ISettingDefinitionContext
{
    protected Dictionary<string, SettingDefinition> Settings { get; }
    //...
    public virtual SettingDefinition GetOrNull(string name)
    {
        return Settings.GetOrDefault(name);
    }


    public virtual IReadOnlyList<SettingDefinition> GetAll()
    {
        return Settings.Values.ToImmutableList();
    }


    public virtual void Add(params SettingDefinition[] definitions)
    {
        //...
        foreach (var definition in definitions)
        {
            Settings[definition.Name] = definition;
        }
    }
}

SettingDefinitionManager

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

204619741_be1a551e-0d57-497b-8265-f0340172dffb ISettingDefinitionManager该部分源码简要如下,主要关注从DefinitionProviders转移定义到自身属性中。

public class SettingDefinitionManager : ISettingDefinitionManager, ISingletonDependency
{
    protected Lazy<IDictionary<string, SettingDefinition>> SettingDefinitions { get; }


    protected AbpSettingOptions Options { get; }


    protected IServiceProvider ServiceProvider { get; }


    public SettingDefinitionManager(
        IOptions<AbpSettingOptions> options,
        IServiceProvider serviceProvider)
    {
        ServiceProvider = serviceProvider;
        Options = options.Value;
        SettingDefinitions = new Lazy<IDictionary<string, SettingDefinition>>(CreateSettingDefinitions, true);
    }


    protected virtual IDictionary<string, SettingDefinition> CreateSettingDefinitions()
    {
        var settings = new Dictionary<string, SettingDefinition>();


        using (var scope = ServiceProvider.CreateScope())
        {
            // 找到所有的DefinitionProviders
            var providers = Options
                .DefinitionProviders
                .Select(p => scope.ServiceProvider.GetRequiredService(p) as ISettingDefinitionProvider)
                .ToList();


            // 转移Definition存储到SettingDefinitions 
            foreach (var provider in providers)
            {
                provider.Define(new SettingDefinitionContext(settings));
            }
        }


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

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

provider.Define(new SettingDefinitionContext(settings));

Dictionary<string, SettingDefinition> settings,保存到SettingDefinitionContext中,各个DefinitionProvider则需要接收一个SettingDefinitionContext,因此从SettingDefinitionManager到SettingDefinitonProvider需要借助一个媒介SettingDefinitionContext。

public class MySettingDefinitionProvider : SettingDefinitionProvider
{
    public override void Define(ISettingDefinitionContext context)
    {
        var smtpPassword = context.GetOrNull("Abp.Mailing.Smtp.Password");
    }
}

SettingDefinitionManager->SettingDefinitionContext->SettingDefinitonProvider 3. 后续获取Setting定义则直接从自身属性SettingDefinitions中获取。

简要概括该部分步骤为:

  • 各模块中完成SettingDefinitionProvider定义。

  • 扫描所有SettingDefinitionProvider存储到AbpSettingOptions中。

  • 初始化SettingDefinitionManager时,从AbpSettingOption中读取定义经过SettingDefinitionContext转移到SettingDefinitions字典属性中。

  • 读取Setting,直接从SettingDefinitions字典属性中读取。

SettingValueProvider

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

204621122_fa31a040-8121-4205-9c11-59545e2cd2dd 在AbpSettingModule中已提前注册好了几个默认的ValueProvider,对于自定义的ValueProvider也需要在开发的module中采用相同方式手动注册到ValueProviders集合中。

[DependsOn(
    typeof(AbpLocalizationAbstractionsModule),
    typeof(AbpSecurityModule),
    typeof(AbpMultiTenancyModule)
    )]
public class AbpSettingsModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure<AbpSettingOptions>(options =>
        {
            options.ValueProviders.Add<DefaultValueSettingValueProvider>();
            options.ValueProviders.Add<ConfigurationSettingValueProvider>();
            options.ValueProviders.Add<GlobalSettingValueProvider>();
            options.ValueProviders.Add<TenantSettingValueProvider>();
            options.ValueProviders.Add<UserSettingValueProvider>();
        });
    }
}

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

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

  • ConfigurationSettingValueProvider,从appsettings.json中获取对应定义名字值,其配置值需要在Settings节点下,例如Mail相关的配置

{
    "Settings": {
        "Abp.Mailing.Smtp.Port": "465",
        "SettingDefinitionName": "SettingValue"
    }
}

该ValueProvider类源码中,限定了Settings节点,如果想要更换节点,可参照该类,实现自定义的ValueProvider

public class ConfigurationSettingValueProvider : ISettingValueProvider, ITransientDependency
{
    public const string ConfigurationNamePrefix = "Settings:";


    public const string ProviderName = "C";


    public string Name => ProviderName;


    protected IConfiguration Configuration { get; }


    public ConfigurationSettingValueProvider(IConfiguration configuration)
    {
        Configuration = configuration;
    }


    public virtual Task<string> GetOrNullAsync(SettingDefinition setting)
    {
        return Task.FromResult(Configuration[ConfigurationNamePrefix + setting.Name]);
    }


    public Task<List<SettingValue>> GetAllAsync(SettingDefinition[] settings)
    {
        return Task.FromResult(settings.Select(x => new SettingValue(x.Name, Configuration[ConfigurationNamePrefix + x.Name])).ToList());
    }
}
  • Global/Tenant/UserSettingValueProvider,这三个分别从全局配置角度/租户配置角度/用户配置角度取配置,最终取的逻辑又移交给ISettingStore,有别于前两个ValueProvider。以租户为例,取得当前租户Id,依赖SettingStore获取到配置来源。
public class TenantSettingValueProvider : SettingValueProvider
{
    public const string ProviderName = "T";


    public override string Name => ProviderName;


    protected ICurrentTenant CurrentTenant { get; }


    public TenantSettingValueProvider(ISettingStore settingStore, ICurrentTenant currentTenant)
        : base(settingStore)
    {
        CurrentTenant = currentTenant;
    }


    public override async Task<string> GetOrNullAsync(SettingDefinition setting)
    {
        return await SettingStore.GetOrNullAsync(setting.Name, Name, CurrentTenant.Id?.ToString());
    }


    public override async Task<List<SettingValue>> GetAllAsync(SettingDefinition[] settings)
    {
        return await SettingStore.GetAllAsync(settings.Select(x => x.Name).ToArray(), Name, CurrentTenant.Id?.ToString());
    }
}

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

public class SettingProvider : ISettingProvider, ITransientDependency
{
    //...
    protected virtual async Task<string> GetOrNullValueFromProvidersAsync(
        IEnumerable<ISettingValueProvider> providers,
        SettingDefinition setting)
    {
        foreach (var provider in providers)
        {
            //取到有效值后直接返回
            var value = await provider.GetOrNullAsync(setting);
            if (value != null)
            {
                return value;
            }
        }


        return null;
    }
}

SettingValueProviderManager

所有ValueProvider注册到AbpSettingOptions中,在后续小节中获取Setting值时,有SettingProvider,不会直接和每一个SettingValueProvider关联,而是借助于SettingValueProviderManager来管理。

SettingProvider->SettingValueProviderManager->Multi SettingValueProvider

其内部,从注册源AbpSettingOptions中获取ValueProviders,后续的Providers的提供则由SettingValueProviderManager提供,不再从AbpSettingOptions获取,此处也可以参照扩展,能够实现自身业务需要的ValueProvider注册方式。

public class SettingValueProviderManager : ISettingValueProviderManager, ISingletonDependency
{
    public List<ISettingValueProvider> Providers => _lazyProviders.Value;
    protected AbpSettingOptions Options { get; }
    private readonly Lazy<List<ISettingValueProvider>> _lazyProviders;


    public SettingValueProviderManager(
        IServiceProvider serviceProvider,
        IOptions<AbpSettingOptions> options)
    {


        Options = options.Value;


        _lazyProviders = new Lazy<List<ISettingValueProvider>>(
            () => Options
                .ValueProviders
                .Select(type => serviceProvider.GetRequiredService(type) as ISettingValueProvider)
                .ToList(),
            true
        );
    }
}

SettingStore

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

204622304_c2b68444-ac3d-4695-8d33-0a7c780c1381 在SettingManagement模块中实现了SettingStore,也没有实现具体的取值逻辑,而是依赖SettingManagementStore。

public class SettingStore : ISettingStore, ITransientDependency
{
    protected ISettingManagementStore ManagementStore { get; }


    public SettingStore(ISettingManagementStore managementStore)
    {
        ManagementStore = managementStore;
    }


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


    public virtual Task<List<SettingValue>> GetAllAsync(string[] names, string providerName, string providerKey)
    {
        return ManagementStore.GetListAsync(names, providerName, providerKey);
    }
}

此处SettingStore算是一个Wrapper,SettingManagementStore承载了对配置的管理职责,而SettingStore只承担读取配置职责,从设计上,分开成两个,服务两套系统确实合理。 SettingStore->SettingManagementStore->SettingRepository->SettingTable

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

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


    public virtual async Task<List<SettingValue>> GetListAsync(string providerName, string providerKey)
    {
        var settings = await SettingRepository.GetListAsync(providerName, providerKey);
        return settings.Select(s => new SettingValue(s.Name, s.Value)).ToList();
    }
}

此处可以参照SettingStore,扩展自身的功能,比如自定义Setting表,而不用依赖Abp生成的Setting表。

SettingProvider

Setting定义好了,其值配置好了,剩下的则是在代码中通过定义,取到值。在业务代码中,直接使用ISettingProvider来获取配置值。

public class MyService
{
    private readonly ISettingProvider _settingProvider;


    public MyService(ISettingProvider settingProvider)
    {
        _settingProvider = settingProvider;
    }


    public async Task FooAsync()
    {
        //Get a value as string.
        var userName = await _settingProvider.GetOrNullAsync("Smtp.UserName");
    }
}

Abp定义了ISettingProvider,传入一个或多个Setting定义名,返回对应的一组值。 204623476_7a1ca5ac-078f-4711-a14a-2ea56747fe7e

  • 对于ISettingDefinitionManager在Setting定义一节中提及。

  • 对于ISettingValueProviderManager在Setting值提供者一节中提及。

  • 对于ISettingEncryptionService,此处简要说明,这是一个加密服务,负责对部分Setting定义中要求了要加密值的定义负责加解密处理。

204624747_cccf6e60-e81d-4c87-af24-d25cad80b943 如下简要解析源码,以GetOrNull方法为例。

public class SettingProvider : ISettingProvider, ITransientDependency
{
    protected ISettingDefinitionManager SettingDefinitionManager { get; }
    protected ISettingEncryptionService SettingEncryptionService { get; }
    protected ISettingValueProviderManager SettingValueProviderManager { get; }


    public SettingProvider(
        ISettingDefinitionManager settingDefinitionManager,
        ISettingEncryptionService settingEncryptionService,
        ISettingValueProviderManager settingValueProviderManager)
    {
        SettingDefinitionManager = settingDefinitionManager;
        SettingEncryptionService = settingEncryptionService;
        SettingValueProviderManager = settingValueProviderManager;
    }


    public virtual async Task<string> GetOrNullAsync(string name)
    {
        // 按照name获取SettingDefinition
        var setting = SettingDefinitionManager.Get(name);


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


        // 获取到具体值
        var value = await GetOrNullValueFromProvidersAsync(providers, setting);
        if (value != null && setting.IsEncrypted)
        {
            // 如果SettingDefinition有加密要求,则解密值获取真实值
            value = SettingEncryptionService.Decrypt(setting, value);
        }


        return value;
    }


    protected virtual async Task<string> GetOrNullValueFromProvidersAsync(
        IEnumerable<ISettingValueProvider> providers,
        SettingDefinition setting)
    {
        // 循环调用ValueProvider
        foreach (var provider in providers)
        {
            var value = await provider.GetOrNullAsync(setting);
            if (value != null)
            {
                return value;
            }
        }


        return null;
    }
}

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

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

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

  • 如果获取的配置有加密要求,则再调用加解密服务解密从而得到真实值。

加解密服务

此处额外扩展下,StringEncryptionService中,默认从如下配置中取到加密key。

{
    "StringEncryption": {
        "DefaultPassPhrase": "eTKGkJNvNrxVeyYr"
    }
}

该部分代码简要提及一下,加解密方法从AbpStringEncryptionOptions中获取key。

public class StringEncryptionService : IStringEncryptionService, ITransientDependency
{
    protected AbpStringEncryptionOptions Options { get; }


    public StringEncryptionService(IOptions<AbpStringEncryptionOptions> options)
    {
        Options = options.Value;
    }


    public virtual string Encrypt(string plainText, string passPhrase = null, byte[] salt = null)
    {
        if (passPhrase == null)
        {
            passPhrase = Options.DefaultPassPhrase;
        }
        if (salt == null)
        {
            salt = Options.DefaultSalt;
        }
    }


    public virtual string Decrypt(string cipherText, string passPhrase = null, byte[] salt = null)
    {
        if (passPhrase == null)
        {
            passPhrase = Options.DefaultPassPhrase;
        }
        if (salt == null)
        {
            salt = Options.DefaultSalt;
        }
    }
}

在AbpSecurity模块中完成AbpStringEncryptionOptions配置注册。

public class AbpSecurityModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        var configuration = context.Services.GetConfiguration();
        context.Services.Configure<AbpStringEncryptionOptions>(options =>
        {
            var keySize = configuration["StringEncryption:KeySize"];
            if (!keySize.IsNullOrWhiteSpace())
            {
                if (int.TryParse(keySize, out var intValue))
                {
                    options.Keysize = intValue;
                }
            }


            var defaultPassPhrase = configuration["StringEncryption:DefaultPassPhrase"];
            if (!defaultPassPhrase.IsNullOrWhiteSpace())
            {
                options.DefaultPassPhrase = defaultPassPhrase;
            }


            var initVectorBytes = configuration["StringEncryption:InitVectorBytes"];
            if (!initVectorBytes.IsNullOrWhiteSpace())
            {
                options.InitVectorBytes = Encoding.ASCII.GetBytes(initVectorBytes); ;
            }


            var defaultSalt = configuration["StringEncryption:DefaultSalt"];
            if (!defaultSalt.IsNullOrWhiteSpace())
            {
                options.DefaultSalt = Encoding.ASCII.GetBytes(defaultSalt); ;
            }
        });
    }
}

因此,如果Setting定义需要加解密服务,则应设置好加解密key。

SettingManagement

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

扩展

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

  • 对于SettingDefinitionManager可以按照需要扩展,可以不再依赖AbpSettingOptions获取到所有的SettingDefinitionProvider。

  • 对于SettingValueProvider可以按照需要扩展,比如如果配置源在Azure,则可以实现对应的ValueProvider,或者在Redis,则实现对应Redis的ValueProvider。

  • 对于SettingValueProviderManager可以按照需要扩展,直接改变ValueProvider的提供源头,甚至于不依赖AbpSettingOptions。

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

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