公司是做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; ////// 设置项的帮助类 /// public class ConfigSettingTool { ////// 保存读取时, 是否加密解密; 设为false, 可以方便调试 /// 其实也只能防小白, 随便反编译一下就啥都漏出来了 /// private static bool isEncrypt = false; ////// 默认的配置文件名 /// public static readonly string DefaultXmlFileName = "ConfigSetting.xml"; ////// 获取XDocument, 解密失败、XML结构不合理, 都会根据模板重新生成一个 /// /// /// ///确保返回如下格式 /// /// 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"))); } } ////// ////// /// 将xml信息读出到settingArray, 如果缺少某项设定则增加到xdoc /// /// /// 通常就是GlobalSetting.DefaultGlobalSettingArray public static void ReadValueToSettingArray(XDocument xdoc, ListsettingArray) { 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(); } } /// /// 将xml信息读出到settingArray /// /// /// 通常就是GlobalSetting.DefaultGlobalSettingArray public static void ReadConfig(XDocument xdoc, out ListsettingList) { settingList = new List (); 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); } } /// /// 尝试解析设置内容 到 目标class /// /// 通常就是GlobalSetting.DefaultGlobalSettingArray, 配置项设置不合理时, 会将错误信息保存到ErrorMsg /// 通常就是GlobalSetting ///成功, true; 失败: false public static bool TryParseConfig(ListsettingArray, 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; } /// /// 写入 /// /// /// 通常就是GlobalSetting.DefaultGlobalSettingArray ///成功, null public static bool TrySaveToXML(string xmlFileName, ListsettingArray, 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 }; /// /// 加密 /// 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; } ////// 解密 /// 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 } ////// 单个设置项 /// ///由于XML中不能保存null, 所以所有属性都不会被设置为null public class ConfigureItemModel { ////// 单个设置项 /// /// 显示名称 /// 参数名称 /// 默认值 /// 描述, 该项不设定时候, 显示默认值 /// 能否为空字符串 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 = ""; ////// 显示名称 /// public string Caption { get { return caption; } set { caption = string.IsNullOrWhiteSpace(value) ? "" : value; } } private string name = ""; ////// 参数名称 /// public string Name { get { return name; } set { name = string.IsNullOrWhiteSpace(value) ? "" : value; ; } } private string description = ""; ////// 说明, 如果该值没有赋值, 则显示DefaultValue /// public string Description { get { return string.IsNullOrWhiteSpace(description) ? defaultValue : description; } set { description = string.IsNullOrWhiteSpace(value) ? "" : value; } } private string defaultValue = ""; ////// 默认值 /// public string DefaultValue { get { return defaultValue; } set { defaultValue = string.IsNullOrWhiteSpace(value) ? "" : value; } } ////// 能否为空字符串 /// public bool CanBeEmpty { get; set; } ////// 能否为空字符串 的字符串形式 /// public string CanBeEmptyString { get { return CanBeEmpty ? "是" : "否"; } } private string innerValue = ""; ////// 值 /// public string Value { get { return innerValue; } set { innerValue = string.IsNullOrWhiteSpace(value) ? "" : value; ; } } private string errorMsg = ""; ////// 错误信息 /// public string ErrorMsg { get { return errorMsg; } set { errorMsg = string.IsNullOrWhiteSpace(value) ? "" : value; ; } } }
产品里建一个 GlobalSetting 类, 里面的配置项都必须是 static 属性, 然后加入 public static List<ConfigureItemModel> DefaultGlobalSettingArray 保存默认设置
////// 全局设定 /// ///所有属性都必须是 static , 即 类属性 public class GlobalSetting { public static ListDefaultGlobalSettingArray = new List () { new ConfigureItemModel("数据库的主机地址","ConnectionStringHost", "127.0.0.1"), }; /// /// 数据库的主机地址 /// public static string ConnectionStringHost { get; set; } }
具体的示例请参考 ConfigSettingToolTest, 第一次运行时会报错: 升级地址 不能为空, 使用 配置文件编辑工具2.exe 为其赋值后, 就可以正常启动了