ABP开发框架前后端开发系列---(12)配置模块的管理

一般来说,一个系统或多或少都会涉及到一些系统参数或者用户信息的配置,而ABP框架也提供了一套配置信息的管理模块,ABP框架的配置信息,必须提前定义好配置的各项内容,然后才能在系统中初始化或者通过接口查询来使用,本篇随笔引入了另外一种配置信息的定义,实现更加简化的处理,本篇随笔着重介绍两者之间的差异和不同的地方。

1、ABP框架的配置管理

如下面是邮件配置信息,配置信息一般先继承自SettingProvider,初始化定义后,才能被系统所使用。

EmailSettingProvider:继承自SettingProvider, 将SMTP的各项设置封装成SettingDefinition,并以数组形式返回。

image

配置的管理类,主要通过接口ISettingManager来进行统一管理的,底层协同了SettingStore配置存储和SetttingDefinitionMananger的配置定义管理两个部分。

image

这种方式的配置信息,糅合了配置项的定义(强制性),以及多语言特性的处理,根据不同的语言返回不同的配置名称,同时也整合了缓存信息的处理,以减少系统的一些消耗。

不过从上面的图示我们也可以看到,整个配置模块由于引入这些内容,导致处理起来必须按部就班的创建配置管理类,定义配置信息,重新编译系统后,然后才能进行信息的调用,因此这些配置信息必须预定义。而且管理起来协同这些类的处理,也略显得有点复杂化。

在ABP核心模块的启动过程中,会预先初始化这些配置管理类,如下代码所示

image

然后在AddSettingProviders中加入预先定义好的配置类。

image

接着在完成初始化过程中,有配置定义类统一根据这些配置对象,进行定义的初始化,这样才能在系统中进行使用。

image

配置定义的管理类接口,可以用下面这个图示进行说明。

image

以上就是在ABP框架中,基于配置模块的管理过程。

一般情况下,如果我们需要在Web API端中对这些接口进行调用管理,如对用户或者系统Email配置信息的获取和修改,那么我们需要定义一个配置接口服务(默认下载的ABP框架中没有公布这个接口定义和实现)。

如下我们定义一个SettingsAppService和他的接口

image

然后我们可以实现它的获取信息和修改信息的接口,如下所示是对系统级别的邮件参数进行配置管理。

        /// <summary>
        /// 获取应用程序级别的邮件配置(系统邮件配置)
        /// </summary>
        /// <returns></returns>
        public async Task<EmailSettingsEditDto> GetEmailSettingsForApplication()
        {
            var smtpPassword = await SettingManager.GetSettingValueForApplicationAsync(EmailSettingNames.Smtp.Password);

            return new EmailSettingsEditDto
            {
                DefaultFromAddress = await SettingManager.GetSettingValueForApplicationAsync(EmailSettingNames.DefaultFromAddress),
                DefaultFromDisplayName = await SettingManager.GetSettingValueForApplicationAsync(EmailSettingNames.DefaultFromDisplayName),
                SmtpHost = await SettingManager.GetSettingValueForApplicationAsync(EmailSettingNames.Smtp.Host),
                SmtpPort = await SettingManager.GetSettingValueForApplicationAsync<int>(EmailSettingNames.Smtp.Port),
                SmtpUserName = await SettingManager.GetSettingValueForApplicationAsync(EmailSettingNames.Smtp.UserName),
                SmtpPassword = SimpleStringCipher.Instance.Decrypt(smtpPassword),
                SmtpDomain = await SettingManager.GetSettingValueForApplicationAsync(EmailSettingNames.Smtp.Domain),
                SmtpEnableSsl = await SettingManager.GetSettingValueForApplicationAsync<bool>(EmailSettingNames.Smtp.EnableSsl),
                SmtpUseDefaultCredentials = await SettingManager.GetSettingValueForApplicationAsync<bool>(EmailSettingNames.Smtp.UseDefaultCredentials)
            };
        }

        /// <summary>
        /// 更新应用程序级别的邮件配置(系统邮件配置)
        /// </summary>
        /// <returns></returns>
        public async Task UpdateEmailSettingsForApplication(EmailSettingsEditDto input)
        {
            await SettingManager.ChangeSettingForApplicationAsync(EmailSettingNames.DefaultFromAddress, input.DefaultFromAddress);
            await SettingManager.ChangeSettingForApplicationAsync(EmailSettingNames.DefaultFromDisplayName, input.DefaultFromDisplayName);
            await SettingManager.ChangeSettingForApplicationAsync(EmailSettingNames.Smtp.Host, input.SmtpHost);
            await SettingManager.ChangeSettingForApplicationAsync(EmailSettingNames.Smtp.Port, input.SmtpPort.ToString(CultureInfo.InvariantCulture));
            await SettingManager.ChangeSettingForApplicationAsync(EmailSettingNames.Smtp.UserName, input.SmtpUserName);
            await SettingManager.ChangeSettingForApplicationAsync(EmailSettingNames.Smtp.Password, SimpleStringCipher.Instance.Encrypt(input.SmtpPassword));
            await SettingManager.ChangeSettingForApplicationAsync(EmailSettingNames.Smtp.Domain, input.SmtpDomain);
            await SettingManager.ChangeSettingForApplicationAsync(EmailSettingNames.Smtp.EnableSsl, input.SmtpEnableSsl.ToString().ToLowerInvariant());
            await SettingManager.ChangeSettingForApplicationAsync(EmailSettingNames.Smtp.UseDefaultCredentials, input.SmtpUseDefaultCredentials.ToString().ToLowerInvariant());
        }

