• [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 通过 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.点此跳转到总目录

  • 相关阅读:
    第19篇 2016年计划
    第18篇 我的中国梦
    Linux中文件实时同步
    Ansible Playbook
    Ansible简介及常用模块
    HTTP协议简单认识
    zabbix 分布式监控Proxy
    Zabbix中Agent自动注册
    Groovy基础语法
    Python文件操作
  • 原文地址:https://www.cnblogs.com/myzony/p/9253122.html
Copyright © 2020-2023  润新知