[Abp 源码分析]五、系统设置
2018-07-02 11:45
253 查看
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); } }
3.点此跳转到总目录
相关文章推荐
- [Abp 源码分析]二、模块系统
- android M Settings(系统设置)源码分析 设置的初始化过程
- Android Settings(系统设置)源码分析(一)
- Android系统设置源码分析(ROM)
- Android Settings(系统设置)源码分析(一)
- Linux内核源码分析--文件系统(八、Block_dev.c)
- CM系统应用源码分析与rom定制
- poll系统调用源码简要分析
- 【裸机开发笔记】6410的系统时钟设置(下)---几个常用函数的C源码。
- ABP源码分析四十二:ZERO的身份认证
- Android 5.0内核和源代码学习(2)——源码下载和系统启动过程分析
- ABP源码分析四十三:ZERO的本地化
- android系统源码分析--Activity Launcher组件启动过程
- Android 的系统属性(SystemProperties)设置分析
- Android Camera 系统架构源码分析(4)---->Camera的数据来源及Camera的管理
- Android View系统源码分析(八)—— View.onFocusedChanged()
- Java分布式跟踪系统Zipkin(二):Brave源码分析-Tracer和Span
- 转【Windows源码分析】(一)初始化内核与执行体子系统
- Linux proc 的文件系统的源码分析
- Android系统属性(SystemProperties)设置分析