//******************************************************************************************************
// 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("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("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("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
}
}