Custom (inherited) ConfigurationSection saved using Configuration.Save() Problem! Please Help!

V

VaidTalon

I have taken a common Generic ConfigurationSection that was posted on
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

}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top