/// <summary> /// Updates the specified configuration loading settings from the configuration file. /// </summary> /// <exception cref="InvalidOperationException"> /// The configuration is a child configuration (try to load the root configuration instead). /// </exception> /// <exception cref="ConfigurationException">Loading the configuration file failed.</exception> public override void Load(CascadedConfiguration configuration) { if (configuration != configuration.RootConfiguration) { throw new InvalidOperationException("The configuration is a child configuration (try to load the root configuration instead)."); } lock (configuration.Sync) { // load existing configuration file mXmlDocument = new XmlDocument(); try { if (File.Exists(mConfigurationFilePath)) { mXmlDocument.Load(mConfigurationFilePath); } } catch (Exception ex) { throw new ConfigurationException( string.Format("Loading configuration file ({0}) failed.", mConfigurationFilePath), ex); } // read configuration from the xml document into the configuration (should not throw any exception) XmlElement root = mXmlDocument.SelectSingleNode("//ConfigurationFile") as XmlElement; if (root != null) { LoadInternal(configuration, root); } } }
/// <summary> /// Adds/sets an XML element ('Item') with the specified name in the 'name' attribute and the specified value /// as inner text of the 'Item' element. /// </summary> /// <param name="parent">The parent XML element of the 'Item' element to add/set.</param> /// <param name="configuration">Configuration the configuration item is in.</param> /// <param name="itemName">Name of the 'Item' element to add/set.</param> /// <param name="type">Type of the value to set.</param> /// <param name="value">Value of the 'Item' element to add/set (null to remove the item).</param> private XmlElement SetItem(XmlElement parent, CascadedConfiguration configuration, string itemName, Type type, object value) { if (type.IsArray && type.GetArrayRank() == 1) { // an hash value is stored using nested 'Item' elements Type elementType = type.GetElementType(); Array array = value as Array; XmlElement arrayElement = SetItem(parent, itemName, null); // remove all old xml elements representing an hash element foreach (XmlNode node in arrayElement.SelectNodes("Item")) { arrayElement.RemoveChild(node); } // add new xml elements, one for each hash element for (int i = 0; i < array.Length; i++) { object obj = array.GetValue(i); SetItem(arrayElement, configuration, null, elementType, obj); } return(arrayElement); } else if (type.IsEnum) { // an enumeration value should be stored as a string string s = value.ToString(); return(SetItem(parent, itemName, s)); } else { // the configuration item contains a value that is not covered by the standard types // try to get a converter that has been registered with the configuration IConverter converter = GetValueConverter(type); if (converter == null) { converter = Converters.GetGlobalConverter(type); } if (converter != null) { // found converter, write configuration item into the xml document string s = converter.ConvertObjectToString(value, CultureInfo.InvariantCulture); return(SetItem(parent, itemName, s)); } else { // there is no way to make the configuration item persistent // (should never happen, since such configuration item should never get into the configuration!) throw new ConfigurationException( "The configuration contains an item the configuration cannot make persistent (configuration: {0}, item: {1}, item type: {2}).", configuration.Name, itemName, type.FullName); } } }
/// <summary> /// Saves configuration data from the specified configuration into the backend storage. /// </summary> /// <param name="configuration">Configuration to save.</param> /// <param name="flags">Flags controlling the saving behavior.</param> /// <exception cref="ConfigurationException"> /// The configuration is a child configuration (try to load the root configuration instead). /// </exception> public override void Save(CascadedConfiguration configuration, CascadedConfigurationSaveFlags flags) { if (mConfigurationFilePath == null) { throw new ConfigurationException("The configuration is a child configuration (try to save the root configuration instead)."); } // load existing configuration file XmlDocument doc = new XmlDocument(); try { if (File.Exists(mConfigurationFilePath)) { doc.Load(mConfigurationFilePath); } } catch (Exception ex) { throw new ConfigurationException( string.Format("Loading existing configuration file ({0}) before saving failed.", mConfigurationFilePath), ex); } // create root node, if necessary XmlElement root = doc.SelectSingleNode("//ConfigurationFile") as XmlElement; if (root == null) { root = doc.CreateElement("ConfigurationFile"); doc.AppendChild(root); } // modify the xml document to reflect the settings in the configuration lock (configuration.Sync) { SaveInternal(configuration, root, flags); } // save the xml document string directoryPath = Path.GetDirectoryName(mConfigurationFilePath); if (!string.IsNullOrWhiteSpace(directoryPath) && !Directory.Exists(directoryPath)) { Directory.CreateDirectory(directoryPath); } string tempFile = mConfigurationFilePath + ".tmp"; doc.Save(tempFile); try { File.Delete(mConfigurationFilePath); } catch { } File.Move(tempFile, mConfigurationFilePath); }
public void GetChildConfiguration_AddNewAndGet(string path) { // at the beginning, the configuration should not exist CascadedConfiguration child = mConfiguration.GetChildConfiguration(path, false); Assert.Null(child); // create configuration child = mConfiguration.GetChildConfiguration(path, true); Assert.NotNull(child); // get existing configuration CascadedConfiguration existingChild = mConfiguration.GetChildConfiguration(path, false); Assert.NotNull(existingChild); Assert.Same(child, existingChild); }
/// <summary> /// Reads the specified XML element (a 'Configuration' element) and updates the corresponding items in the specified /// configuration. /// </summary> /// <param name="configuration">Configuration to update.</param> /// <param name="parent">Parent element in the XML tree.</param> private void LoadInternal(CascadedConfiguration configuration, XmlElement parent) { // read 'Configuration' element XmlElement configurationElement = parent.SelectSingleNode(string.Format("Configuration[@name='{0}']", configuration.Name)) as XmlElement; if (configurationElement != null) { foreach (ICascadedConfigurationItem item in configuration.Items) { XmlElement itemElement = configurationElement.SelectSingleNode(string.Format("Item[@name='{0}']", item.Name)) as XmlElement; if (itemElement != null) { object value = GetValueFromXmlElement(itemElement, item.Name, item.Type); item.Value = value; } else { item.ResetValue(); } } // load child configurations foreach (CascadedConfiguration child in configuration.Children) { LoadInternal(child, configurationElement); } // add configurations that do not exist, yet // (items are not mapped, since items are typed and the file does not contain any type information) foreach (XmlElement element in configurationElement.SelectNodes("Configuration[@name]")) { string name = element.Attributes["name"].Value; CascadedConfiguration child = configuration.Children.Where(x => x.Name == name).FirstOrDefault(); if (child == null) { child = configuration.GetChildConfiguration(CascadedConfigurationPathHelper.EscapeName(name), true); LoadInternal(child, configurationElement); } } } else { configuration.ResetItems(true); } }
/// <summary> /// Loads the value of the specified configuration item from the persistent storage. /// </summary> /// <param name="item">Item to load.</param> public override void LoadItem(ICascadedConfigurationItem item) { CascadedConfiguration configuration = item.Configuration.RootConfiguration; lock (configuration.Sync) { if (mXmlDocument != null) { XmlElement root = mXmlDocument.SelectSingleNode("//ConfigurationFile") as XmlElement; if (root != null) { XmlElement rootConfigurationElement = root.SelectSingleNode(string.Format("Configuration[@name='{0}']", configuration.Name)) as XmlElement; if (rootConfigurationElement != null) { LoadItemInternal(item, item.Path.TrimStart('/'), rootConfigurationElement); } } } } }
/// <summary> /// Writes the specified configuration into the specified XML element /// </summary> /// <param name="configuration">Configuration to write.</param> /// <param name="parent">Parent element in the XML tree.</param> /// <param name="flags">Flags controlling the saving behavior.</param> private void SaveInternal(CascadedConfiguration configuration, XmlElement parent, CascadedConfigurationSaveFlags flags) { // create 'Configuration' element XmlElement configurationElement = parent.SelectSingleNode(string.Format("Configuration[@name='{0}']", configuration.Name)) as XmlElement; if (configurationElement == null) { configurationElement = parent.OwnerDocument.CreateElement("Configuration"); XmlAttribute configurationNameAttribute = parent.OwnerDocument.CreateAttribute("name"); configurationNameAttribute.InnerText = configuration.Name; configurationElement.Attributes.Append(configurationNameAttribute); parent.AppendChild(configurationElement); } foreach (ICascadedConfigurationItem item in configuration.Items) { if (item.HasValue || flags.HasFlag(CascadedConfigurationSaveFlags.SaveInheritedSettings)) { XmlElement itemElement = SetItem(configurationElement, configuration, item.Name, item.Type, item.Value); // remove all comment nodes before the node for (int i = 0; i < configurationElement.ChildNodes.Count; i++) { XmlNode node = configurationElement.ChildNodes[i]; if (node == itemElement) { for (int j = i; j > 0; j--) { node = configurationElement.ChildNodes[j - 1]; if (node.NodeType != XmlNodeType.Comment) { break; } configurationElement.RemoveChild(node); } break; } } // add comment nodes if (item.Comment != null) { string[] commentLines = item.Comment.Split('\n'); foreach (string commentLine in commentLines) { string line = commentLine.Trim(); if (line.Length > 0) { XmlComment commentNode = configurationElement.OwnerDocument.CreateComment(line); configurationElement.InsertBefore(commentNode, itemElement); } } } } else { RemoveItem(configurationElement, item.Name); } } // save child configurations foreach (CascadedConfiguration child in configuration.Children) { SaveInternal(child, configurationElement, flags); } }
/// <summary> /// Sets the configuration the current item is in. /// </summary> /// <param name="configuration">Configuration to set.</param> void ICascadedConfigurationItemInternal.SetConfiguration(CascadedConfiguration configuration) { Configuration = configuration; }
public CascadedConfigurationTests_XmlFilePersistence() { ICascadedConfigurationPersistenceStrategy persistence = new XmlFilePersistenceStrategy(ConfigurationFilePath); mConfiguration = new CascadedConfiguration(RootConfigurationName, persistence); }
public CascadedConfigurationTests_RegistryPersistence() { ICascadedConfigurationPersistenceStrategy persistence = new RegistryPersistenceStrategy(RegistryKeyPath); mConfiguration = new CascadedConfiguration(RootConfigurationName, persistence); }
/// <summary> /// Saves the specified configuration in the registry. /// </summary> /// <param name="configuration">Configuration to save.</param> /// <param name="flags">Flags controlling the save behavior.</param> /// <exception cref="SecurityException">The user does not have the permissions required to read from the registry key.</exception> /// <exception cref="ConfigurationException">Saving the configuration failed due to a serialization error.</exception> public override void Save(CascadedConfiguration configuration, CascadedConfigurationSaveFlags flags) { lock (configuration.Sync) { string keyPath = mKeyBasePath + "\\" + configuration.Path.Trim('/').Replace('/', '\\'); foreach (ICascadedConfigurationItem item in configuration.Items) { if (item.HasValue || flags.HasFlag(CascadedConfigurationSaveFlags.SaveInheritedSettings)) { if (item.Type.IsArray) { // array type => always mapped to a multi-string // => use converter to perform the conversion (all basically supported types also have a converter) Type elementType = item.Type.GetElementType(); IConverter converter = GetValueConverter(elementType); if (converter == null) { converter = Converters.GetGlobalConverter(elementType); } List <string> value = new List <string>(); foreach (object obj in (IEnumerable)item.Value) { value.Add(converter.ConvertObjectToString(obj, CultureInfo.InvariantCulture)); } Registry.SetValue(keyPath, item.Name, value.ToArray(), RegistryValueKind.MultiString); } else { if (item.Type == typeof(bool) || item.Type == typeof(byte) || item.Type == typeof(ushort) || item.Type == typeof(uint)) { // booleans and unsigned integers (8/16/32-bit) best map to a registry DWORD (32-bit) object value = item.Value; if (value is bool) { value = (bool)value ? 1 : 0; } Registry.SetValue(keyPath, item.Name, value, RegistryValueKind.DWord); } else if (item.Type == typeof(ulong)) { // unsigned integer (64-bit) best maps to a registry DWORD (64-bit) Registry.SetValue(keyPath, item.Name, item.Value, RegistryValueKind.QWord); } else if (item.Type == typeof(string[])) { // a string hash becomes a registry multi-string Registry.SetValue(keyPath, item.Name, item.Value, RegistryValueKind.MultiString); } else if (item.Type.IsEnum) { // an enumeration value should be stored as a string string value = item.Value.ToString(); Registry.SetValue(keyPath, item.Name, value, RegistryValueKind.String); } else { // the configuration item contains a value that is not covered by the standard types // => try to find some other way to make it persistent... if (SaveUsingConverter(item, keyPath)) { continue; } // there is no way to make the configuration item persistent // (should never happen, since types that are not supported should never get into the configuration) throw new ConfigurationException( "The configuration contains an item (path: {0}, type: {1}) that cannot be loaded/saved.", item.Path, item.Type.FullName); } } } else { // the configuration item does not have a value // => remove corresponding registry value DeleteRegistryValue(keyPath, item.Name); } } } // save child configurations foreach (CascadedConfiguration child in configuration.Children) { child.Save(flags); } }
/// <summary> /// Updates the specified configuration loading settings from the registry. /// </summary> /// <exception cref="SecurityException"> /// The user does not have the permissions required to read from the registry key. /// </exception> /// <exception cref="ConfigurationException"> /// Loading the configuration failed due to a serialization error. /// </exception> public override void Load(CascadedConfiguration configuration) { lock (configuration.Sync) { string keyPath = mKeyBasePath + "\\" + configuration.Path.Trim('/').Replace('/', '\\'); foreach (ICascadedConfigurationItem item in configuration.Items) { object registryValue = Registry.GetValue(keyPath, item.Name, null); if (registryValue != null) { if (item.Type.IsArray) { // array types are always mapped to a registry multi-string if (registryValue.GetType() == typeof(string[])) { IConverter converter = GetValueConverter(item.Type); if (converter == null) { converter = Converters.GetGlobalConverter(item.Type); } if (converter != null) { try { string[] regMultiStringValue = (string[])registryValue; Array conversionResult = Array.CreateInstance(item.Type.GetElementType(), regMultiStringValue.Length); for (int i = 0; i < regMultiStringValue.Length; i++) { object obj = converter.ConvertStringToObject(regMultiStringValue[i], CultureInfo.InvariantCulture); conversionResult.SetValue(obj, i); } item.Value = conversionResult; continue; } catch (Exception) { throw new ConfigurationException( @"Loading configuration item (path: {0}, type: {1}) failed, because parsing the registry value (path: {2}\{3}, value: {4}) failed.", item.Path, item.Type.FullName, keyPath, item.Name, registryValue); } } } else { throw new ConfigurationException( @"Loading configuration item (path: {0}, type: {1}) failed, because the registry value (path: {2}\{3}) is expected to be a '{4}', but it isn't.", item.Path, item.Type.FullName, keyPath, item.Name, RegistryValueKind.MultiString); } } else { if (item.Type == typeof(bool) || item.Type == typeof(byte) || item.Type == typeof(ushort) || item.Type == typeof(uint)) { // booleans and 8/16/32 bit unsigned integers are mapped to a registry DWORD if (registryValue.GetType() == typeof(int)) { if (item.Type == typeof(bool)) { item.Value = (bool)((int)registryValue != 0); } else if (item.Type == typeof(byte)) { item.Value = (byte)(int)registryValue; } else if (item.Type == typeof(ushort)) { item.Value = (ushort)(int)registryValue; } else if (item.Type == typeof(uint)) { item.Value = (uint)(int)registryValue; } continue; } else { throw new ConfigurationException( @"Loading configuration item (path: {0}, type: {1}) failed, because the registry value (path: {2}\{3}) is expected to be a '{4}', but it isn't.", item.Path, item.Type.FullName, mKeyBasePath, item.Name, RegistryValueKind.DWord); } } else if (item.Type == typeof(ulong)) { // 64 bit unsigned integers are mapped to a registry QWORD if (registryValue.GetType() == typeof(long)) { item.Value = (ulong)(long)registryValue; continue; } else { throw new ConfigurationException( @"Loading configuration item (path: {0}, type: {1}) failed, because the registry value (path: {2}\{3}) is expected to be a '{4}', but it isn't.", item.Path, item.Type.FullName, keyPath, item.Name, RegistryValueKind.QWord); } } else if (item.Type == typeof(string[])) { // string arrays are mapped to a registry multi-string if (registryValue.GetType() == typeof(string[])) { item.Value = registryValue; continue; } else { throw new ConfigurationException( @"Loading configuration item (path: {0}, type: {1}) failed, because the registry value (path: {2}\{3}) is expected to be a '{4}', but it isn't.", item.Path, item.Type.FullName, keyPath, item.Name, RegistryValueKind.MultiString); } } else if (item.Type.IsEnum) { // enumerations are mapped to a registry string if (registryValue.GetType() == typeof(string)) { try { item.Value = Enum.Parse(item.Type, (string)registryValue); continue; } catch (Exception) { throw new ConfigurationException( @"Loading configuration item (path: {0}, type: {1}) failed, because parsing the registry value (path: {2}\{3}, value: {4}) failed.", item.Path, item.Type.FullName, keyPath, item.Name, registryValue); } } else { throw new ConfigurationException( @"Loading configuration item (path: {0}, type: {1}) failed, because the registry value (path: {2}\{3}) is expected to be a '{4}', but it isn't.", item.Path, item.Type.FullName, keyPath, item.Name, RegistryValueKind.String); } } else { // any other type is converted to a string using a converter if (registryValue.GetType() == typeof(string)) { IConverter converter = GetValueConverter(item.Type); if (converter == null) { converter = Converters.GetGlobalConverter(item.Type); } try { if (converter != null) { object obj = converter.ConvertStringToObject((string)registryValue, CultureInfo.InvariantCulture); item.Value = obj; continue; } } catch (Exception) { throw new ConfigurationException( @"Loading configuration item (path: {0}, type: {1}) failed, because parsing the registry value (path: {2}\{3}, value: {4}) failed.", item.Path, item.Type.FullName, keyPath, item.Name, registryValue); } } else { throw new ConfigurationException( @"Loading configuration item (path: {0}, type: {1}) failed, because the registry value (path: {2}\{3}) is expected to be a '{4}', but it isn't.", item.Path, item.Type.FullName, keyPath, item.Name, RegistryValueKind.String); } } // there is no way to make the configuration item persistent // (should never happen, since types that are not supported should never get into the configuration) throw new ConfigurationException( "The configuration contains an item (path: {0}, type: {1}) that cannot be loaded/saved.", item.Path, item.Type.FullName); } } } // load child configurations foreach (CascadedConfiguration child in configuration.Children) { child.Load(); } } }
/// <summary> /// Saves configuration data from the specified configuration into the backend storage. /// </summary> /// <param name="configuration">Configuration to save.</param> /// <param name="flags">Flags controlling the saving behavior.</param> public abstract void Save(CascadedConfiguration configuration, CascadedConfigurationSaveFlags flags);
/// <summary> /// Loads configuration data from the backend storage into the specified configuration. /// </summary> /// <param name="configuration">Configuration to update.</param> public abstract void Load(CascadedConfiguration configuration);
public CascadedConfigurationTests_NoPersistence() { mConfiguration = new CascadedConfiguration("Test Configuration", null); }