0.简要介绍

Abp 本身有两种设置,一种就是 上一篇文章 所介绍的模块配置 Configuration,该配置主要用于一些复杂的数据类型设置,不仅仅是字符串,也有可能是一些 C# 运行时的一些变量。另外一种则是本篇文章所讲的 Setting,Setting 主要用于配置一些简单的参数,比如 SMTP 地址,数据库连接字符串等一些基本的配置类型可以使用 Setting 来进行处理。

1.代码分析

1.1 启动流程

我们先来看一下设置是怎样被加入到 Abp 框架当中,并且是如何来使用它的。

在 Abp 框架内部开发人员可以通过 ISettingsConfiguration 的 Providers 属性来添加自己实现的 SettingProvider ,而 ISettingsConfiguration 的初始化是在上一篇文章所写的 AbpBootstrapper.Initialize() 里面进行初始化的。

开发人员通过继承 SettingProvider 来提供这些设置信息,并且在模块的 PreInitialize() 方法当中通过 Configuration 来添加书写好的配置提供者。

在模块进行初始化之后(也就是在 PostInitiailze() 方法内部),所有开发人员定义的 SettingProvider 通过 ISettingDefinitionManagerInitialize() 方法存储到一个 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 系统设置相关的最核心的部分就是 ISettingManagerISettingDefinitionManagerISettingStoreSettingProviderSettingDefinition 下面就这几个类进行一些细致的解析。

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

可以看到在这里面,他注入了 ISetingStoreISettingDefinitionManager ,并且使用了三个 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 源码分析]五、系统设置的更多相关文章

  1. ABP源码分析五:ABP初始化全过程

    ABP在初始化阶段做了哪些操作,前面的四篇文章大致描述了一下. 为个更清楚的描述其脉络,做了张流程图以辅助说明.其中每一步都涉及很多细节,难以在一张图中全部表现出来.每一步的细节(会涉及到较多接口,类 ...

  2. [Abp 源码分析]十二、多租户体系与权限验证

    0.简介 承接上篇文章我们会在这篇文章详细解说一下 Abp 是如何结合 IPermissionChecker 与 IFeatureChecker 来实现一个完整的多租户系统的权限校验的. 1.多租户的 ...

  3. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  4. C# DateTime的11种构造函数 [Abp 源码分析]十五、自动审计记录 .Net 登陆的时候添加验证码 使用Topshelf开发Windows服务、记录日志 日常杂记——C#验证码 c#_生成图片式验证码 C# 利用SharpZipLib生成压缩包 Sql2012如何将远程服务器数据库及表、表结构、表数据导入本地数据库

    C# DateTime的11种构造函数   别的也不多说没直接贴代码 using System; using System.Collections.Generic; using System.Glob ...

  5. ABP源码分析十五:ABP中的实用扩展方法

    类名 扩展的类型 方法名 参数 作用 XmlNodeExtensions XmlNode GetAttributeValueOrNull attributeName Gets an   attribu ...

  6. ABP源码分析二十五:EventBus

    IEventData/EventData: 封装了EventData信息,触发event的源对象和时间 IEventBus/EventBus: 定义和实现了了一系列注册,注销和触发事件处理函数的方法. ...

  7. ABP源码分析三十五:ABP中动态WebAPI原理解析

    动态WebAPI应该算是ABP中最Magic的功能之一了吧.开发人员无须定义继承自ApiController的类,只须重用Application Service中的类就可以对外提供WebAPI的功能, ...

  8. ABP源码分析四十五:ABP ZERO中的EntityFramework模块

    AbpZeroDbContext:配置ABP.Zero中定义的entity的Dbset EntityFrameworkModelBuilderExtensions:给PrimitiveProperty ...

  9. [Abp 源码分析]零、文章目录

    0.系列文章目录 一.Abp 框架启动流程分析 二.模块系统 三.依赖注入 四.模块配置 五.系统设置 六.工作单元的实现 七.仓储与 Entity Framework Core 八.缓存管理 九.事 ...

随机推荐

  1. nginx学习.第一部分

    1.nginx的版本发布历史 2015年支持thread pool提供stream四层反向代理支持reuseport特性,支持http v2协议.完全可以替代LVS 2016年支持动态模块 2.ngi ...

  2. gulp 粗粗学习 记录下

    看视频学习 粗粗记录下 以便以后学习 1.初记录 gulp.task //定义一个任务 gulp.src //锁定到做task任务的文件路径 gulp.dest //锁定到任务做完后文件去向的路径 g ...

  3. VS2013+ffmpeg开发环境搭建

    VS2013+ffmpeg开发环境搭建 转 https://blog.csdn.net/u014253332/article/details/86657868 一.准备ffmpeg相对应开发dll.i ...

  4. json劫持payload

    <html> <head>jsonp hijacking</head> <body> <script> function jj(json){ ...

  5. codeforces1152 div2

    比赛的链接 C gcd(a+k, b+k) == gcd(a+k, b-a) #include <bits/stdc++.h> using namespace std; const int ...

  6. Git 如何解决部署远程仓库出现 fatal: refusing to merge unrelated histories 问题

    想把本地仓库的文件搬到Github,先将远程仓库和本地仓库关联起来: 先到Github上复制远程仓库的SSH地址: 运行 git remote add origin 你复制的地址 理论上—如果在创建 ...

  7. org.apache.http.client.ClientProtocolException: URI does not specify a valid host name

    问题截图: 原因:http:// 少了两个//

  8. Java的四种引用——强引用、软引用、弱引用、虚引用

    目录 强引用 软引用 弱引用 虚引用 强引用 拥有强引用的对象永远不会被GC,可以根据引用的get方法获取到被引用对象 软引用 在内存充足的额时候,拥有软引用的对象不会被GC:即将内存溢出的时候,会对 ...

  9. mysql数据库 ,java 代码巧妙结合提升系统性能。

       查询频繁的表t_yh_transport_task 保证数据量最少,增加查询效率, 常用于查询的字段增加索引, 每日定时移动数据 <!-- 医院系统预约任务历史删除定时器 --> & ...

  10. #工具 Intellij IDEA中自定义的Maven Archetype管理

    背景,手贱在输入自定义的 archetype时后面多输入了一个空格 解决:自定义的Archetype 会保存在Windows下面的文件中 C:\Users\<user>\.IntelliJ ...