[Abp 源碼分析]5、系統設置

0.簡要介紹

Abp 自己有兩種設置,一種就是 上一篇文章 所介紹的模塊配置 Configuration,該配置主要用於一些複雜的數據類型設置,不單單是字符串,也有多是一些 C# 運行時的一些變量。另一種則是本篇文章所講的 Setting,Setting 主要用於配置一些簡單的參數,好比 SMTP 地址,數據庫鏈接字符串等一些基本的配置類型可使用 Setting 來進行處理。html

1.代碼分析

1.1 啓動流程

咱們先來看一下設置是怎樣被加入到 Abp 框架當中,而且是如何來使用它的。數據庫

在 Abp 框架內部開發人員能夠經過 ISettingsConfiguration 的 Providers 屬性來添加本身實現的 SettingProvider ,而 ISettingsConfiguration 的初始化是在上一篇文章所寫的 AbpBootstrapper.Initialize() 裏面進行初始化的。緩存

開發人員經過繼承 SettingProvider 來提供這些設置信息,而且在模塊的 PreInitialize() 方法當中經過 Configuration 來添加書寫好的配置提供者。服務器

在模塊進行初始化以後(也就是在 PostInitiailze() 方法內部),全部開發人員定義的 SettingProvider 經過 ISettingDefinitionManagerInitialize() 方法存儲到一個 Dictionary 裏面。app

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 來進行管理的。async

全部的設置項是經過 ServiceProvider 來提供的。ide

設置的持久化配置則是經過 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>();
        // 其餘代碼
    }
}

注入以後,那麼相應的模塊如何獲得已經注入的配置項呢?ui

咱們拿一個最直觀的例子來展現一下,這裏咱們來到 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.點此跳轉到總目錄

相關文章
相關標籤/搜索