基本共識:
ConfigurationManager 自帶緩存,且不支持 寫入。
如果 通過 文本寫入方式 修改 配置文件,程序 無法刷新加載 最新配置。
PS. Web.config 除外:Web.config 修改後,網站會重啟 (即 Web 程序 也無法在 運行時 刷新配置)。
為什麼要在程序運行時,修改配置(刷新配置):
> 以前C++,VB 時代,用戶在程序界面 勾選的配置,會寫到 ini 文件。
> C# 自帶 .exe.config 配置文件 —— 但是,C# 自帶的 ConfigurationManager 不支持 運行時 修改,運行時刷新配置。
> 本文 提供工具類,徹底 解決 這個問題 —— 從此,用戶手動勾選的配置 再也不用寫入 ini,而是直接修改 .exe.config 文件,且立即刷新。
刷新 ConfigurationManager 配置 的 代碼 有兩種:
> 第一種:
ConfigurationManager.RefreshSection("appSettings"); //刷新 appSettings 節點 (立即生效) ConfigurationManager.RefreshSection("connectionString"); //刷新 connectionString 節點 (無法生效 —— 可能是 微軟處理時,因為 LocalSqlServer 這個默認配置 而導致的疏忽)
> 第二種:
FieldInfo fieldInfo = typeof(ConfigurationManager).GetField("s_initState", BindingFlags.NonPublic | BindingFlags.Static); if (fieldInfo != null) fieldInfo.SetValue(null, 0); //將配置文件 設置為: 未分析 狀態, 配置文件 將會在下次讀取 時 重新分析.
//立即生效,而且效果 明顯 —— 就喜歡這種 暴力做法。
一起反編譯 ConfigurationManager 代碼:
> 首先 下載 ILSpy 或 Reflector (本文使用的是 ILSpy.)
> 打開 ILSpy 搜索 ConfigurationManager,執行如下操作:
> 編寫 反射代碼,刷新 配置文件數據。(具體代碼 在 文章最開始。)
額外提供 配置文件 修改的 工具類代碼:
以下代碼 實現如下功能:
> 執行 配置寫入操作時,自動創建 .exe.config 文件,自動創建 appSettings connectionString 節點。
> .exe.config 寫入配置時,如果 相同的 key name 存在,則修改,不存在 則創建。
> 額外的 審美操作:
> 很多人習慣 appSettings 顯示在 connectionString 前面。
> 很多人習慣 appSettings 在 最前面。
> appSettings 必須在 configSections 後面。(configSections 配置文件 擴展配置節點,只能寫在第一個,否則 程序報錯。)
1 using System; 2 using System.Collections.Generic; 3 using System.Configuration; 4 using System.IO; 5 using System.Reflection; 6 using System.Runtime.Serialization; 7 using System.Text; 8 using System.Xml; 9 10 namespace InkFx.Utils 11 { 12 public partial class Tools 13 { 14 15 private static ConfigAppSetting m_AppSettings; 16 private static ConfigConnectionStrings m_ConnectionStrings; 17 18 public static ConfigAppSetting AppSettings 19 { 20 get 21 { 22 if (m_AppSettings == null) 23 { 24 m_AppSettings = new ConfigAppSetting(); 25 m_AppSettings.AppSettingChanged += OnAppSettingChanged; 26 } 27 return m_AppSettings; 28 } 29 } 30 public static ConfigConnectionStrings ConnectionStrings 31 { 32 get 33 { 34 if (m_ConnectionStrings == null) 35 { 36 m_ConnectionStrings = new ConfigConnectionStrings(); 37 m_ConnectionStrings.ConnectionStringsChanged += OnConnectionStringsChanged; 38 } 39 return m_ConnectionStrings; 40 } 41 } 42 43 44 45 private static void OnAppSettingChanged(string name, string value) 46 { 47 string configPath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; 48 if (!File.Exists(configPath)) 49 { 50 const string content = @"<?xml version=""1.0""?><configuration></configuration>"; 51 File.WriteAllText(configPath, content, Encoding.UTF8); 52 } 53 54 XmlDocument doc = new XmlDocument(); 55 doc.Load(configPath); 56 57 XmlNode nodeConfiguration = doc.SelectSingleNode(@"configuration"); 58 if (nodeConfiguration == null) 59 { 60 nodeConfiguration = doc.CreateNode(XmlNodeType.Element, "configuration", string.Empty); 61 doc.AppendChild(nodeConfiguration); 62 } 63 64 XmlNode nodeAppSettings = nodeConfiguration.SelectSingleNode(@"appSettings"); 65 if (nodeAppSettings == null) 66 { 67 nodeAppSettings = doc.CreateNode(XmlNodeType.Element, "appSettings", string.Empty); 68 if (!nodeConfiguration.HasChildNodes) 69 nodeConfiguration.AppendChild(nodeAppSettings); 70 else 71 { 72 //configSections 必須放在 第一個, 所以得 避開 configSections 73 XmlNode firstNode = nodeConfiguration.ChildNodes[0]; 74 bool firstNodeIsSections = string.Equals(firstNode.Name, "configSections", StringComparison.CurrentCultureIgnoreCase); 75 76 if (firstNodeIsSections) 77 nodeConfiguration.InsertAfter(nodeAppSettings, firstNode); 78 else 79 nodeConfiguration.InsertBefore(nodeAppSettings, firstNode); 80 } 81 } 82 83 string xmlName = FormatXmlStr(name); 84 XmlNode nodeAdd = nodeAppSettings.SelectSingleNode(@"add[@key='" + xmlName + "']"); 85 if (nodeAdd == null) 86 { 87 nodeAdd = doc.CreateNode(XmlNodeType.Element, "add", string.Empty); 88 nodeAppSettings.AppendChild(nodeAdd); 89 } 90 91 XmlElement nodeElem = (XmlElement)nodeAdd; 92 nodeElem.SetAttribute("key", name); 93 nodeElem.SetAttribute("value", value); 94 doc.Save(configPath); 95 96 try { ConfigurationManager.RefreshSection("appSettings"); } catch (Exception) { } 97 } 98 private static void OnConnectionStringsChanged(string name, string value) 99 { 100 string configPath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; 101 if (!File.Exists(configPath)) 102 { 103 const string content = @"<?xml version=""1.0""?><configuration></configuration>"; 104 File.WriteAllText(configPath, content, Encoding.UTF8); 105 } 106 107 XmlDocument doc = new XmlDocument(); 108 doc.Load(configPath); 109 110 XmlNode nodeConfiguration = doc.SelectSingleNode(@"configuration"); 111 if (nodeConfiguration == null) 112 { 113 nodeConfiguration = doc.CreateNode(XmlNodeType.Element, "configuration", string.Empty); 114 doc.AppendChild(nodeConfiguration); 115 } 116 117 XmlNode nodeAppSettings = nodeConfiguration.SelectSingleNode(@"appSettings"); 118 XmlNode nodeConnectionStrings = nodeConfiguration.SelectSingleNode(@"connectionStrings"); 119 if (nodeConnectionStrings == null) 120 { 121 nodeConnectionStrings = doc.CreateNode(XmlNodeType.Element, "connectionStrings", string.Empty); 122 if (!nodeConfiguration.HasChildNodes) 123 nodeConfiguration.AppendChild(nodeConnectionStrings); 124 else 125 { 126 //優先將 connectionStrings 放在 appSettings 後面 127 if (nodeAppSettings != null) 128 nodeConfiguration.InsertAfter(nodeConnectionStrings, nodeAppSettings); 129 else 130 { 131 //如果 沒有 appSettings 節點, 則 configSections 必須放在 第一個, 所以得 避開 configSections 132 XmlNode firstNode = nodeConfiguration.ChildNodes[0]; 133 bool firstNodeIsSections = string.Equals(firstNode.Name, "configSections", StringComparison.CurrentCultureIgnoreCase); 134 135 if (firstNodeIsSections) 136 nodeConfiguration.InsertAfter(nodeConnectionStrings, firstNode); 137 else 138 nodeConfiguration.InsertBefore(nodeConnectionStrings, firstNode); 139 } 140 } 141 } 142 143 string xmlName = FormatXmlStr(name); 144 XmlNode nodeAdd = nodeConnectionStrings.SelectSingleNode(@"add[@name='" + xmlName + "']"); 145 if (nodeAdd == null) 146 { 147 nodeAdd = doc.CreateNode(XmlNodeType.Element, "add", string.Empty); 148 nodeConnectionStrings.AppendChild(nodeAdd); 149 } 150 151 XmlElement nodeElem = (XmlElement)nodeAdd; 152 nodeElem.SetAttribute("name", name); 153 nodeElem.SetAttribute("connectionString", value); 154 doc.Save(configPath); 155 156 try 157 { 158 ConfigurationManager.RefreshSection("connectionString"); //RefreshSection 無法刷新 connectionString 節點 159 FieldInfo fieldInfo = typeof(ConfigurationManager).GetField("s_initState", BindingFlags.NonPublic | BindingFlags.Static); 160 if (fieldInfo != null) fieldInfo.SetValue(null, 0); //將配置文件 設置為: 未分析 狀態, 配置文件 將會在下次讀取 時 重新分析. 161 } 162 catch (Exception) { } 163 } 164 165 private static string FormatXmlStr(string value) 166 { 167 if (string.IsNullOrEmpty(value)) return string.Empty; 168 169 string result = value 170 .Replace("<", "<") 171 .Replace(">", ">") 172 .Replace("&", "&") 173 .Replace("'", "'") 174 .Replace("\"", """); 175 return result; 176 //< < 小於號 177 //> > 大於號 178 //& & 和 179 //' ' 單引號 180 //" " 雙引號 181 } 182 183 184 public class ConfigAppSetting 185 { 186 private readonly InnerIgnoreDict<string> m_Hash = new InnerIgnoreDict<string>(); 187 188 public string this[string name] 189 { 190 get 191 { 192 string value = m_Hash[name]; 193 if (string.IsNullOrWhiteSpace(value)) 194 { 195 try { value = ConfigurationManager.AppSettings[name]; } catch(Exception) { } 196 m_Hash[name] = value; 197 return value; 198 } 199 return value; 200 } 201 set 202 { 203 m_Hash[name] = value; 204 try{ ConfigurationManager.AppSettings[name] = value; } catch(Exception) { } 205 if (AppSettingChanged != null) AppSettingChanged(name, value); 206 } 207 } 208 public AppSettingValueChanged AppSettingChanged; 209 210 public delegate void AppSettingValueChanged(string name, string value); 211 } 212 public class ConfigConnectionStrings 213 { 214 private readonly InnerIgnoreDict<ConnectionStringSettings> m_Hash = new InnerIgnoreDict<ConnectionStringSettings>(); 215 216 public string this[string name] 217 { 218 get 219 { 220 ConnectionStringSettings value = m_Hash[name]; 221 if (value == null || string.IsNullOrWhiteSpace(value.ConnectionString)) 222 { 223 try { value = ConfigurationManager.ConnectionStrings[name]; } catch (Exception) { } 224 m_Hash[name] = value; 225 return value == null ? string.Empty : value.ConnectionString; 226 } 227 return value.ConnectionString; 228 } 229 set 230 { 231 232 ConnectionStringSettings setting = new ConnectionStringSettings(); 233 setting.Name = name; 234 setting.ConnectionString = value; 235 m_Hash[name] = setting; 236 //try { ConfigurationManager.ConnectionStrings[name] = setting; } catch (Exception) { } 237 if (ConnectionStringsChanged != null) ConnectionStringsChanged(name, value); 238 } 239 } 240 public ConnectionStringsValueChanged ConnectionStringsChanged; 241 242 public delegate void ConnectionStringsValueChanged(string name, string value); 243 } 244 245 246 247 private class InnerIgnoreDict<T> : Dictionary<string, T> 248 { 249 public InnerIgnoreDict(): base(StringComparer.CurrentCultureIgnoreCase) 250 { 251 } 252 253 #if (!WindowsCE && !PocketPC) 254 public InnerIgnoreDict(SerializationInfo info, StreamingContext context) : base(info, context) { } 255 #endif 256 257 private readonly object getSetLocker = new object(); 258 private static readonly T defaultValue = default(T); 259 260 public new T this[string key] 261 { 262 get 263 { 264 if (key == null) return defaultValue; 265 lock (getSetLocker) //為了 多線程的 高並發, 取值也 加上 線程鎖 266 { 267 T record; 268 if (TryGetValue(key, out record)) return record; 269 else return defaultValue; 270 } 271 } 272 set 273 { 274 try 275 { 276 if (key != null) 277 { 278 lock (getSetLocker) 279 { 280 //if (!value.Equals(default(T))) 281 //{ 282 if (base.ContainsKey(key)) base[key] = value; 283 else base.Add(key, value); 284 //} 285 //else 286 //{ 287 // base.Remove(key); 288 //} 289 } 290 } 291 } 292 catch (Exception) { } 293 } 294 } 295 } 296 297 } 298 } View Code
工具類使用代碼:
1 static void Main(string[] args) 2 { 3 Tools.AppSettings["Test"] = "Love"; //修改配置文件 4 Console.WriteLine(ConfigurationManager.AppSettings["Test"]); //傳統方式 讀取配置文件 5 Console.WriteLine(Tools.AppSettings["Test"]); //工具類 讀取配置文件 6 7 Tools.ConnectionStrings["ConnString"] = "Data Source=127.0.0.1;Initial Catalog=master;User=sa;password=123.com;"; 8 Console.WriteLine(ConfigurationManager.ConnectionStrings["ConnString"]); 9 Console.WriteLine(Tools.ConnectionStrings["ConnString"]); 10 11 Tools.AppSettings["Test"] = "<Love>"; 12 Console.WriteLine(ConfigurationManager.AppSettings["Test"]); 13 Console.WriteLine(Tools.AppSettings["Test"]); 14 15 Console.ReadKey(); 16 }
執行結果:
配置文件變化:
> 程序執行前,刪除配置文件。
> 程序執行後,自動生成配置文件。