公司是做CS产品的, 最近分配给我一个活, 要求:
1. 公司程序启动时, 检测是否有配置文件, 没有的话则按默认值创建一个
2. 配置文件要加密, 不能让客户随便看到里面的参数
3. 配置文件要有配套的GUI配置工具, 因为现场实施人员嫌XML配置麻烦
如果只有一个产品需要这个功能, 把每个配置项的读写功能硬编码写到工具里就完事了, 但公司有好几个产品都需要这个, 不得不写一个通用的工具类
这个工作还解决了两个问题:
a. 以前设置项都配置在 app.config 里, 每次升级都会覆盖原来的设置, 所以现场人员都必须先将 app.config复制出来.
b. app.config 里新增了配置项, 现场实施人员必须仔细对比, 将新增项人工放入原来的app.config
现在的做法是, 配置文件ConfigSetting.xml并不在安装包中, 所以卸载升级都不会影响它; 程序第一次启动时, 会按默认值生成一个ConfigSetting.xml; 以后程序启动的时候, 假如有新增的配置项, 则将其加入ConfigSetting.xml
我把涉及的两个类都放在了一个文件, 这样引入一个文件即可
using System; using System.Collections.Generic; using System.Xml.Linq; using System.Security.Cryptography; using System.IO; /// <summary> /// 设置项的帮助类 /// </summary> public class ConfigSettingTool { /// <summary> /// 保存读取时, 是否加密解密; 设为false, 可以方便调试 /// 其实也只能防小白, 随便反编译一下就啥都漏出来了 /// </summary> private static bool isEncrypt = false; /// <summary> /// 默认的配置文件名 /// </summary> public static readonly string DefaultXmlFileName = "ConfigSetting.xml"; /// <summary> /// 获取XDocument, 解密失败、XML结构不合理, 都会根据模板重新生成一个 /// </summary> /// <param name="xmlFileName"></param> /// <param name="msg"></param> /// <returns>确保返回如下格式 /// <?xml version="1.0" encoding="utf-8" standalone="yes"?> /// <Setting> /// <SingleSetting></SingleSetting> /// </Setting> /// </returns> public static XDocument GetXDocument(string xmlFileName, out string msg) { msg = null; if (!System.IO.File.Exists(xmlFileName)) { msg = "配置文件不存在, 创建默认配置文件"; return new XDocument(new XDeclaration("1.0", "utf-8", "yes"), new XElement("Setting", new XElement("SingleSetting"))); } try { var textContent = System.IO.File.ReadAllText(xmlFileName); textContent = isEncrypt ? Decrypt(textContent) : textContent; var xdoc = XDocument.Parse(textContent); if (xdoc.Root.Name != "Setting") { throw new Exception("根节点不是 Setting"); } if (xdoc.Root.Element("SingleSetting") == null) { throw new Exception("没有 SingleSetting 节点"); } return xdoc; } catch { msg = "配置文件不是标准格式, 删除后, 创建默认配置文件"; return new XDocument(new XDeclaration("1.0", "utf-8", "yes"), new XElement("Setting", new XElement("SingleSetting"))); } } /// <summary> /// 将xml信息读出到settingArray, 如果缺少某项设定则增加到xdoc /// </summary> /// <param name="xdoc"></param> /// <param name="settingList">通常就是GlobalSetting.DefaultGlobalSettingArray</param> public static void ReadValueToSettingArray(XDocument xdoc, List<ConfigureItemModel> settingArray) { var singleSettingElement = xdoc.Root.Element("SingleSetting"); foreach (var configureItem in settingArray) { configureItem.ErrorMsg = null; var element = singleSettingElement.Element(configureItem.Name); if (element == null) { element = new XElement(configureItem.Name, configureItem.DefaultValue, new XAttribute("Caption", configureItem.Caption), new XAttribute("Description", configureItem.Description), new XAttribute("DefaultValue", configureItem.DefaultValue), new XAttribute("CanBeEmpty", configureItem.CanBeEmpty)); singleSettingElement.Add(element); } configureItem.Value = string.IsNullOrWhiteSpace(element.Value) ? "" : element.Value.Trim(); } } /// <summary> /// 将xml信息读出到settingArray /// </summary> /// <param name="xdoc"></param> /// <param name="settingList">通常就是GlobalSetting.DefaultGlobalSettingArray</param> public static void ReadConfig(XDocument xdoc, out List<ConfigureItemModel> settingList) { settingList = new List<ConfigureItemModel>(); var singleSettingElement = xdoc.Root.Element("SingleSetting"); foreach (var element in singleSettingElement.Elements()) { var captionAttribute = element.Attribute("Caption"); var caption = captionAttribute != null ? captionAttribute.Value : ""; var name = element.Name.ToString(); var value = element.Value.ToString(); var descriptionAttribute = element.Attribute("Description"); var description = descriptionAttribute != null ? descriptionAttribute.Value : ""; var defaultValueAttribute = element.Attribute("DefaultValue"); var defaultValue = defaultValueAttribute != null ? defaultValueAttribute.Value : ""; var canBeEmpty = false; try { canBeEmpty = bool.Parse(element.Attribute("CanBeEmpty").Value); } catch { } var errorMsgAttribute = element.Attribute("ErrorMsg"); var errorMsg = errorMsgAttribute != null ? errorMsgAttribute.Value : ""; var configureItem = new ConfigureItemModel(caption, name, defaultValue, description, canBeEmpty) { Value = value, ErrorMsg = errorMsg }; settingList.Add(configureItem); } } /// <summary> /// 尝试解析设置内容 到 目标class /// </summary> /// <param name="settingList">通常就是GlobalSetting.DefaultGlobalSettingArray, 配置项设置不合理时, 会将错误信息保存到ErrorMsg</param> /// <param name="targetSettingClass">通常就是GlobalSetting</param> /// <returns>成功, true; 失败: false</returns> public static bool TryParseConfig(List<ConfigureItemModel> settingArray, Type targetSettingClass) { bool isAllSuccess = true; foreach (var configureItem in settingArray) { configureItem.ErrorMsg = null; configureItem.Value = string.IsNullOrWhiteSpace(configureItem.Value) ? "" : configureItem.Value.Trim(); if (configureItem.Value == "" && configureItem.CanBeEmpty == false) { configureItem.ErrorMsg += "该项值不能为空, 请手动填写该值;"; isAllSuccess = false; continue; } var property = targetSettingClass.GetProperty(configureItem.Name); //如果 targetSettingClass 没有对应的静态属性, 则跳过 if (property == null) { continue; } object value = null; try { value = Convert.ChangeType(configureItem.Value, property.PropertyType); property.SetValue(null, value, null); } catch { configureItem.ErrorMsg += configureItem.Value + "不能转换为" + property.PropertyType.Name + ", 请重新填写该值;"; isAllSuccess = false; continue; } } return isAllSuccess; } /// <summary> /// 写入 /// </summary> /// <param name="xmlFileName"></param> /// <param name="settingList">通常就是GlobalSetting.DefaultGlobalSettingArray</param> /// <returns>成功, null</returns> public static bool TrySaveToXML(string xmlFileName, List<ConfigureItemModel> settingArray, out string msg) { msg = null; var xdoc = GetXDocument(xmlFileName, out msg);//原文件读出错误, 忽略即可, 因为settingArray会自动填充 var singleSettingElement = xdoc.Root.Element("SingleSetting"); foreach (var configureItem in settingArray) { var element = singleSettingElement.Element(configureItem.Name); if (element == null) { element = new XElement(configureItem.Name, configureItem.Value); singleSettingElement.Add(element); } else { element.Value = configureItem.Value ?? ""; } element.RemoveAttributes(); element.Add(new XAttribute("Caption", configureItem.Caption)); element.Add(new XAttribute("Description", configureItem.Description)); element.Add(new XAttribute("DefaultValue", configureItem.DefaultValue)); element.Add(new XAttribute("CanBeEmpty", configureItem.CanBeEmpty)); if (!string.IsNullOrWhiteSpace(configureItem.ErrorMsg)) { element.Add(new XAttribute("ErrorMsg", configureItem.ErrorMsg)); } } var textContent = xdoc.ToString(); textContent = isEncrypt ? Encrypt(textContent) : textContent; try { System.IO.File.WriteAllText(xmlFileName, textContent); return true; } catch (Exception ex) { msg= "保存失败:" + ex.Message; return false; } } #region 加密解密部分 private static byte[] DESKey = new byte[] { 11, 69, 93, 102, 172, 41, 18, 12 }; private static byte[] DESIV = new byte[] { 75, 77, 46, 197, 78, 157, 23, 36 }; /// <summary> /// 加密 /// </summary> private static string Encrypt(string source) { string reValue = ""; DESCryptoServiceProvider objDes = new DESCryptoServiceProvider(); MemoryStream objMemoryStream = new MemoryStream(); CryptoStream objCrytoStream = new CryptoStream(objMemoryStream, objDes.CreateEncryptor(DESKey, DESIV), CryptoStreamMode.Write); StreamWriter objStreamWriter = new StreamWriter(objCrytoStream); objStreamWriter.Write(source); objStreamWriter.Flush(); objCrytoStream.FlushFinalBlock(); objMemoryStream.Flush(); reValue = Convert.ToBase64String(objMemoryStream.GetBuffer(), 0, (int)objMemoryStream.Length); return reValue; } /// <summary> /// 解密 /// </summary> private static string Decrypt(string source) { string reValue = ""; DESCryptoServiceProvider objDES = new DESCryptoServiceProvider(); byte[] Input = Convert.FromBase64String(source); MemoryStream objMemoryStream = new MemoryStream(Input); CryptoStream objCryptoStream = new CryptoStream(objMemoryStream, objDES.CreateDecryptor(DESKey, DESIV), CryptoStreamMode.Read); StreamReader objStreamReader = new StreamReader(objCryptoStream); reValue = objStreamReader.ReadToEnd(); return reValue; } #endregion } /// <summary> /// 单个设置项 /// </summary> /// <remarks>由于XML中不能保存null, 所以所有属性都不会被设置为null</remarks> public class ConfigureItemModel { /// <summary> /// 单个设置项 /// </summary> /// <param name="captionParam">显示名称</param> /// <param name="nameParam">参数名称</param> /// <param name="defaultValueParam">默认值</param> /// <param name="descriptionParam">描述, 该项不设定时候, 显示默认值</param> /// <param name="canBeEmptyParam">能否为空字符串</param> public ConfigureItemModel(string captionParam, string nameParam, string defaultValueParam, string descriptionParam = "", bool canBeEmptyParam = false) { Caption = captionParam; Name = nameParam; Description = descriptionParam; DefaultValue = defaultValueParam; CanBeEmpty = canBeEmptyParam; } private string caption = ""; /// <summary> /// 显示名称 /// </summary> public string Caption { get { return caption; } set { caption = string.IsNullOrWhiteSpace(value) ? "" : value; } } private string name = ""; /// <summary> /// 参数名称 /// </summary> public string Name { get { return name; } set { name = string.IsNullOrWhiteSpace(value) ? "" : value; ; } } private string description = ""; /// <summary> /// 说明, 如果该值没有赋值, 则显示DefaultValue /// </summary> public string Description { get { return string.IsNullOrWhiteSpace(description) ? defaultValue : description; } set { description = string.IsNullOrWhiteSpace(value) ? "" : value; } } private string defaultValue = ""; /// <summary> /// 默认值 /// </summary> public string DefaultValue { get { return defaultValue; } set { defaultValue = string.IsNullOrWhiteSpace(value) ? "" : value; } } /// <summary> /// 能否为空字符串 /// </summary> public bool CanBeEmpty { get; set; } /// <summary> /// 能否为空字符串 的字符串形式 /// </summary> public string CanBeEmptyString { get { return CanBeEmpty ? "是" : "否"; } } private string innerValue = ""; /// <summary> /// 值 /// </summary> public string Value { get { return innerValue; } set { innerValue = string.IsNullOrWhiteSpace(value) ? "" : value; ; } } private string errorMsg = ""; /// <summary> /// 错误信息 /// </summary> public string ErrorMsg { get { return errorMsg; } set { errorMsg = string.IsNullOrWhiteSpace(value) ? "" : value; ; } } }
产品里建一个 GlobalSetting 类, 里面的配置项都必须是 static 属性, 然后加入 public static List<ConfigureItemModel> DefaultGlobalSettingArray 保存默认设置
/// <summary> /// 全局设定 /// </summary> /// <remarks>所有属性都必须是 static , 即 类属性</remarks> public class GlobalSetting { public static List<ConfigureItemModel> DefaultGlobalSettingArray = new List<ConfigureItemModel>() { new ConfigureItemModel("数据库的主机地址","ConnectionStringHost", "127.0.0.1"), }; /// <summary> /// 数据库的主机地址 /// </summary> public static string ConnectionStringHost { get; set; } }
具体的示例请参考 ConfigSettingToolTest, 第一次运行时会报错: 升级地址 不能为空, 使用 配置文件编辑工具2.exe 为其赋值后, 就可以正常启动了