[Abp 源码分析]五、系统设置
0.简要介绍
Abp 本身有两种设置,一种就是 所介绍的模块配置 Configuration,该配置主要用于一些复杂的数据类型设置,不仅仅是字符串,也有可能是一些 C# 运行时的一些变量。另外一种则是本篇文章所讲的 Setting,Setting 主要用于配置一些简单的参数,比如 SMTP 地址,数据库连接字符串等一些基本的配置类型可以使用 Setting 来进行处理。
1.代码分析
1.1 启动流程
我们先来看一下设置是怎样被加入到 Abp 框架当中,并且是如何来使用它的。
在 Abp 框架内部开发人员可以通过 ISettingsConfiguration
的 Providers 属性来添加自己实现的 SettingProvider
,而 ISettingsConfiguration
的初始化是在上一篇文章所写的 AbpBootstrapper.Initialize()
里面进行初始化的。
开发人员通过继承 SettingProvider
来提供这些设置信息,并且在模块的 PreInitialize()
方法当中通过 Configuration
来添加书写好的配置提供者。
在模块进行初始化之后(也就是在 PostInitiailze()
方法内部),所有开发人员定义的 SettingProvider
通过 ISettingDefinitionManager
的 Initialize()
方法存储到一个 Dictionary
里面。
public sealed class AbpKernelModule : AbpModule { // 其他代码 public override void PostInitialize() { // 其他代码 IocManager.Resolve<SettingDefinitionManager>().Initialize(); // 其他代码 } }
Initialize()
方法内部:
private readonly IDictionary<string, SettingDefinition> _settings; public void Initialize() { var context = new SettingDefinitionProviderContext(this); foreach (var providerType in _settingsConfiguration.Providers) { using (var provider = CreateProvider(providerType)) { foreach (var settings in provider.Object.GetSettingDefinitions(context)) { _settings[settings.Name] = settings; } } } }
对外则是通过 ISettingManager
来进行管理的。
所有的设置项是通过 ServiceProvider
来提供的。
设置的持久化配置则是通过 ISettingStore
来实现的,开发者可以通过替换 ISettingStore
的实现达到持久化到数据库或者是其他位置。
1.2 典型用法
1.2.1 设置提供者定义
internal class EmailSettingProvider : SettingProvider { public override IEnumerable<SettingDefinition> GetSettingDefinitions(SettingDefinitionProviderContext context) { return new[] { new SettingDefinition(EmailSettingNames.Smtp.Host, "127.0.0.1", L("SmtpHost"), scopes: SettingScopes.Application | SettingScopes.Tenant), new SettingDefinition(EmailSettingNames.Smtp.Port, "25", L("SmtpPort"), scopes: SettingScopes.Application | SettingScopes.Tenant), new SettingDefinition(EmailSettingNames.Smtp.UserName, "", L("Username"), scopes: SettingScopes.Application | SettingScopes.Tenant), new SettingDefinition(EmailSettingNames.Smtp.Password, "", L("Password"), scopes: SettingScopes.Application | SettingScopes.Tenant), new SettingDefinition(EmailSettingNames.Smtp.Domain, "", L("DomainName"), scopes: SettingScopes.Application | SettingScopes.Tenant), new SettingDefinition(EmailSettingNames.Smtp.EnableSsl, "false", L("UseSSL"), scopes: SettingScopes.Application | SettingScopes.Tenant), new SettingDefinition(EmailSettingNames.Smtp.UseDefaultCredentials, "true", L("UseDefaultCredentials"), scopes: SettingScopes.Application | SettingScopes.Tenant), new SettingDefinition(EmailSettingNames.DefaultFromAddress, "", L("DefaultFromSenderEmailAddress"), scopes: SettingScopes.Application | SettingScopes.Tenant), new SettingDefinition(EmailSettingNames.DefaultFromDisplayName, "", L("DefaultFromSenderDisplayName"), scopes: SettingScopes.Application | SettingScopes.Tenant) }; } private static LocalizableString L(string name) { return new LocalizableString(name, AbpConsts.LocalizationSourceName); } }
1.2.2 注入设置提供者
public sealed class AbpKernelModule : AbpModule { public override void PreInitialize() { // 其他代码 Configuration.Settings.Providers.Add<EmailSettingProvider>(); // 其他代码 } }
注入之后,那么相应的模块如何得到已经注入的配置项呢?
我们拿一个最直观的例子来展示一下,这里我们来到 Abp 项目的 Email 模块,来看看它是如何使用的。
public class DefaultMailKitSmtpBuilder : IMailKitSmtpBuilder, ITransientDependency { private readonly ISmtpEmailSenderConfiguration _smtpEmailSenderConfiguration; public DefaultMailKitSmtpBuilder(ISmtpEmailSenderConfiguration smtpEmailSenderConfiguration) { _smtpEmailSenderConfiguration = smtpEmailSenderConfiguration; } public virtual SmtpClient Build() { var client = new SmtpClient(); try { ConfigureClient(client); return client; } catch { client.Dispose(); throw; } } protected virtual void ConfigureClient(SmtpClient client) { client.Connect( _smtpEmailSenderConfiguration.Host, _smtpEmailSenderConfiguration.Port, _smtpEmailSenderConfiguration.EnableSsl ); if (_smtpEmailSenderConfiguration.UseDefaultCredentials) { return; } client.Authenticate( _smtpEmailSenderConfiguration.UserName, _smtpEmailSenderConfiguration.Password ); } }
可以看到以上代码通过 ISmtpEmailSenderConfiguration
来拿到 SMTP 对应的主机名与端口号,那这与我们的 ISettingManager
又有何关系呢?
其实我们转到 ISmtpEmailSenderConfiguration
的实现 SmtpEmailSenderConfiguration
就清楚了。
public class SmtpEmailSenderConfiguration : EmailSenderConfiguration, ISmtpEmailSenderConfiguration, ITransientDependency { /// <summary> /// SMTP Host name/IP. /// </summary> public virtual string Host { get { return GetNotEmptySettingValue(EmailSettingNames.Smtp.Host); } } /// <summary> /// SMTP Port. /// </summary> public virtual int Port { get { return SettingManager.GetSettingValue<int>(EmailSettingNames.Smtp.Port); } } /// <summary> /// User name to login to SMTP server. /// </summary> public virtual string UserName { get { return GetNotEmptySettingValue(EmailSettingNames.Smtp.UserName); } } /// <summary> /// Password to login to SMTP server. /// </summary> public virtual string Password { get { return GetNotEmptySettingValue(EmailSettingNames.Smtp.Password); } } /// <summary> /// Domain name to login to SMTP server. /// </summary> public virtual string Domain { get { return SettingManager.GetSettingValue(EmailSettingNames.Smtp.Domain); } } /// <summary> /// Is SSL enabled? /// </summary> public virtual bool EnableSsl { get { return SettingManager.GetSettingValue<bool>(EmailSettingNames.Smtp.EnableSsl); } } /// <summary> /// Use default credentials? /// </summary> public virtual bool UseDefaultCredentials { get { return SettingManager.GetSettingValue<bool>(EmailSettingNames.Smtp.UseDefaultCredentials); } } /// <summary> /// Creates a new <see cref="SmtpEmailSenderConfiguration"/>. /// </summary> /// <param name="settingManager">Setting manager</param> public SmtpEmailSenderConfiguration(ISettingManager settingManager) : base(settingManager) { } }
在这里我们可以看到这些配置项其实是通过一个名字叫做 GetNotEmptySettingValue()
的方法来得到的,该方法定义在 SmtpEmailSenderConfiguration
的基类 EmailSenderConfiguration
当中。
public abstract class EmailSenderConfiguration : IEmailSenderConfiguration { // 其他代码,已经省略 /// <summary> /// Creates a new <see cref="EmailSenderConfiguration"/>. /// </summary> protected EmailSenderConfiguration(ISettingManager settingManager) { SettingManager = settingManager; } /// <summary> /// Gets a setting value by checking. Throws <see cref="AbpException"/> if it's null or empty. /// </summary> /// <param name="name">Name of the setting</param> /// <returns>Value of the setting</returns> protected string GetNotEmptySettingValue(string name) { var value = SettingManager.GetSettingValue(name); if (value.IsNullOrEmpty()) { throw new AbpException($"Setting value for '{name}' is null or empty!"); } return value; } }
总而言之,如果你想要获取已经添加好的设置项,直接注入 ISettingManager
通过其 GetSettingValue()
就可以拿到这些设置项。
1.3 具体代码分析
Abp 系统设置相关的最核心的部分就是 ISettingManager
、ISettingDefinitionManager
、ISettingStore
,SettingProvider
、SettingDefinition
下面就这几个类进行一些细致的解析。
1.3.1 SettingDefinition
在 Abp 当中,一个设置项就是一个 SettingDefinition
,每个 SettingDefinition
的 Name 与 Value 是必填的,其中 Scopes 字段对应一个 SettingScopes
枚举,该属性用于确定这个设置项的使用应用范围。
public class SettingDefinition { /// <summary> /// Unique name of the setting. /// </summary> public string Name { get; private set; } /// <summary> /// Display name of the setting. /// This can be used to show setting to the user. /// </summary> public ILocalizableString DisplayName { get; set; } /// <summary> /// A brief description for this setting. /// </summary> public ILocalizableString Description { get; set; } /// <summary> /// Scopes of this setting. /// Default value: <see cref="SettingScopes.Application"/>. /// </summary> public SettingScopes Scopes { get; set; } /// <summary> /// Is this setting inherited from parent scopes. /// Default: True. /// </summary> public bool IsInherited { get; set; } /// <summary> /// Gets/sets group for this setting. /// </summary> public SettingDefinitionGroup Group { get; set; } /// <summary> /// Default value of the setting. /// </summary> public string DefaultValue { get; set; } /// <summary> /// Can clients see this setting and it's value. /// It maybe dangerous for some settings to be visible to clients (such as email server password). /// Default: false. /// </summary> [Obsolete("Use ClientVisibilityProvider instead.")] public bool IsVisibleToClients { get; set; } /// <summary> /// Client visibility definition for the setting. /// </summary> public ISettingClientVisibilityProvider ClientVisibilityProvider { get; set; } /// <summary> /// Can be used to store a custom object related to this setting. /// </summary> public object CustomData { get; set; } public SettingDefinition( string name, string defaultValue, ILocalizableString displayName = null, SettingDefinitionGroup group = null, ILocalizableString description = null, SettingScopes scopes = SettingScopes.Application, bool isVisibleToClients = false, bool isInherited = true, object customData = null, ISettingClientVisibilityProvider clientVisibilityProvider = null) { if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name)); } Name = name; DefaultValue = defaultValue; DisplayName = displayName; Group = @group; Description = description; Scopes = scopes; IsVisibleToClients = isVisibleToClients; IsInherited = isInherited; CustomData = customData; ClientVisibilityProvider = new HiddenSettingClientVisibilityProvider(); if (isVisibleToClients) { ClientVisibilityProvider = new VisibleSettingClientVisibilityProvider(); } else if (clientVisibilityProvider != null) { ClientVisibilityProvider = clientVisibilityProvider; } } }
1.3.2 ISettingManager
首先我们看一下 ISettingManager
的默认实现 SettingManager
。
public class SettingManager : ISettingManager, ISingletonDependency { public const string ApplicationSettingsCacheKey = "ApplicationSettings"; /// <summary> /// Reference to the current Session. /// </summary> public IAbpSession AbpSession { get; set; } /// <summary> /// Reference to the setting store. /// </summary> public ISettingStore SettingStore { get; set; } private readonly ISettingDefinitionManager _settingDefinitionManager; private readonly ITypedCache<string, Dictionary<string, SettingInfo>> _applicationSettingCache; private readonly ITypedCache<int, Dictionary<string, SettingInfo>> _tenantSettingCache; private readonly ITypedCache<string, Dictionary<string, SettingInfo>> _userSettingCache; /// <inheritdoc/> public SettingManager(ISettingDefinitionManager settingDefinitionManager, ICacheManager cacheManager) { _settingDefinitionManager = settingDefinitionManager; AbpSession = NullAbpSession.Instance; SettingStore = DefaultConfigSettingStore.Instance; _applicationSettingCache = cacheManager.GetApplicationSettingsCache(); _tenantSettingCache = cacheManager.GetTenantSettingsCache(); _userSettingCache = cacheManager.GetUserSettingsCache(); } }
可以看到在这里面,他注入了 ISetingStore
与 ISettingDefinitionManager
,并且使用了三个 ITypedCache
来为这些设置进行一个缓存。
下面这个 GetSettingValueAsync()
方法则是获取一个指定名称的设置值。
public Task<string> GetSettingValueAsync(string name) { return GetSettingValueInternalAsync(name, AbpSession.TenantId, AbpSession.UserId); } private async Task<string> GetSettingValueInternalAsync(string name, int? tenantId = null, long? userId = null, bool fallbackToDefault = true) { // 获取指定 Name 的 SettingDefine var settingDefinition = _settingDefinitionManager.GetSettingDefinition(name); // 判断该设置项的使用范围是否为 User if (settingDefinition.Scopes.HasFlag(SettingScopes.User) && userId.HasValue) { var settingValue = await GetSettingValueForUserOrNullAsync(new UserIdentifier(tenantId, userId.Value), name); if (settingValue != null) { return settingValue.Value; } if (!fallbackToDefault) { return null; } if (!settingDefinition.IsInherited) { return settingDefinition.DefaultValue; } } // 判断该设置项的使用范围是否为 Tenant if (settingDefinition.Scopes.HasFlag(SettingScopes.Tenant) && tenantId.HasValue) { var settingValue = await GetSettingValueForTenantOrNullAsync(tenantId.Value, name); if (settingValue != null) { return settingValue.Value; } if (!fallbackToDefault) { return null; } if (!settingDefinition.IsInherited) { return settingDefinition.DefaultValue; } } // 判断该设置项的使用范围是否为 Application if (settingDefinition.Scopes.HasFlag(SettingScopes.Application)) { var settingValue = await GetSettingValueForApplicationOrNullAsync(name); if (settingValue != null) { return settingValue.Value; } if (!fallbackToDefault) { return null; } } // 如果都没有定义,则返回默认的设置值 return settingDefinition.DefaultValue; }
这里又为每个判断内部封装了一个方法,这里以 GetSettingValueForApplicationOrNullAsync()
为例,转到其定义:
private async Task<SettingInfo> GetSettingValueForApplicationOrNullAsync(string name) { return (await GetApplicationSettingsAsync()).GetOrDefault(name); } private async Task<Dictionary<string, SettingInfo>> GetApplicationSettingsAsync() { // 从缓存当中获取设置信息,如果不存在,则执行其工厂方法 return await _applicationSettingCache.GetAsync(ApplicationSettingsCacheKey, async () => { var dictionary = new Dictionary<string, SettingInfo>(); // 从 ISettingStore 当中获取对应的 Value 值 var settingValues = await SettingStore.GetAllListAsync(null, null); foreach (var settingValue in settingValues) { dictionary[settingValue.Name] = settingValue; } return dictionary; }); }
1.3.3 ISettingDefinitionManager
这个管理器作用最开始已经说明了,就是单纯的获取到用户注册到 Providers 里面的 SettingDefinition
。
1.3.4 SettingProvider
SettingProvider 用于开发人员配置自己的配置项,所有的设置提供者只需要继承自本类,实现其 GetSettingDefinitions
方法即可。
1.3.5 ISettingStore
本类用于设置项值的存储,其本身并不做设置项的新增,仅仅是相同的名称的设置项,优先从 ISettingStore
当中进行获取,如果不存在的话,才会使用开发人员在 SettingProvider
定义的值。
Abp 项目默认的 DefaultConfigSettingStore
实现并不会进行任何实质性的操作,只有 Zero.Common 项目当中重新实现的 SettingStore
类才是针对这些设置的值进行了持久化操作。
2.扩展:Abp.MailKit 模块配置
如果要在 .NetCore 环境下面使用邮件发送的话,首先推荐的就是 MailKit 这个库,而 Abp 针对 MailKit 库封装了一个新的模块,叫做 Abp.MailKit ,只需要进行简单的设置就可以发送邮件了。
在需要使用的模块上面添加:
[DependsOn(typeof(AbpMailKitModule))] public class TestModule : AbpModule { // 其他代码 }
之后需要自己定义一个 SettingProvider
并且在里面做好 SMTP 发件服务器配置:
public class DevEmailSettings : SettingProvider { public override IEnumerable<SettingDefinition> GetSettingDefinitions(SettingDefinitionProviderContext context) { return new[] { // smtp 服务器地址 new SettingDefiniion(EmailSettingNames.Smtp.Host, "smtpserver"), // smtp 用户名称 new SettingDefinition(EmailSettingNames.Smtp.UserName, "yourusername"), // smtp 服务端口 new SettingDefinition(EmailSettingNames.Smtp.Port, "25"), // smtp 用户密码 new SettingDefinition(EmailSettingNames.Smtp.Password, "yourpassword"), // 发件人邮箱地址 new SettingDefinition(EmailSettingNames.DefaultFromAddress, "youremailaddress"), // 是否启用默认验证 new SettingDefinition(EmailSettingNames.Smtp.UseDefaultCredentials,"false") }; } }
然后在之前的模块预加载当中添加这个 Provider 到全局设置当中:
[DependsOn(typeof(AbpMailKitModule))] public class TestModule : AbpModule { public override void PreInitialize() { Configuration.Settings.Providers.Add<DevEmailSettings>(); } }
发送邮件十分简单,直接在需要使用的地方注入 IEmailSender
调用其 Send
或者 SendAsync
方法即可,下面是一个例子:
public class TestApplicationService : ApplicationService { private readonly IEmailSender _emailSender; public TestApplicationService(IEmailSender emailSender) { _emailSender = emailSender; } public Task TestMethod() { _emailSender.Send("xxxxxx@qq.com","无主题","测试正文",false); return Task.FromResult(0); } }
推荐阅读
-
SQL语句练习实例之五 WMS系统中的关于LIFO或FIFO的问题分析
-
[Abp vNext 源码分析] - 7. 权限与验证
-
[Abp 源码分析]十、异常处理
-
[Abp vNext 源码分析] - 5. DDD 的领域层支持(仓储、实体、值对象)
-
[Abp 源码分析]四、模块配置
-
[Abp vNext 源码分析] - 11. 用户的自定义参数与配置
-
abp(net core)+easyui+efcore实现仓储管理系统——ABP WebAPI与EasyUI结合增删改查之五(三十一)
-
abp(net core)+easyui+efcore实现仓储管理系统——EasyUI之货物管理五 (二十三)
-
[Abp vNext 源码分析] - 7. 权限与验证
-
并发编程(五)——AbstractQueuedSynchronizer 之 ReentrantLock源码分析