2、使用自定义的参数配置管理

我在较早的随笔《Winform开发框架之参数配置管理功能实现-基于SettingsProvider.net的构建》中介绍过对配置信息的管理实现,这种配置参数方式一直很好的应用在我的各个框架上,定义和使用都相对比较简单,能够满足绝大多数的应用场景,相对ABP框架的配置模块来说,简单易用。

首先我们定义一个用来存储通用配置信息的表,如下所示。

image

这个配置表的主要特点也是以键为操作对象,然后内容是JSON序列化后的内容,可以存储用户自定义的类的序列号字符串,这个是它的灵魂所在。和ABP框架仅仅存储简单类型的值有所不同。

和其他模块的定义一样,我们可以先根据常规表的方式,使用代码快速生成类的结构,如下所示。

    /// <summary>
    /// 用户参数配置,应用层服务接口实现
    /// </summary>
    [AbpAuthorize]
    public class UserParameterAppService : MyAsyncServiceBase<UserParameter, UserParameterDto, string, UserParameterPagedDto, CreateUserParameterDto, UserParameterDto>, IUserParameterAppService
    {
        private readonly IRepository<UserParameter, string> _repository;

        public UserParameterAppService(IRepository<UserParameter, string> repository) : base(repository)
        {
            _repository = repository;
        }

然后定义几个用于用户级别和系统程序级别的接口实现,如获取信息,修改信息等。

image

然后,在生成的Caller层类里面,增加以上的Web API接口调用的实现代码,如下所示

    /// <summary>
    /// 用户参数配置的Web API调用处理
    /// </summary>
    public class UserParameterApiCaller : AsyncCrudApiCaller<UserParameterDto, string, UserParameterPagedDto, CreateUserParameterDto, UserParameterDto>, IUserParameterAppService
    {
        /// <summary>
        /// 提供单件对象使用
        /// </summary>
        public static UserParameterApiCaller Instance
        {
            get
            {
                return Singleton<UserParameterApiCaller>.Instance;
            }
        }

        /// <summary>
        /// 默认构造函数
        /// </summary>
        public UserParameterApiCaller()
        {
            this.DomainName = "UserParameter";//指定域对象名称,用于组装接口地址
        }

        public async Task<UserParameterDto> GetSettingForUser(NameInputDto input)
        {
            return await DoActionAsync<UserParameterDto>(MethodBase.GetCurrentMethod(), input);
        }

        public async Task ChangeSettingForUser(NameValueDto input)
        {
            await DoActionAsync(MethodBase.GetCurrentMethod(), input);
        }

        public async Task<UserParameterDto> GetSettingForApplication(NameInputDto input)
        {
            return await DoActionAsync<UserParameterDto>(MethodBase.GetCurrentMethod(), input);
        }

        public async Task ChangeSettingForApplication(NameValueDto input)
        {
            await DoActionAsync(MethodBase.GetCurrentMethod(), input);
        }
    }

如果对于上面的DoActionAsyn的处理有疑问,可以参考之前随笔《ABP开发框架前后端开发系列---(10)Web API调用类的简化处理》进行了解。

我在之前介绍过的配置模块里面,结合过FireFoxDialog界面效果,实现较好的参数配置管理功能,如下界面所示。

image

我们本次使用这两个不同的配置模块,也希望使用这个来展现一下,以便更好的理解。

由于整合了SettingsProvider.net组件,我们只需要封装一下对数据库的存储获取方式就可以了。

    /// <summary>
    /// 数据库参数存储设置
    /// </summary>
    public class DatabaseStorage : JsonSettingsStoreBase
    {
        /// <summary>
        /// 配置级别
        /// </summary>
        public SettingScopes Scope { get; set; }

        /// <summary>
        /// 构造函数
        /// </summary>
        public DatabaseStorage()
        {
            this.Scope = SettingScopes.User;
        }

        /// <summary>
        /// 参数构造函数
        /// </summary>
        /// <param name="scope">配置级别</param>
        public DatabaseStorage(SettingScopes scope)
        {
            this.Scope = scope;
        }

        /// <summary>
        /// 保存到数据库
        /// </summary>
        /// <param name="filename">文件名称(类型名称)</param>
        /// <param name="fileContents">参数内容</param>
        protected override void WriteTextFile(string filename, string fileContents)
        {
            var info = new NameValueDto(filename, fileContents);
            if (this.Scope == SettingScopes.Application)
            {
                AsyncContext.Run(()=> UserParameterApiCaller.Instance.ChangeSettingForApplication(info));
            }
            else
            {
                AsyncContext.Run(() => UserParameterApiCaller.Instance.ChangeSettingForUser(info));
            }
        }

        /// <summary>
        /// 从数据库读取
        /// </summary>
        /// <param name="filename">文件名称(类型名称)</param>
        /// <returns></returns>
        protected override string ReadTextFile(string filename)
        {
            var info = new NameInputDto(filename);

            UserParameterDto result = null;
            if (this.Scope == SettingScopes.Application)
            {
                result = AsyncContext.Run(() => UserParameterApiCaller.Instance.GetSettingForApplication(info));
            }
            else
            {
                result = AsyncContext.Run(() => UserParameterApiCaller.Instance.GetSettingForUser(info));
            }

            return result != null ? result.Content : null;
        }
    }

有了这个实现,这样在操作上,就不用管理这些内容如何获取和更新了,和之前的使用配置管理方式一致了。可以处理各种不同的配置对象信息。

先来看看默认ABP的配置处理方式,管理界面如下所示。

image

这里的配置存储咋ABP的AbpSettings表里面,如下所示,每项内容是以字符串方式独立存储的。

image

它的调用主要就是SettingsApiCaller的内容了,注意这个邮件配置,必须在EmailSettingProvider中提前定义好对象的信息。

        private EmailSettingsEditDto GetParameter()
        {
            EmailSettingsEditDto param =  AsyncContext.Run(() => SettingsApiCaller.Instance.GetEmailSettingsForApplication());
            if(param == null)
            {
                param = new EmailSettingsEditDto();
            }
            return param;
        }

        public override void OnInit()
        {
            var parameter = GetParameter();
            if (parameter != null)
            {
                this.txtEmail.Text = parameter.DefaultFromAddress;
                this.txtLoginId.Text = parameter.SmtpUserName;
                this.txtPassword.Text = parameter.SmtpPassword;
                this.txtPassword.Tag = parameter.SmtpPassword;
                this.txtSmtpPort.Value = parameter.SmtpPort;
                this.txtSmtpServer.Text = parameter.SmtpHost;
                this.txtUseSSL.Checked = parameter.SmtpEnableSsl;
            }
        }

下面我们再来看看自定义的配置管理方式。如下是自定义配置模块获取显示的内容。

image

这个配置是系统级别的,它的获取方式如下所示。

    public partial class PageEmailApplication : PropertyPage
    {
        private SettingsProvider settings;
        private ISettingsStorage store;

        public PageEmailApplication()
        {
            InitializeComponent();

            if (!this.DesignMode)
            {
                store = new DatabaseStorage(SettingScopes.Application);
                settings = new SettingsProvider(store);
            }
        }

        public override void OnInit()
        {
            EmailParameter parameter = settings.GetSettings<EmailParameter>();
            if (parameter != null)
            {
                this.txtEmail.Text = parameter.Email;
                this.txtLoginId.Text = parameter.LoginId;
                this.txtPassword.Text = parameter.Password;
                this.txtPassword.Tag = parameter.Password;
                this.txtPop3Port.Value = parameter.Pop3Port;
                this.txtPop3Server.Text = parameter.Pop3Server;
                this.txtSmtpPort.Value = parameter.SmtpPort;
                this.txtSmtpServer.Text = parameter.SmtpServer;
                this.txtUseSSL.Checked = parameter.UseSSL;
            }
        }

以上是标准的SettingsProvider.net的组件调用方式,我们不用知道具体的数据存储,只需要把内容直接GetSetting方式获取出来即可。

而保存内容,直接通过使用SaveSettings保存即可。

    EmailParameter parameter = settings.GetSettings<EmailParameter>();
    if (parameter != null)
    {                    
        parameter.Email = this.txtEmail.Text;
        parameter.LoginId = this.txtLoginId.Text;
        parameter.Password = this.txtPassword.Text;
        parameter.Pop3Port = Convert.ToInt32(this.txtPop3Port.Value);
        parameter.Pop3Server = this.txtPop3Server.Text;
        parameter.SmtpPort = Convert.ToInt32(this.txtSmtpPort.Value);
        parameter.SmtpServer = this.txtSmtpServer.Text;
        parameter.UseSSL = this.txtUseSSL.Checked;

        settings.SaveSettings<EmailParameter>(parameter);
    }

其中 EmailParameter 类是我们定义的一个类,用来承载相关的配置信息,如下所示。它支持默认值,加密处理等设置。

    /// <summary>
    /// 邮箱设置
    /// </summary>
    public class EmailParameter
    {
        /// <summary>
        /// 邮件账号
        /// </summary>
        //[DefaultValue("wuhuacong@163.com")]
        public string Email { get; set; }

        /// <summary>
        /// POP3服务器
        /// </summary>
        [DefaultValue("pop.163.com")]
        public string Pop3Server { get; set; }

        /// <summary>
        /// POP3端口
        /// </summary>
        [DefaultValue(110)]
        public int Pop3Port { get; set; }

        /// <summary>
        /// SMTP服务器
        /// </summary>
        [DefaultValue("smtp.163.com")]
        public string SmtpServer { get; set; }

        /// <summary>
        /// SMTP端口
        /// </summary>
        [DefaultValue(25)]
        public int SmtpPort { get; set; }

        /// <summary>
        /// 登陆账号
        /// </summary>
        public string LoginId { get; set; }

        /// <summary>
        /// 登陆密码
        /// </summary>
        [ProtectedString]
        public string Password { get; set; }

        /// <summary>
        /// 使用SSL加密
        /// </summary>
        [DefaultValue(false)]
        public bool UseSSL { get; set; }
    }

由于SettingsProvider.net组件的支持,我们还可以把配置信息当成本地文件存储起来,对于一些需要存为文件的方式的配置,非常不错。

    public partial class PageReport : PropertyPage
    {
        private SettingsProvider settings;
        private ISettingsStorage store;

        public PageReport()
        {
            InitializeComponent();

            if (!this.DesignMode)
            {
                // PortableStorage: 在运行程序目录创建一个setting的文件记录参数数据
                store = new PortableStorage();
                settings = new SettingsProvider(store);
            }
        }

以上就是介绍了ABP配置管理模块的实现原理和客户端的调用,以及使用自定义配置管理模块的方式进行处理更加动态化或者灵活一点的配置信息,使用自定义配置信息管理服务,整合了SettingProvider.net的支持,可以实现更好的参数配置管理体验。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容