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