基本共识:
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 }
执行结果:
配置文件变化:
> 程序执行前,删除配置文件。
> 程序执行后,自动生成配置文件。