Brandon
Brandon

Reputation: 4593

Is it possible to create a ConfigurationElement from xml text?

We have a custom class that inherits from ConfigurationElement called SignalConfigurationElement and defines a bunch of properties using the ConfigurationProperty attribute.

The SignalConfigurationElement class is part of a much larger hierarchy of configuration elements and does not have a constructor. I'm able to retrieve the entire configuration through the ConfigurationManager.GetSection() call, providing the root element's name which is SystemConfiguration, which is defined in the <configSections> of the app.config file.

I don't have control over the custom configuration elements so I can't alter them to provide constructors. I also can't modify the app.config because it's used by a much larger application.

Is it possible to create instances or collections of SignalConfigurationElement given an XML string of <signals> entries? The class doesn't have a constructor so I'm assuming the ConfigurationManager.GetSection call used by our other applications to retrieve the entire configuration (not what I want) uses reflection to create its instances.

Code (Can't change any of this at all.):

App.Config

<configSections>
    <section name="SystemConfiguration" type="Fully.Qualified.Namespace.SystemConfiguration, SystemConfiguration"/>
</configSections>

<SystemConfiguration id="System1" name="System 1">
    <equipments>
      <clear />
      <add id="Equipment11" equipmentType="EQ">
        <signals>
          <clear />
          <add id="EQ11Signal1" signalId="EQ11Signal1" type="Version" />
          <add id="EQ11Signal2" signalId="EQ11Signal2" type="Status" />
          <add id="EQ11Signal3" signalId="EQ11Signal3" type="Status" />
        </signals>
      </add>
      <add id="Equipment21" equipmentType="EQ">
        <signals>
          <clear />
          <add id="EQ21Signal1" signalId="EQ21Signal1" type="Version" />
          <add id="EQ21Signal2" signalId="EQ21Signal2" type="Status" />
          <add id="EQ21Signal3" signalId="EQ21Signal3" type="Status" />
        </signals>
      </add>
     </equipments>
<!-- And a whole lot more. Somewhere in the avenue of 30,000 <signals> entries.-->
</SystemConfiguration>

Classes:

public class SystemConfigurationSection : ConfigurationSection
{
/// <summary>
/// Determines the XML tag that will contain this Configuration Section in an .config file.
/// </summary>
public const string SystemConfigurationSectionName = "SystemConfiguration";

/// <summary>
/// Instance factory method for an instance of the Configuration Section creation.
/// </summary>
/// <returns>
/// Instance of the System Configuration section created based on the .config file of the application.
/// </returns>
public static SystemConfigurationSection GetSystemConfigurationSection()
{
    SystemConfigurationSection result =
        (SystemConfigurationSection) ConfigurationManager.GetSection(SystemConfigurationSectionName);
    return result;
}

/// <summary>
/// Represents the XML attribute used to store ID of the System.
/// </summary>
[ConfigurationProperty(IdConfigurationElementName, IsRequired = true)]
public string Id
{
    get { return (string) this[IdConfigurationElementName]; }
    set { this[IdConfigurationElementName] = value; }
}

/// <summary>
/// Determines name of the XML attribute that will contain ID of the System.
/// </summary>
public const string IdConfigurationElementName = "id";

/// <summary>
/// Represents the XML attribute used to store Name of the System.
/// </summary>
[ConfigurationProperty(NameConfigurationElementName, IsRequired = true)]
public string Name
{
    get { return (string) this[NameConfigurationElementName]; }
    set { this[NameConfigurationElementName] = value; }
}

/// <summary>
/// Determines name of the XML attribute that will contain Name of the System.
/// </summary>
public const string NameConfigurationElementName = "name";

/// <summary>
/// Represents the XML attribute used to store Description of the System
/// </summary>
[ConfigurationProperty(DescriptionConfigurationElementName, IsRequired = false, DefaultValue = "")]
public string Description
{
    get { return (string) this[DescriptionConfigurationElementName]; }
    set { this[DescriptionConfigurationElementName] = value; }
}

/// <summary>
/// Determines name of the XML attribute that will contain Name of the System.
/// </summary>
public const string DescriptionConfigurationElementName = "description";

/// <summary>
/// Represents the collection of the System's Equipments as they are described in the .config file.
/// </summary>
[ConfigurationProperty(EquipmentsConfigurationElementCollectionName, IsDefaultCollection = false)]
[ConfigurationCollection(typeof (EquipmentConfigurationElementCollection), AddItemName = "add",
    ClearItemsName = "clear", RemoveItemName = "remove")]
public EquipmentConfigurationElementCollection Equipments
{
    get { return (EquipmentConfigurationElementCollection) base[EquipmentsConfigurationElementCollectionName]; }
}

/// <summary>
/// Determines name of the XML tag that will contain collection of the System's Equipments.
/// </summary>
public const string EquipmentsConfigurationElementCollectionName = "equipments";

}

 

/// <summary>
/// Extends the standard .Net ConfigurationElementCollection re-definind commectio manipulation members and making them strongly-typed.
/// </summary>
/// <typeparam name="TElementType">Type of the Configuration Elements that can be included into the collection.</typeparam>
public class ConfigurationElementCollectionBase<TElementType> : ConfigurationElementCollection, IEnumerable<TElementType>
where TElementType : ConfigurationElement, new()
{
/// <summary>
/// Makes the addition operation public.
/// </summary>
/// <param name="customElement">Configuration element to add to the collection.</param>
public virtual void Add(TElementType customElement)
{
    BaseAdd(customElement);
}

/// <summary>
/// Overrides the base implementation of the overloaded method masking an exception throwing.
/// </summary>
/// <param name="element">Configuration element to add.</param>
protected override void BaseAdd(ConfigurationElement element)
{
    BaseAdd(element, false);
}

/// <summary>
/// Overrides the base property hardcoding the returned value.
/// </summary>
public override ConfigurationElementCollectionType CollectionType
{
    get { return ConfigurationElementCollectionType.AddRemoveClearMap; }
}

/// <summary>
/// Overrides the base implementation of the instance factory method.
/// </summary>
/// <returns>A new instance of the Configuration Element type determined by the type parameter.</returns>
protected override ConfigurationElement CreateNewElement()
{
    return new TElementType();
}

/// <summary>
/// Overrides the base implementation of the method determining the indexing algorithm used in the collection.
/// </summary>
/// <param name="element">The configuration element to get index of.</param>
/// <returns></returns>
protected override object GetElementKey(ConfigurationElement element)
{
    return ((TElementType)element);
}

/// <summary>
/// Collection's element accessor by index property.
/// </summary>
/// <param name="index">Index of the desired element in the collection.</param>
/// <returns>The requested collection element if exists.</returns>
public TElementType this[int index]
{
    get { return (TElementType)BaseGet(index); }
    set
    {
        if (BaseGet(index) != null)
        {
            BaseRemoveAt(index);
        }
        BaseAdd(index, value);
    }
}

/// <summary>
/// Overrides the collection's element accessor by key property.
/// </summary>
/// <param name="id">Key of the desired collection element.</param>
/// <returns>The requested collection element if exists</returns>
public new TElementType this[string id]
{
    get { return (TElementType)BaseGet(id); }
}

/// <summary>
/// Implements a standard collection method looking for an element in the collection and returning the element's index if found.
/// </summary>
/// <param name="element">The element to look for.</param>
/// <returns>Index of the element in the collection if exists.</returns>
public int GetIndexOf(TElementType element)
{
    return BaseIndexOf(element);
}

/// <summary>
/// Implements a standard collection method removing an element from the collection.
/// </summary>
/// <param name="url">The element to be removed from the collection.</param>
public void Remove(TElementType url)
{
    if (BaseIndexOf(url) >= 0)
        BaseRemove(url);
}

/// <summary>
/// Implements the standard collection member removing an element from the collection by the element's index.
/// </summary>
/// <param name="index">Index of the element to be removed from the collection.</param>
public void RemoveAt(int index)
{
    BaseRemoveAt(index);
}

/// <summary>
/// Implements a standard collection method removing an element by the element's key.
/// </summary>
/// <param name="id">Key of the element to be removed from the collection.</param>
public void Remove(string id)
{
    BaseRemove(id);
}

/// <summary>
/// Implements the standard collection method that clears the collection.
/// </summary>
public void Clear()
{
    BaseClear();
}


public new IEnumerator<TElementType> GetEnumerator()
{
    for (int i = 0; i < Count; i++)
    {
        yield return this[i];
    }
}

 

public class EquipmentConfigurationElement : ConfigurationElement
{
    /// <summary>
    /// Represents the collection of the Equipment Unit's Signals as they are described in the .config file.
    /// </summary>
    [ConfigurationProperty(signalsConfigurationElementCollectionName, IsDefaultCollection = false)]
    [ConfigurationCollection(typeof(SignalConfigurationElementCollection), AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove")]
    public SignalConfigurationElementCollection Signals
    {
        get
        {
            return (SignalConfigurationElementCollection)base[signalsConfigurationElementCollectionName];
        }
    }
    /// <summary>
    /// Determines name of the XML tag that will contain collection of the Equipment Unit's Signals.
    /// </summary>
    private const string signalsConfigurationElementCollectionName = "signals";

}

 

/// <summary>
/// Represents a type-safe collection of Equipment Unit Configuration Elements.
/// </summary>
public class EquipmentConfigurationElementCollection : ConfigurationElementCollectionBase<EquipmentConfigurationElement>
{
}

 

/// <summary>
/// Represensts a Signal's configuration element
/// </summary>
/// <remarks>
/// As the class is derived from ConfigurationElementBase, a Signal's configuration element will expect "is", "name", and "description" XML attributes defined in the configuration file.
/// </remarks>
public sealed class SignalConfigurationElement : ConfigurationElement
{

    /// <summary>
    /// Represents an XML attribute used to determine type of the Signal.
    /// </summary>
    /// <remarks>
    /// The attribute is expected to have a string value which is equal to one of SignalType enumeration member names: "Status" or "Command".
    /// </remarks>
    [ConfigurationProperty(typeConfigurationElementName, IsRequired = false, DefaultValue = "status")]
    public string Type
    {
        get { return (string) this[typeConfigurationElementName]; }
        set { this[typeConfigurationElementName] = value; }
    }

    /// <summary>
    /// Determines name of the XML attribute that will contain type of the Signal.
    /// </summary>
    private const string typeConfigurationElementName = "type";
}

 

/// <summary>
/// Represents a type-safe collection of Signal Configuration Elements.
/// </summary>
public class SignalConfigurationElementCollection : ConfigurationElementCollectionBase<SignalConfigurationElement>
{
}

Upvotes: 2

Views: 1988

Answers (1)

dbc
dbc

Reputation: 116980

This is possible through the dark arts of reflection. Given the XML shown in your question, you can do this:

    var configXml = GetAppConfigXml(); // Returns the XML shown in your question.

    var config = (SystemConfigurationSection)Activator.CreateInstance(typeof(SystemConfigurationSection), true);

    using (var sr = new StringReader(configXml))
    using (var xmlReader = XmlReader.Create(sr))
    {
        if (xmlReader.ReadToDescendant("SystemConfiguration"))
            using (var subReader = xmlReader.ReadSubtree())
            {
                var dynMethod = config.GetType().GetMethod("DeserializeSection", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(XmlReader) }, null);
                dynMethod.Invoke(config, new object[] { subReader });
            }
    }
    Debug.Assert(config.Equipments.Count == 2); // No assert

Reference: source for ConfigurationSection.DeserializeSection.

Incidentally, the configuration classes attached to your question don't match the XML. Some properties are missing, and the properties that correspond to attributes in the XML must have IsKey = true. If the XML doesn't match the configuration classes, DeserializeSection will throw an exception.

Upvotes: 4

Related Questions