通用后台管理系统必备功能模块包含日志管理,权限管理,数据字典,参数配置等功能。参数设置主要用于设置系统运行所需的一些基础性配置项,比如redis缓存,mq消息队列,系统版本等信息。好的参数设置需要达到以下几点1.使用简单 2.功能强大,方便拓展 3.界面美观。本篇将带你实现通用参数设置,在阅读之前你需要了解的知识,ASP.NET MVC,Entity Framework,MEF。在线预览地址:http://config.myscloud.cn
阅读目录
- 添加配置项及使用
- 实现思路
- 关键代码解析
- 总结
回到顶部
添加配置项及使用
为了验证系统实现了这几个目标1.使用简单 2.功能强大,方便拓展 3.界面美观,这里先通过实例来演示如何添加配置项以及怎么使用该配置项。
1.添加配置项组
只需添加一个继承于ConfigOption抽象类的类,这里我们称继承于ConfigOption的类为配置项组。
/// <summary> /// 测试配置信息 /// </summary> [Export(typeof(ConfigOption))] [ConfigType(Group = "TestConfig", GroupCn = "测试配置项", ImmediateUpdate = true)] public class TestConfig : ConfigOption { /// <summary> /// 是否记录执行SQL /// </summary> [Config(Name = "记录执行SQL", DefaultValue = false)] public static bool IfLogSQL { get; set; } }
注意点:继承ConfigOption抽象类,添加ConfigTypeAttribute属性描述(Group:分组 GroupCn:分组名称,用于显示在界面)
2.添加配置项
配置项组里面的每个静态属性称为配置项
/// <summary> /// 是否记录执行SQL /// </summary> [Config(Name = "记录执行SQL", DefaultValue = false)] public static bool IfLogSQL { get; set; }
注意点:ConfigAttribute属性描述中 Name:对应配置项中文说明 DefaultValue:默认值 ConfigValueType:bool类型会显示成单选radio,更多设置可参考ConfigAttribute类
3.可选步骤 实现个性化业务
/// <summary> /// 保存前校验 /// </summary> /// <param name="value"></param> /// <returns>bool</returns> public override bool BeforeSave(OptionViewModel value) { foreach(var item in value.ListOptions) { switch (item.Key) { case "IfLogSQL": if (string.IsNullOrEmpty(item.Value)) { return false; } break; default: break; } } return base.BeforeSave(value); } public override void AfterSave(List<Options> ListOptions) { foreach (Options item in ListOptions) { switch (item.Key) { case "IfLogSQL": //开启或者关闭EF日志记录 bool curValue = Convert.ToBoolean(item.Value); if (curValue != TestConfig.IfLogSQL) { //EfLogConfig.ManagerEFLog(curValue); } break; default: break; } } }
使用BeforeSave和AfterSave方法可以实现个性化业务
4.参数使用
public ActionResult Index() { ViewBag.Title = SystemConfig.SystemTitle; return View(); }
由于定义的是静态属性,所以可以直接使用
小结:只需通过添加配置项类,无需修改其它东西,所需的保存逻辑和界面都已经完成。这里留个疑问,我是如何知道前台界面渲染的时候该用radio,text,password中哪种控件的呢?
回到顶部
实现思路
通用配置管理达到以下目标
1.使用简单
通过添加配置项类,无需额外操作即可完成工作
2.功能强大,方便拓展
界面等其它工作都已经由框架完成,对于个性化的配置比如需要实现校验,或者额外工作,可以通过beforesave,aftersave进行拓展
3.界面美观
基于bootstrap实现,整体效果还是挺不错的
系统的类图
系统主流程
回到顶部
关键代码解析
1.初始化(Global.asax.cs)
//1.MEF初始化 MefConfig.Init(); //2.EF初始化 EFInitializer.UnSafeInit(); //3.初始化系统参数配置 ConfigManager configManager =MefConfig.TryResolve<ConfigManager>(); configManager.Init();
MefConfig.Init() 方法初始化组合容器
EFInitializer.UnSafeInit() Entity Framework数据库连接和类型初始化
configManager.Init() 读取所有配置项 从数据库读取所有配置项值进行赋值
2.关键类ConfigManager
/// <summary> /// 系统所有配置信息 /// </summary> [ImportMany(typeof(ConfigOption))] public IEnumerable<ConfigOption> AllConfig { get { return _allConfig; } set { if (_allConfig == null) { _allConfig = value; } } }
通过MEF导入器读取所有配置项组,存储在静态变量 _allConfig 中
/// <summary> /// 初始化系统参数配置信息 /// </summary> public void Init() { //所有选项值 List<Options> listOption = ConfigService.GetAllOptions(); ConfigDescription desc = null; //代码现有配置项 foreach (ConfigOption item in AllConfig) { //反射读取配置项ConfigTypeAttribute ConfigAttribute 信息 desc = ConfigDescriptionCache.GetTypeDiscription(item.GetType()); //设置当前配置项的GroupType desc.GroupTypePropertyInfo.SetValue(item, Convert.ChangeType(desc.Group, desc.GroupTypePropertyInfo.PropertyType), null); //每项值信息 List<Options> itemOptions = listOption.Where(e => e.OptionType.Equals(desc.Group, StringComparison.OrdinalIgnoreCase)).ToList(); Options op = null; ConfigAttribute ca = null; foreach (PropertyInfo prop in desc.StaticPropertyInfo) { op = itemOptions.FirstOrDefault(e1 => e1.Key.Equals(prop.Name, StringComparison.OrdinalIgnoreCase)); ca = desc.MemberDict[prop.Name]; if (op == null) { //设置默认值 prop.SetValue(null, Convert.ChangeType(ca.DefaultValue, prop.PropertyType), null); } else { prop.SetValue(null, Convert.ChangeType(op.Value, prop.PropertyType), null); } } } }
ConfigService.GetAllOptions()从数据库中读取所有选项值,通过ConfigDescriptionCache.GetTypeDiscription(item.GetType())反射读取所有静态属性的相关值
属性类型和前台控件映射关系
/// <summary> /// 设置默认项数值类型-根据属性类型进行转换 /// </summary> /// <param name="configAttr"></param> /// <param name="propertyType">属性类型</param> private static void SetConfigValueType(ConfigAttribute configAttr,Type propertyType) { switch (propertyType.ToString()) { case "System.String": configAttr.ValueType = ConfigValueType.String; //文本框 break; case "System.Boolean": configAttr.ValueType = ConfigValueType.Bool; //对应前台 radio break; case "System.Int32": case "System.Double": configAttr.ValueType = ConfigValueType.Number; //对应数值输入框 break; default: configAttr.ValueType = ConfigValueType.String; break; } }
密码类型的可以自行进行指定
/// <summary> /// MQ连接密码 /// </summary> [Config(Name = "密码", ValueType = ConfigValueType.Password)] public static string Password { get; set; }
提供的获取配置项的接口
/// <summary> /// 获取所有配置信息 /// </summary> /// <returns>所有配置信息</returns> public List<OptionViewModel> GetAllOption(string GroupType = "")
/// <summary> /// 获取指定项配置信息 /// </summary> /// <param name="GroupType">分组项</param> /// <returns>所有配置信息</returns> public OptionViewModel GetOptionByGroup(string GroupType)
/// <summary> /// 获取指定项配置信息 /// </summary> /// <param name="GroupType">分组项</param> /// <returns>所有配置信息</returns> public Options GetOptionByGroupAndKey(string GroupType, string key){}
保存方法实现
/// <summary> /// 保存配置信息 /// </summary> /// <param name="value">配置信息</param> public ApiResult<string> Save(OptionViewModel value) { ApiResult<string> result = new ApiResult<string>(); result.HasError = true; string GroupType = value.Group.GroupType; if (value.Group == null || string.IsNullOrEmpty(GroupType) || value.ListOptions == null) { result.Message = "保存参数配置时发生参数空异常"; return result; } //调用保存前处理事件 ConfigOption curConfigOption = AllConfig.FirstOrDefault(e => e.GroupType.Equals(GroupType, StringComparison.OrdinalIgnoreCase)); if (curConfigOption == null) { //如果没有找到匹配项 result.Message = string.Format("当前保存配置信息{0}不对应后台的任务配置类", GroupType); return result; } //调用配置项的保存前校验事件 if (!curConfigOption.BeforeSave(value)) { result.Message = "当前配置项不允许保存"; return result; } //保存数据 try { using (CommonDbContext cdb = new CommonDbContext()) { var dbSet = cdb.Set<Options>(); var delObjs=dbSet.Where(e => e.OptionType == GroupType).ToList(); //删除原有数据 foreach (var item in delObjs) { cdb.Entry(item).State = EntityState.Deleted; } //保存数据 foreach (var item in value.ListOptions) { item.OptionId = Guid.NewGuid().ToString("N"); } dbSet.AddRange(value.ListOptions); cdb.SaveChanges(); } } catch (Exception ex) { result.Message = ex.Message; return result; } //对当前配置项进行赋值 SetValue(curConfigOption, value.ListOptions); result.HasError = false; return result; } /// <summary> /// 保存时 对当前配置项进行赋值 /// </summary> /// <param name="item">当前配置项</param> /// <param name="ListOptions">配置项值</param> public void SetValue(ConfigOption item, List<Options> ListOptions) { //调用保存后处理事件 item.AfterSave(ListOptions); var desc = ConfigDescriptionCache.GetTypeDiscription(item.GetType()); Options option = null; foreach (PropertyInfo prop in desc.StaticPropertyInfo) { option = ListOptions.First(e => e.Key.Equals(prop.Name, StringComparison.OrdinalIgnoreCase)); if (option == null) { //不存在该配置项,则清空当前值 prop.SetValue(null, Convert.ChangeType(null, prop.PropertyType), null); } else { prop.SetValue(null, Convert.ChangeType(option.Value, prop.PropertyType), null); } } }
前台渲染逻辑(config.js)
//初始化数据 initData = function () { $.ajax({ type: "get", url: "/Config/GetAllOption", //调用后台获取所有配置项接口 dataType: "json", beforeSend: function () { //加载等待层 index = layer.load(0); }, complete: function () { layer.close(index); }, success: function (data) { BaseData = data; drawConfig(BaseData); //绘制界面 } }); }
回到顶部
总结
该参数配置可以很简单的移植到自己系统里面,在TaskManagerV2.0这边博客中使用的参数配置功能就是直接移植的该系统的代码。另外使用的时候记得修改Web.config中的数据库连接字符串,本篇写到这里就完结了,在介绍一下与文章无关的内容。我在博客上添加的打赏功能,分别位于公告和文章结尾处,欢迎资助我持续写作!下篇将结合参数配置介绍消息队列RabbitMQ的使用,敬请期待!
源代码下载地址:http://files.cnblogs.com/files/yanweidie/CommonParamConfig.rar,svn源码地址http://code.taobao.org/svn/commonparamconfig。
如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】按钮。
如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的【关注我】。
如果,想给予我更多的鼓励,求打
因为,我的写作热情也离不开您的肯定支持。
感谢您的阅读,如果您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是焰尾迭 。