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

3.点此跳转到总目录
[Abp 源码分析]五、系统设置的更多相关文章
- ABP源码分析五:ABP初始化全过程
ABP在初始化阶段做了哪些操作,前面的四篇文章大致描述了一下. 为个更清楚的描述其脉络,做了张流程图以辅助说明.其中每一步都涉及很多细节,难以在一张图中全部表现出来.每一步的细节(会涉及到较多接口,类 ...
- [Abp 源码分析]十二、多租户体系与权限验证
0.简介 承接上篇文章我们会在这篇文章详细解说一下 Abp 是如何结合 IPermissionChecker 与 IFeatureChecker 来实现一个完整的多租户系统的权限校验的. 1.多租户的 ...
- ABP源码分析一:整体项目结构及目录
ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...
- C# DateTime的11种构造函数 [Abp 源码分析]十五、自动审计记录 .Net 登陆的时候添加验证码 使用Topshelf开发Windows服务、记录日志 日常杂记——C#验证码 c#_生成图片式验证码 C# 利用SharpZipLib生成压缩包 Sql2012如何将远程服务器数据库及表、表结构、表数据导入本地数据库
C# DateTime的11种构造函数 别的也不多说没直接贴代码 using System; using System.Collections.Generic; using System.Glob ...
- ABP源码分析十五:ABP中的实用扩展方法
类名 扩展的类型 方法名 参数 作用 XmlNodeExtensions XmlNode GetAttributeValueOrNull attributeName Gets an attribu ...
- ABP源码分析二十五:EventBus
IEventData/EventData: 封装了EventData信息,触发event的源对象和时间 IEventBus/EventBus: 定义和实现了了一系列注册,注销和触发事件处理函数的方法. ...
- ABP源码分析三十五:ABP中动态WebAPI原理解析
动态WebAPI应该算是ABP中最Magic的功能之一了吧.开发人员无须定义继承自ApiController的类,只须重用Application Service中的类就可以对外提供WebAPI的功能, ...
- ABP源码分析四十五:ABP ZERO中的EntityFramework模块
AbpZeroDbContext:配置ABP.Zero中定义的entity的Dbset EntityFrameworkModelBuilderExtensions:给PrimitiveProperty ...
- [Abp 源码分析]零、文章目录
0.系列文章目录 一.Abp 框架启动流程分析 二.模块系统 三.依赖注入 四.模块配置 五.系统设置 六.工作单元的实现 七.仓储与 Entity Framework Core 八.缓存管理 九.事 ...
随机推荐
- 检查linux版本命令
lsb_release -a cat /etc/issue cat /proc/version uname -a cat /etc/redhat-release
- python连接mysql数据库读取数据
#-*- coding:utf-8 -*- #Author:'Lmc' #DATE: 2019/4/28/0028 上午 11:22:47 #FileName:test.PY import pymys ...
- SpringBoot与日志框架2(日志内斗)
一.SpringBoot如何引入slf4j+logback框架的呢? 在POM文件中 <dependency> <groupId>org.springframework.boo ...
- PBRT笔记(13)——光线传播1:表面反射
采样反射函数 BxDF::Sample_f()方法根据与相应的散射函数相似的分布来选择方向.在8.2节中,该方法用于寻找来自完美镜面的反射和透射光线;在这里讲介绍实现其他类型的采样技术. BxDF:: ...
- (lua) 基于cocos 的插入光效
具体的表现是:移动滚动容器到头部, 新增icon淡入并掉入格子,显示格子中的图标,icon放大并淡出 function UISkyResource:playActivedEffect(id) then ...
- SharePoint2016: 使用powerShell启用project web app
1. 创建pwa承载的webApplication 在SharePoint2016管理中心>应用程序管理>管理web应用程序,新建web应用程序>sharepoint-1001, ...
- vue-cli+mock.js+axios模拟前后台数据交互
最近工作不是很忙,自己做了一个vue的移动端的小项目,涉及到后台数据的时候,网上查阅了一些资料,因为是自己写的项目没有后台只能自己模拟数据,刚开始就自己写了一些静态数据后来觉得尽量模拟真实的比较好些, ...
- 我和python的初相识
认识Python是大二的选修 单纯只是想赚学分而已 后来觉得越来越有趣. 一.python简介 简单来说Python 是一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言.Python 的设 ...
- 【原创】XAF 常见错误以及对应解决方法
1.Appearance Criteria设置错误 Exception occurs while assigning the 'DetailView, ID:xxx_DetailView' view ...
- SQL语句基本
基础 创建数据库 CREATE DATABASE database-name 1 删除数据库 drop database dbname 1 备份sql server 创建 备份数据的 device U ...