公司是做CS產品的, 最近分配給我一個活, 要求: 1. 公司程式啟動時, 檢測是否有配置文件, 沒有的話則按預設值創建一個 2. 配置文件要加密, 不能讓客戶隨便看到裡面的參數 3. 配置文件要有配套的GUI配置工具, 因為現場實施人員嫌XML配置麻煩 如果只有一個產品需要這個功能, 把每個配置項 ...
公司是做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; ; } } }View Code
產品里建一個 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; } }View Code
具體的示例請參考 ConfigSettingToolTest, 第一次運行時會報錯: 升級地址 不能為空, 使用 配置文件編輯工具2.exe 為其賦值後, 就可以正常啟動了
ConfigSettingToolTest ConfigEditer