//****************************************************************************************************** // ConfigurationFile.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 04/12/2006 - Pinal C. Patel // Generated original version of source code. // 11/14/2006 - Pinal C. Patel // Modified the ValidateConfigurationFile to save the config file only it was modified. // 12/12/2006 - Pinal C. Patel // Wrote the TrimEnd function that is being used for initialized the Configuration object. // 08/20/2007 - Darrell Zuercher // Edited code comments. // 09/22/2008 - Pinal C. Patel // Converted code to C#. // 09/29/2008 - Pinal C. Patel // Reviewed code comments. // 09/14/2009 - Stephen C. Wills // Added new header and license agreement. // 09/29/2008 - Pinal C. Patel // Added new CurrentWeb and CurrentWin static properties to get the configuration file for a // specific type of application (i.e. Web or Windows). // Modified GetConfiguration() to remove dependency on HttpContext.Current to avoid exception in // async web application operations where HttpContext.Current is null. // 04/20/2010 - Pinal C. Patel // Added static Open() method as the only way of retrieving a ConfigurationFile instance. // Added internal UserConfigurationFile class for accessing and updating user scope settings. // 07/01/2010 - Pinal C. Patel // Removed FileName, AppSettings and ConnectionStrings proxy properties and replaced them with // new Configuration property to allow for full access to the application configuration. // 11/26/2010 - Pinal C. Patel // Updated usage example to include user-scope setting sample. // 12/05/2010 - Pinal C. Patel // Added Culture property that can be used for specifying a culture to use for value conversion. // 01/04/2011 - J. Ritchie Carroll // Modified culture to default to InvariantCulture for English style parsing defaults. // 04/07/2011 - J. Ritchie Carroll // Added a Reload() method to reload the configuration file settings and a // RestoreDefaultUserSettings() method to restore the default user settings. // 12/12/2011 - J. Ritchie Carroll // Added synchronization to save methods such that saves from multiple threads will not // cause issues when lots of new settings are being added. // 12/14/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections.Concurrent; using System.Configuration; using System.Globalization; using System.IO; using System.Text; using System.Web.Configuration; using System.Web.Hosting; using System.Xml; using GSF.IO; using GSF.Threading; using GSF.Xml; namespace GSF.Configuration { /// /// Represents a configuration file of a Windows or Web application. /// /// /// This example shows how to save and read settings from the config file: /// /// using System; /// using System.Configuration; /// using GSF; /// using GSF.Configuration; /// /// class Program /// { /// static void Main(string[] args) /// { /// // Get the application config file. /// ConfigurationFile config = ConfigurationFile.Current; /// /// // Get the sections of config file. /// CategorizedSettingsElementCollection startup = config.Settings["Startup"]; /// CategorizedSettingsElementCollection passwords = config.Settings["Passwords"]; /// CategorizedSettingsElementCollection monitoring = config.Settings["Monitoring"]; /// KeyValueConfigurationCollection appSettings = config.Configuration.AppSettings.Settings; /// ConnectionStringSettingsCollection connStrings = config.Configuration.ConnectionStrings.ConnectionStrings; /// /// // Add settings to the config file under the "appSettings" section. /// appSettings.Add("SaveSettingOnExit", true.ToString()); /// // Add settings to the config file under the "connectionStrings" section. /// connStrings.Add(new ConnectionStringSettings("DevSql", "Server=SqlServer;Database=Sandbox;Trusted_Connection=True")); /// // Add settings to the config (if they don't exist) under a custom "monitoring" section. /// monitoring.Add("RefreshInterval", 5, "Interval in seconds at which the Monitor screen is to be refreshed."); /// monitoring.Add("MessagesSnapshot", 30000, "Maximum messages length to be displayed on the Monitor screen."); /// // Add password to the config file encrypted (if it doesn't exist) under a custom "passwords" section. /// passwords.Add("Admin", "Adm1nP4ss", "Password used for performing administrative tasks.", true); /// // Add user-scope setting to the config (if it doesn't exist) under a custom "startup" section. /// startup.Add("Theme", "Default", "Application theme to use for the session.", false, SettingScope.User); /// config.Save(); // Save settings to the config file. /// /// // Read saved settings from the config file. /// bool saveSettingsOnExit = appSettings["SaveSettingOnExit"].Value.ParseBoolean(); /// string devConnectionString = connStrings["DevSql"].ConnectionString; /// string appTheme = startup["Theme"].Value; /// string adminPassword = passwords["Admin"].Value; /// int refreshInterval = monitoring["RefreshInterval"].ValueAsInt32(); /// int messagesSnapshot = monitoring["MessagesSnapshot"].ValueAsInt32(); /// /// // Print the retrieved settings to the console. /// Console.WriteLine("SaveSettingOnExit = {0}", saveSettingsOnExit); /// Console.WriteLine("DevSql = {0}", devConnectionString); /// Console.WriteLine("Theme = {0}", appTheme); /// Console.WriteLine("Admin = {0}", adminPassword); /// Console.WriteLine("RefreshInterval = {0}", refreshInterval); /// Console.WriteLine("MessagesSnapshot = {0}", messagesSnapshot); /// /// Console.ReadLine(); /// } /// } /// /// This example shows the content of the config file from the sample code above: /// /// /// /// ///
/// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// ]]> /// /// /// /// /// // Since each instance of this class is statically cached for the process lifetime, IDisposable is not implemented so that // the instance will not be inadvertently disposed public class ConfigurationFile { #region [ Members ] // Nested Types internal class UserConfigurationFile { private readonly string m_fileName; private readonly XmlDocument m_settings; private bool m_settingsModified; private const string RootNode = "settings"; private const string Filename = "Settings.xml"; public UserConfigurationFile() { m_settings = new XmlDocument(); m_fileName = Path.Combine(FilePath.GetApplicationDataFolder(), Filename); if (File.Exists(m_fileName)) { try { // Load existing settings. m_settings.Load(m_fileName); } catch (Exception) { // Create new settings file. m_settings.AppendChild(m_settings.CreateNode(XmlNodeType.XmlDeclaration, null, null)); m_settings.AppendChild(m_settings.CreateElement(RootNode)); } } else { // Create new settings file. m_settings.AppendChild(m_settings.CreateNode(XmlNodeType.XmlDeclaration, null, null)); m_settings.AppendChild(m_settings.CreateElement(RootNode)); } } public string FileName { get { return m_fileName; } } public void Save(bool forceSave) { if (forceSave || m_settingsModified) { // Create directory if missing. string folder = FilePath.GetDirectoryName(m_fileName); if (!Directory.Exists(folder)) Directory.CreateDirectory(folder); // Save settings to the file. m_settings.Save(m_fileName); } } public void Write(string category, string name, string value) { XmlNode node = GetSetting(category, name); if ((object)node != null) { // Setting exists so update it. node.SetAttributeValue("value", value); } else { // Setting doesn't exist so add it. XmlNode setting = m_settings.CreateElement("add"); setting.SetAttributeValue("name", name); setting.SetAttributeValue("value", value); m_settings.GetXmlNode(category).AppendChild(setting); } m_settingsModified = true; } public string Read(string category, string name, string defaultValue) { XmlNode node = GetSetting(category, name); // If setting exists, return its value if ((object)node != null && (object)node.Attributes != null) return node.Attributes["value"].Value; // Setting doesn't exist so return the default return defaultValue; } public XmlNode GetSetting(string category, string name) { return m_settings.SelectSingleNode(string.Format("{0}/{1}/add[@name='{2}']", RootNode, category, name)); } } // Constants private const string CustomSectionName = "categorizedSettings"; private const string CustomSectionType = "GSF.Configuration.CategorizedSettingsSection, GSF.Core"; // Fields private string m_cryptoKey; private CultureInfo m_culture; private System.Configuration.Configuration m_configuration; private UserConfigurationFile m_userConfiguration; private volatile ConfigurationSaveMode m_saveMode; private readonly LongSynchronizedOperation m_saveOperation; internal bool m_forceSave; #endregion #region [ Constructors ] /// /// Initializes a new instance of the class. /// internal ConfigurationFile(string configFilePath) { m_culture = CultureInfo.InvariantCulture; // Do not run the save operation on a background thread since the application may be // performing the save as a final step during shutdown and the save operation must // complete, incomplete saves can cause a zero length config file to be created. m_saveOperation = new LongSynchronizedOperation(ExecuteConfigurationSave) { IsBackground = false }; m_configuration = GetConfiguration(configFilePath); if (m_configuration.HasFile && File.Exists(m_configuration.FilePath)) ValidateConfigurationFile(m_configuration.FilePath); else CreateConfigurationFile(m_configuration.FilePath); m_configuration = GetConfiguration(configFilePath); m_userConfiguration = new UserConfigurationFile(); } #endregion #region [ Properties ] /// /// Gets or sets the to use for the conversion of setting values to and from . /// public CultureInfo Culture { get { return m_culture; } set { if ((object)value != null) m_culture = value; else m_culture = CultureInfo.InvariantCulture; } } /// /// Get the underlying that can be accessed using this object. /// public System.Configuration.Configuration Configuration { get { return m_configuration; } } /// /// Gets the object representing settings under the "categorizedSettings" section of the config file. /// public CategorizedSettingsSection Settings { get { CategorizedSettingsSection settings = (CategorizedSettingsSection)m_configuration.GetSection(CustomSectionName); settings.File = this; settings.SetCryptoKey(m_cryptoKey); return settings; } } /// /// Gets the where user specific settings are saved. /// internal UserConfigurationFile UserSettings { get { return m_userConfiguration; } } private string BackupConfigFilePath { get { string filePath = m_configuration.FilePath; return Path.Combine(FilePath.GetDirectoryName(filePath), FilePath.GetFileNameWithoutExtension(filePath) + ".config.backup"); } } #endregion #region [ Methods ] /// /// Writes the configuration settings contained within this object to the configuration file that it represents. /// public void Save() { Save(m_forceSave ? ConfigurationSaveMode.Full : ConfigurationSaveMode.Modified); } /// /// Writes the configuration settings contained within this object to the configuration file that it represents. /// /// One of the values. public void Save(ConfigurationSaveMode saveMode) { m_saveMode = saveMode; #if MONO // As of Mono v3.8.0, threads are killed even when marked as non-background (at least on Linux) m_saveOperation.Run(); #else m_saveOperation.RunOnceAsync(); #endif } private void ExecuteConfigurationSave() { ConfigurationSaveMode saveMode = m_saveMode; m_userConfiguration.Save(saveMode == ConfigurationSaveMode.Full); m_configuration.Save(saveMode, m_forceSave); m_forceSave = false; try { // Attempt to create a backup configuration file #if MONO m_configuration.SaveAs(BackupConfigFilePath, ConfigurationSaveMode.Full); #else File.Copy(m_configuration.FilePath, BackupConfigFilePath, true); #endif } catch { // May not have needed rights to save backup configuration file } } /// /// Writes the configuration settings contained within this object to the specified configuration file. /// /// The path and file name to save the configuration file to. public void SaveAs(string fileName) { m_configuration.SaveAs(fileName); } /// /// Reloads the current configuration settings from the configuration file that the represents. /// public void Reload() { m_configuration = GetConfiguration(m_configuration.FilePath); m_userConfiguration = new UserConfigurationFile(); } /// /// Restores all the default settings for scoped settings. /// public void RestoreDefaultUserSettings() { if ((object)m_userConfiguration != null) { string fileName = m_userConfiguration.FileName; if (File.Exists(fileName)) File.Delete(fileName); m_userConfiguration = new UserConfigurationFile(); } } /// /// Sets the key to be used for encrypting and decrypting values of . /// /// New crypto key. public void SetCryptoKey(string cryptoKey) { m_cryptoKey = cryptoKey; } private System.Configuration.Configuration GetConfiguration(string configFilePath) { ApplicationType appType = Common.GetApplicationType(); System.Configuration.Configuration configuration; if ((object)configFilePath != null) { if (string.IsNullOrEmpty(configFilePath) || string.Compare(FilePath.GetExtension(configFilePath), ".config", StringComparison.OrdinalIgnoreCase) == 0) { // PCP - 12/12/2006: Using the TrimEnd function to get the correct value that needs to be passed // to the method call for getting the Configuration object. The previous method (String.TrimEnd()) // yielded incorrect output resulting in the Configuration object not being initialized correctly. switch (appType) { case ApplicationType.Web: if (string.IsNullOrEmpty(configFilePath)) configFilePath = HostingEnvironment.ApplicationVirtualPath; configuration = WebConfigurationManager.OpenWebConfiguration(TrimEnd(configFilePath, "web.config")); break; default: configuration = ConfigurationManager.OpenExeConfiguration(TrimEnd(configFilePath, ".config").EnsureEnd(".exe")); break; } } else { throw new ArgumentException("Path of configuration file must be either empty or end in \'.config\'"); } } else { throw new ArgumentNullException(nameof(configFilePath), "Path of configuration file path cannot be null"); } return configuration; } private void CreateConfigurationFile(string configFilePath) { if (!string.IsNullOrEmpty(configFilePath)) { XmlTextWriter configFileWriter = new XmlTextWriter(configFilePath, Encoding.UTF8); configFileWriter.Indentation = 2; configFileWriter.Formatting = Formatting.Indented; // Populates the very basic information required in a config file. configFileWriter.WriteStartDocument(); configFileWriter.WriteStartElement("configuration"); configFileWriter.WriteEndElement(); configFileWriter.WriteEndDocument(); // Closes the config file. configFileWriter.Close(); ValidateConfigurationFile(configFilePath); } else { throw (new ArgumentNullException(nameof(configFilePath), "Path of configuration file path cannot be null")); } } // Validate configuration file private void ValidateConfigurationFile(string configFilePath) { if (!string.IsNullOrEmpty(configFilePath)) { XmlDocument configFile = new XmlDocument(); bool configFileModified = false; configFile.Load(configFilePath); // Make sure that the config file has the necessary section information under // that is required by the .Net configuration API to process our custom // section. The configuration API will raise an exception if it doesn't find this section. if ((object)configFile.DocumentElement != null) { XmlNodeList configSections = configFile.DocumentElement.SelectNodes("configSections"); if ((object)configSections != null && configSections.Count == 0) { // Add a node, if one is not present. if ((object)configFile.DocumentElement != null) { // ReSharper disable once PossibleNullReferenceException configFile.DocumentElement.InsertBefore(configFile.CreateElement("configSections"), configFile.DocumentElement.FirstChild); configFileModified = true; } } XmlNode configSectionsNode = configFile.DocumentElement.SelectSingleNode("configSections"); if ((object)configSectionsNode != null) { XmlNodeList section = configSectionsNode.SelectNodes("section[@name = \'" + CustomSectionName + "\']"); if ((object)section != null && section.Count == 0) { // Adds the
node that specifies the DLL that handles the node in // the config file, if one is not present. XmlNode node = configFile.CreateElement("section"); node.SetAttributeValue("name", CustomSectionName); node.SetAttributeValue("type", CustomSectionType); configSectionsNode.AppendChild(node); configFileModified = true; } } } // 11/14/2006 - PCP: We'll save the config file only it was modified. This will prevent ASP.Net // web sites from restarting every time a configuration element is accessed. if (configFileModified) configFile.Save(configFilePath); } else { throw new ArgumentNullException(nameof(configFilePath), "Path of configuration file path cannot be null"); } } // Trim suffix from end of string private string TrimEnd(string stringToTrim, string textToTrim) { int trimEndIndex = stringToTrim.LastIndexOf(textToTrim, StringComparison.CurrentCultureIgnoreCase); if (trimEndIndex == -1) trimEndIndex = stringToTrim.Length; return stringToTrim.Substring(0, trimEndIndex); } #endregion #region [ Static ] // Static Fields private static readonly ConcurrentDictionary s_configFiles; // Static Constructor static ConfigurationFile() { s_configFiles = new ConcurrentDictionary(StringComparer.CurrentCultureIgnoreCase); } // Static Properties /// /// Gets the object that represents the config file of the currently executing Windows or Web application. /// public static ConfigurationFile Current { get { return Open(string.Empty); } } // Static Methods /// /// Opens application config file at the specified . /// /// Path of the config file that belongs to a Windows or Web application. /// An object. public static ConfigurationFile Open(string configFilePath) { // Retrieve config file from cache if present or else add it for subsequent uses. return s_configFiles.GetOrAdd(configFilePath, path => new ConfigurationFile(path)); } #endregion } }