|
dev
newsgroups
|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
Custom (inherited) ConfigurationSection saved using Configuration.Save() Problem! Please Help!some public forums a while back and modified it to be a fully generic ConfigurationSection for use on any generic object type. The purpose is to be able to write a Configuration Object that uses Xml Serialization attributes for how to write the XML configuration section. Well, everything works excellent, however, there is a problem. ObjectConfigurationSection and ConfigurationObject classes are defined in a shared assembly that is registered in the GAC and re-used. This assembly has a high likelihood of being upgraded over time, but I DO NOT want to have to upgrade the product. Since GAC assembly references in the <configSections><section> type attribute must use the FULL assembly name (including version), this was impossible. My workaround was making the ObjectConfigurationSection abstract and forcing each product to inherit from that class and create their own local version of the class. This allows the product to load the GAC assembly with a non-version-specific reference, yet the <section> type attribute only has to point at a local assembly to find the appropriate ConfigurationSection object. Problem: I use ConfigurationManager to load and save configuration files (hence why I went through all of this trouble to make this a ConfigurationSection). When I use any of the Configuration.Save or SaveAs methods, it writes everything correctly, but the <section> type attribute it writes is referencing the BASE class and assembly (ObjectConfigurationSection). This is WRONG in my opinion, it should use polymorphism to reference the actual object's type and reference that. Is there a way to get around this? I really don't want it writing a section type that references the GAC assembly. I want it to use the local assembly that has inherited those classes. If I write a configuration file manually with the local assembly reference, everything WORKS. If I use the configuration IT writes (referencing the GAC assembly), everything WORKS, but if I upgrade the GAC assembly (even a compatible, non-structure-changing upgrade), it FAILS because it can't locate that specific GAC assembly version. Here is my code: ObjectConfigurationSection: /// <summary> /// A configuration section for containing an arbitrary serialized object. /// </summary> public class ObjectConfigurationSection : ConfigurationSection { private ConfigurationObject data; private string fileName; private FileSystemWatcher watcher; /// <summary> /// The name of an external file containing the serialized object. /// </summary> public string FileName { get { return fileName; } set { fileName = value; } } /// <summary> /// Create an instance of this object. /// </summary> public ObjectConfigurationSection() : base() {} /// <summary> /// The contained object. /// </summary> public ConfigurationObject Data { get { return data; } set { data = value; } } #region Overrides /// <summary> /// Retrieves the contained object from the section. /// </summary> /// <returns>The contained data object.</returns> protected override object GetRuntimeObject() { SetWatcher(); return data; } #region Serialization /// <summary> /// Serializes the configuration section to an XML string representation. /// </summary> /// <param name="parentElement">The parent element of this element.</param> /// <param name="name">The name of the section.</param> /// <param name="saveMode">The mode to use for saving.</param> /// <returns>The string representation of the section.</returns> protected override string SerializeSection(ConfigurationElement parentElement, string name, ConfigurationSaveMode saveMode) { StringWriter sWriter = new StringWriter(System.Globalization.CultureInfo.InvariantCulture); XmlWriterSettings xSettings = new XmlWriterSettings(); xSettings.Indent = true; xSettings.IndentChars = "\t"; xSettings.OmitXmlDeclaration = true; XmlWriter xWriter = XmlWriter.Create(sWriter, xSettings); this.SerializeToXmlElement(xWriter, name); xWriter.Flush(); return sWriter.ToString(); } /// <summary> /// Serializes the section into the configuration file. /// </summary> /// <param name="writer">The writer to use for serializing the class.</param> /// <param name="elementName">The name of the configuration section.</param> /// <returns>True if successful, false otherwise.</returns> protected override bool SerializeToXmlElement(XmlWriter writer, string elementName) { if (writer == null) return false; bool success = true; if (fileName == null || fileName == string.Empty) { success = SerializeElement(writer, false); } else { writer.WriteStartElement(elementName); writer.WriteAttributeString("fileName", fileName); using (FileStream file = new FileStream(fileName, FileMode.Create, FileAccess.Write)) { XmlWriterSettings settings = new XmlWriterSettings(); settings.Indent = true; settings.IndentChars = ("\t"); settings.OmitXmlDeclaration = false; XmlWriter wtr = XmlWriter.Create(file, settings); success = SerializeElement(wtr, false); wtr.Flush(); wtr.Close(); } writer.WriteEndElement(); } return success; } /// <summary> /// Serilize the element to XML. /// </summary> /// <param name="writer">The XmlWriter to use for the serialization.</param> /// <param name="serializeCollectionKey">Flag whether to serialize the collection keys. Not used in this override.</param> /// <returns>True if the serialization was successful, false otherwise.</returns> protected override bool SerializeElement(XmlWriter writer, bool serializeCollectionKey) { if (writer == null) return false; XmlSerializer serializer = new XmlSerializer(data.GetType()); // Faking the existence of custom namespaces has a nice side effect // of leaving namespaces out entirely. XmlSerializerNamespaces faker = new XmlSerializerNamespaces(); faker.Add("", null); serializer.Serialize(writer, data, faker); return true; } #endregion #region Deserialization /// <summary> /// Deserializes the configuration section in the configuration file. /// </summary> /// <param name="reader">The reader containing the XML for the section.</param> protected override void DeserializeSection(System.Xml.XmlReader reader) { if (!reader.Read() || (reader.NodeType != XmlNodeType.Element)) { throw new ConfigurationErrorsException("Configuration reader expected to find an element", reader); } this.DeserializeElement(reader, false); } /// <summary> /// Deserializes the configuration element in the configuration file. /// </summary> /// <param name="reader">The reader containing the XML for the section.</param> /// <param name="serializeCollectionKey">true to serialize only the collection key properties; otherwise, false. /// Ignored in this implementation. </param> protected override void DeserializeElement(XmlReader reader, bool serializeCollectionKey) { reader.MoveToContent(); // Check for invalid usage if (reader.AttributeCount > 1) throw new ConfigurationErrorsException("Only a single type or fileName attribute is allowed."); if (reader.AttributeCount == 0) throw new ConfigurationErrorsException("A type or fileName attribute is required."); // Determine if we need to get the section from the inline XML or from an external file. fileName = reader.GetAttribute("fileName"); if (fileName == null) { DeserializeData(reader); } else { if (!reader.IsEmptyElement) throw new ConfigurationErrorsException("The section element must be empty when using the fileName attribute."); using (FileStream file = new FileStream(fileName, FileMode.Open, FileAccess.Read)) { XmlReader rdr = new XmlTextReader(file); rdr.MoveToContent(); DeserializeData(rdr); rdr.Close(); } } } #endregion #endregion #region Private Methods /// <summary> /// Deserializes the data from the reader. /// </summary> /// <param name="reader">The XmlReader containing the serilized data.</param> private void DeserializeData(System.Xml.XmlReader reader) { string typeName = reader.GetAttribute("type"); Type t = Type.GetType(typeName); //reader.Read(); reader.MoveToContent(); XmlSerializer serializer = new XmlSerializer(t); this.data = (ConfigurationObject)serializer.Deserialize(reader); } /// <summary> /// Determines if a FileSystemWatcher needs to be set on the file /// to watch for external changes. /// </summary> private void SetWatcher() { if (this.SectionInformation.RestartOnExternalChanges && fileName != null && fileName != string.Empty) { if (watcher == null) { FileInfo configFile = new FileInfo(fileName); watcher = new FileSystemWatcher(configFile.DirectoryName); watcher.Filter = configFile.Name; watcher.NotifyFilter = NotifyFilters.LastWrite; } watcher.Changed += new FileSystemEventHandler(OnConfigChanged); watcher.EnableRaisingEvents = true; } } /// <summary> /// Handle a change event from the FileSystemWatcher. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void OnConfigChanged(object sender, FileSystemEventArgs e) { watcher.EnableRaisingEvents = false; watcher.Changed -= new FileSystemEventHandler(OnConfigChanged); ConfigurationManager.RefreshSection(this.SectionInformation.Name); } #endregion } ConfigurationObject: public abstract class ConfigurationObject { [XmlAttribute("type")] [NoRegistry] public abstract string Type { get; set; } #region Save Method(s) public void Save(string sectionName) { Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); config.Sections.Remove(sectionName); ObjectConfigurationSection newSection = new ObjectConfigurationSection(); newSection.Data = this; newSection.SectionInformation.ForceSave = true; config.Sections.Add(sectionName, newSection); config.Save(ConfigurationSaveMode.Full); } public void Save(string sectionName, string fileName) { Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); config.Sections.Remove(sectionName); ObjectConfigurationSection newSection = new ObjectConfigurationSection(); newSection.Data = this; newSection.SectionInformation.ForceSave = true; config.Sections.Add(sectionName, newSection); config.SaveAs(fileName, ConfigurationSaveMode.Full); } public void SaveToCurrentUserRegistry(string subKeyPath) { using (RegistryKey key = Registry.CurrentUser.CreateSubKey(subKeyPath)) { foreach (PropertyInfo pInfo in this.GetType().GetProperties()) { if (pInfo.DeclaringType != this.GetType()) continue; object value = pInfo.GetValue(this, null); if (pInfo.GetCustomAttributes(typeof(NoRegistryAttribute), true).Length == 0 && pInfo.CanWrite && pInfo.CanRead) { if (pInfo.PropertyType == typeof(bool) || pInfo.PropertyType == typeof(string) || pInfo.PropertyType.IsSubclassOf(typeof(Enum))) { if (value != null) key.SetValue(pInfo.Name, value); else key.DeleteValue(pInfo.Name, false); } } } } } #endregion #region Load Method(s) public static ConfigurationObject Load(string sectionName) { return (ConfigurationObject)ConfigurationManager.GetSection(sectionName); } public static ConfigurationObject Load(string sectionName, string filename) { ExeConfigurationFileMap configFile = new ExeConfigurationFileMap(); configFile.ExeConfigFilename = filename; Configuration config = ConfigurationManager.OpenMappedExeConfiguration(configFile, ConfigurationUserLevel.None); ObjectConfigurationSection configSection = (ObjectConfigurationSection)config.Sections[sectionName]; ConfigurationObject data = (ConfigurationObject)configSection.Data; return data; } public void LoadFromCurrentUserRegistry(string subKeyPath) { using (RegistryKey key = Registry.CurrentUser.OpenSubKey(subKeyPath)) { if (key == null) return; foreach (string keyName in key.GetValueNames()) { try { string value = (string)key.GetValue(keyName); if (value == null) continue; PropertyInfo pInfo = this.GetType().GetProperty(keyName); if (pInfo != null && pInfo.GetCustomAttributes(typeof(NoRegistryAttribute), true).Length == 0) { if (pInfo.PropertyType == typeof(bool)) pInfo.SetValue(this, bool.Parse(value), null); else if (pInfo.PropertyType.IsSubclassOf(typeof(Enum))) pInfo.SetValue(this, Enum.Parse(pInfo.PropertyType, value), null); else pInfo.SetValue(this, value, null); } } catch (Exception) { } } } } #endregion } InheritedConfigurationSection: public class InheritedConfigurationSection : ObjectConfigurationSection { } InheritedConfigurationObject: [XmlRoot("InheritedObject")] public class InheritedObject : ConfigurationObject { private string _testValue = ""; public string TestValue { get { return _testValue ; } set { _testValue = value; } } #region Overrides / Overloads private string _type = null; [XmlAttribute("type")] [NoRegistry] public override string Type { get { if (_type == null) { string sType = this.GetType().AssemblyQualifiedName; int idx = sType.IndexOf(',', 0); _type = sType.Substring(0, sType.IndexOf(',', idx + 1)); } return _type; } set { } } #endregion } |
|||||||||||||||||||||||