基本共識:html
ConfigurationManager 自帶緩存,且不支持 寫入。node
若是 經過 文本寫入方式 修改 配置文件,程序 沒法刷新加載 最新配置。緩存
PS. Web.config 除外:Web.config 修改後,網站會重啓 (即 Web 程序 也沒法在 運行時 刷新配置)。多線程
爲何要在程序運行時,修改配置(刷新配置):併發
> 之前C++,VB 時代,用戶在程序界面 勾選的配置,會寫到 ini 文件。app
> C# 自帶 .exe.config 配置文件 —— 可是,C# 自帶的 ConfigurationManager 不支持 運行時 修改,運行時刷新配置。高併發
> 本文 提供工具類,完全 解決 這個問題 —— 今後,用戶手動勾選的配置 不再用寫入 ini,而是直接修改 .exe.config 文件,且當即刷新。工具
刷新 ConfigurationManager 配置 的 代碼 有兩種:網站
> 第一種:this
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 配置文件 擴展配置節點,只能寫在第一個,不然 程序報錯。)
using System; using System.Collections.Generic; using System.Configuration; using System.IO; using System.Reflection; using System.Runtime.Serialization; using System.Text; using System.Xml; namespace InkFx.Utils { public partial class Tools { private static ConfigAppSetting m_AppSettings; private static ConfigConnectionStrings m_ConnectionStrings; public static ConfigAppSetting AppSettings { get { if (m_AppSettings == null) { m_AppSettings = new ConfigAppSetting(); m_AppSettings.AppSettingChanged += OnAppSettingChanged; } return m_AppSettings; } } public static ConfigConnectionStrings ConnectionStrings { get { if (m_ConnectionStrings == null) { m_ConnectionStrings = new ConfigConnectionStrings(); m_ConnectionStrings.ConnectionStringsChanged += OnConnectionStringsChanged; } return m_ConnectionStrings; } } private static void OnAppSettingChanged(string name, string value) { string configPath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; if (!File.Exists(configPath)) { const string content = @"<?xml version=""1.0""?><configuration></configuration>"; File.WriteAllText(configPath, content, Encoding.UTF8); } XmlDocument doc = new XmlDocument(); doc.Load(configPath); XmlNode nodeConfiguration = doc.SelectSingleNode(@"configuration"); if (nodeConfiguration == null) { nodeConfiguration = doc.CreateNode(XmlNodeType.Element, "configuration", string.Empty); doc.AppendChild(nodeConfiguration); } XmlNode nodeAppSettings = nodeConfiguration.SelectSingleNode(@"appSettings"); if (nodeAppSettings == null) { nodeAppSettings = doc.CreateNode(XmlNodeType.Element, "appSettings", string.Empty); if (!nodeConfiguration.HasChildNodes) nodeConfiguration.AppendChild(nodeAppSettings); else { //configSections 必須放在 第一個, 因此得 避開 configSections XmlNode firstNode = nodeConfiguration.ChildNodes[0]; bool firstNodeIsSections = string.Equals(firstNode.Name, "configSections", StringComparison.CurrentCultureIgnoreCase); if (firstNodeIsSections) nodeConfiguration.InsertAfter(nodeAppSettings, firstNode); else nodeConfiguration.InsertBefore(nodeAppSettings, firstNode); } } string xmlName = FormatXmlStr(name); XmlNode nodeAdd = nodeAppSettings.SelectSingleNode(@"add[@key='" + xmlName + "']"); if (nodeAdd == null) { nodeAdd = doc.CreateNode(XmlNodeType.Element, "add", string.Empty); nodeAppSettings.AppendChild(nodeAdd); } XmlElement nodeElem = (XmlElement)nodeAdd; nodeElem.SetAttribute("key", name); nodeElem.SetAttribute("value", value); doc.Save(configPath); try { ConfigurationManager.RefreshSection("appSettings"); } catch (Exception) { } } private static void OnConnectionStringsChanged(string name, string value) { string configPath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; if (!File.Exists(configPath)) { const string content = @"<?xml version=""1.0""?><configuration></configuration>"; File.WriteAllText(configPath, content, Encoding.UTF8); } XmlDocument doc = new XmlDocument(); doc.Load(configPath); XmlNode nodeConfiguration = doc.SelectSingleNode(@"configuration"); if (nodeConfiguration == null) { nodeConfiguration = doc.CreateNode(XmlNodeType.Element, "configuration", string.Empty); doc.AppendChild(nodeConfiguration); } XmlNode nodeAppSettings = nodeConfiguration.SelectSingleNode(@"appSettings"); XmlNode nodeConnectionStrings = nodeConfiguration.SelectSingleNode(@"connectionStrings"); if (nodeConnectionStrings == null) { nodeConnectionStrings = doc.CreateNode(XmlNodeType.Element, "connectionStrings", string.Empty); if (!nodeConfiguration.HasChildNodes) nodeConfiguration.AppendChild(nodeConnectionStrings); else { //優先將 connectionStrings 放在 appSettings 後面 if (nodeAppSettings != null) nodeConfiguration.InsertAfter(nodeConnectionStrings, nodeAppSettings); else { //若是 沒有 appSettings 節點, 則 configSections 必須放在 第一個, 因此得 避開 configSections XmlNode firstNode = nodeConfiguration.ChildNodes[0]; bool firstNodeIsSections = string.Equals(firstNode.Name, "configSections", StringComparison.CurrentCultureIgnoreCase); if (firstNodeIsSections) nodeConfiguration.InsertAfter(nodeConnectionStrings, firstNode); else nodeConfiguration.InsertBefore(nodeConnectionStrings, firstNode); } } } string xmlName = FormatXmlStr(name); XmlNode nodeAdd = nodeConnectionStrings.SelectSingleNode(@"add[@name='" + xmlName + "']"); if (nodeAdd == null) { nodeAdd = doc.CreateNode(XmlNodeType.Element, "add", string.Empty); nodeConnectionStrings.AppendChild(nodeAdd); } XmlElement nodeElem = (XmlElement)nodeAdd; nodeElem.SetAttribute("name", name); nodeElem.SetAttribute("connectionString", value); doc.Save(configPath); try { ConfigurationManager.RefreshSection("connectionString"); //RefreshSection 沒法刷新 connectionString 節點 FieldInfo fieldInfo = typeof(ConfigurationManager).GetField("s_initState", BindingFlags.NonPublic | BindingFlags.Static); if (fieldInfo != null) fieldInfo.SetValue(null, 0); //將配置文件 設置爲: 未分析 狀態, 配置文件 將會在下次讀取 時 從新分析. } catch (Exception) { } } private static string FormatXmlStr(string value) { if (string.IsNullOrEmpty(value)) return string.Empty; string result = value .Replace("<", "<") .Replace(">", ">") .Replace("&", "&") .Replace("'", "'") .Replace("\"", """); return result; //< < 小於號 //> > 大於號 //& & 和 //' ' 單引號 //" " 雙引號 } public class ConfigAppSetting { private readonly InnerIgnoreDict<string> m_Hash = new InnerIgnoreDict<string>(); public string this[string name] { get { string value = m_Hash[name]; if (string.IsNullOrWhiteSpace(value)) { try { value = ConfigurationManager.AppSettings[name]; } catch(Exception) { } m_Hash[name] = value; return value; } return value; } set { m_Hash[name] = value; try{ ConfigurationManager.AppSettings[name] = value; } catch(Exception) { } if (AppSettingChanged != null) AppSettingChanged(name, value); } } public AppSettingValueChanged AppSettingChanged; public delegate void AppSettingValueChanged(string name, string value); } public class ConfigConnectionStrings { private readonly InnerIgnoreDict<ConnectionStringSettings> m_Hash = new InnerIgnoreDict<ConnectionStringSettings>(); public string this[string name] { get { ConnectionStringSettings value = m_Hash[name]; if (value == null || string.IsNullOrWhiteSpace(value.ConnectionString)) { try { value = ConfigurationManager.ConnectionStrings[name]; } catch (Exception) { } m_Hash[name] = value; return value == null ? string.Empty : value.ConnectionString; } return value.ConnectionString; } set { ConnectionStringSettings setting = new ConnectionStringSettings(); setting.Name = name; setting.ConnectionString = value; m_Hash[name] = setting; //try { ConfigurationManager.ConnectionStrings[name] = setting; } catch (Exception) { } if (ConnectionStringsChanged != null) ConnectionStringsChanged(name, value); } } public ConnectionStringsValueChanged ConnectionStringsChanged; public delegate void ConnectionStringsValueChanged(string name, string value); } private class InnerIgnoreDict<T> : Dictionary<string, T> { public InnerIgnoreDict(): base(StringComparer.CurrentCultureIgnoreCase) { } #if (!WindowsCE && !PocketPC) public InnerIgnoreDict(SerializationInfo info, StreamingContext context) : base(info, context) { } #endif private readonly object getSetLocker = new object(); private static readonly T defaultValue = default(T); public new T this[string key] { get { if (key == null) return defaultValue; lock (getSetLocker) //爲了 多線程的 高併發, 取值也 加上 線程鎖 { T record; if (TryGetValue(key, out record)) return record; else return defaultValue; } } set { try { if (key != null) { lock (getSetLocker) { //if (!value.Equals(default(T))) //{ if (base.ContainsKey(key)) base[key] = value; else base.Add(key, value); //} //else //{ // base.Remove(key); //} } } } catch (Exception) { } } } } } }
工具類使用代碼:
static void Main(string[] args) { Tools.AppSettings["Test"] = "Love"; //修改配置文件 Console.WriteLine(ConfigurationManager.AppSettings["Test"]); //傳統方式 讀取配置文件 Console.WriteLine(Tools.AppSettings["Test"]); //工具類 讀取配置文件 Tools.ConnectionStrings["ConnString"] = "Data Source=127.0.0.1;Initial Catalog=master;User=sa;password=123.com;"; Console.WriteLine(ConfigurationManager.ConnectionStrings["ConnString"]); Console.WriteLine(Tools.ConnectionStrings["ConnString"]); Tools.AppSettings["Test"] = "<Love>"; Console.WriteLine(ConfigurationManager.AppSettings["Test"]); Console.WriteLine(Tools.AppSettings["Test"]); Console.ReadKey(); }
執行結果:
配置文件變化:
> 程序執行前,刪除配置文件。
> 程序執行後,自動生成配置文件。
出處:https://www.cnblogs.com/shuxiaolong/p/20160907_1432.html
完畢!