ConfigurationElementCollection with a number of ConfigurationElements of different type

4.2k views Asked by At

Is it possible to have a CollectionElementCollection with a number of different by type CollectionElements, e.g.:

<collection>
    <add type="MyType1, MyLib" Type1SpecificProp="1" />
    <add type="MyType2, MyLib" Type2SpecificProp="2" />
</collection

I have all classes required for such solution:

class MyCollection : ConfigurationElementCollection { }
class MyElement : ConfigurationElement { }
class MyType1 : MyElement { }
class MyType2 : MyElement { }
...
etc

but when I start my application I'm getting next predictable error:

Unrecognized attribute 'Type1SpecificProp'.

because Type1SpecificProp is defined in MyType1 not MyElement, especially if MyCollection has next method:

protected override ConfigurationElement CreateNewElement()
{
    return new MyElement(); // but I want instantiate not the base class but by a type given
}

i.e. returns base class thus OnDeserializeUnrecognizedAttribute() in child classed are never been called.

So the question is: how to let child classes to resolve unknown elements by their self?

3

There are 3 answers

2
abatishchev On BEST ANSWER

Microsoft.Practices.EnterpriseLibrary.Common.Configuration.PolymorphicConfigurationElementCollection<T> from EntLib5 do this job as a charm.

0
AudioBubble On

An instance of specific type (MyType1 and MyType2) needs to be created in order for child classes to resolve unknown elements or attributes by themselves.

Since the CreateNewElement method does not give any information on an element's attributes, that is not place where specific type instantiation can occur.

After some digging via Reflector, one comes to following partial call stack:

VariantCollection.MyCollection.CreateNewElement()
System.Configuration.ConfigurationElementCollection.CallCreateNewElement()
System.Configuration.ConfigurationElementCollection.OnDeserializeUnrecognizedElement(string elementName, XmlReader reader)

The OnDeserializeUnrecognizedElement method can be overriden in MyCollection class in order to create specific type instance. Instead of using the parameterless CallCreateNewElement method, use new one that receives XmlReader:

  1. Read attribute type (ensure its existence and validity).
  2. Create new element of specified type.
  3. Call internal virtual void AssociateContext( BaseConfigurationRecord configRecord ) method of System.Configuration.ConfigurationElement on the element.
  4. Call internal void CallInit() method of System.Configuration.ConfigurationElement on the element.
  5. Return the prepared element.

BTW, if there will not be too many different collection elements, consider using something like:

<myType1Collection>
    <add Type1SpecificProp="1" />
</myType1Collection>
<myType2Collection>
    <add Type2SpecificProp="2" />
</myType2Collection>

That way you can avoid casting the items from MyCollection to specific type.

2
user1088858 On

I looked into this as well. PolymorphicConfigurationElementCollection<T> seems deprecated. Edit: it's not, see the comment of 'abatishchev' below, i was just linking an old version.

The solution of Rest Wing was promising but unfortunately required invoking internal methods residing in another namespace. While this is possible via reflection it isn't going to receive prices for coding beauty in this case.

I digged into the source with Reflection too and came up with the following solution:

[ConfigurationCollection(typeof(ElementBaseConfig), CollectionType=ConfigurationElementCollectionType.BasicMap)]
public class MyTypesConfigCollection : ConfigurationElementCollection
{
    protected override ConfigurationElement CreateNewElement()
    {
        // Not used but function must be defined
        return null;
    }

    protected override object GetElementKey(ConfigurationElement element)
    {
        return element;
    }

    protected override ConfigurationElement CreateNewElement(string elementName)
    {
        switch (elementName)
        {
            case "mytype1":
                return new MyType1Config();

            case "mytype2":
                return new MyType2Config();

            default:
                throw new ConfigurationErrorsException(
                    string.Format("Unrecognized element '{0}'.", elementName));
        }
    }

    protected override bool IsElementName(string elementName)
    {
        // Required to be true
        return true;
    }

    public override ConfigurationElementCollectionType CollectionType
    {
        get { return ConfigurationElementCollectionType.BasicMap; }
    }
}

The override of the CollectionType is REQUIRED, even if this has been specified via the attribute in the top. When not overridden the base class' CollectionType still refers to 'AddRemoveClearMap' which isn't going to trigger the required 'CreateNewElement(string elementName)' function but it's parameterless variant 'CreateNemElement()'. For the same reason the overwritten IsElementName function should return true.

Note that I created a ElementBaseConfig which is the base class of both MyType1Config and MyType2Config in which you could define some shared attributes.