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>();
}
}
该部分类图简要如下
修改定义
当对于引用的模块内部已有的定义不太符合需要时,可以更改已有的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。
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;
}
}
在注册阶段,扫描到所有的DefinitionProvider注册到AbpSettingOptions.DefinitionProviders中。
在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,文件等各种源头。
在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中。
在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定义名,返回对应的一组值。
对于ISettingDefinitionManager在Setting定义一节中提及。
对于ISettingValueProviderManager在Setting值提供者一节中提及。
对于ISettingEncryptionService,此处简要说明,这是一个加密服务,负责对部分Setting定义中要求了要加密值的定义负责加解密处理。
如下简要解析源码,以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,望技术有成后能回来看见自己的脚步。