问题
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?
回答1:
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.
回答2:
Microsoft.Practices.EnterpriseLibrary.Common.Configuration.PolymorphicConfigurationElementCollection<T>
from EntLib5 do this job as a charm.
回答3:
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
:
- Read attribute
type
(ensure its existence and validity). - Create new element of specified type.
- Call
internal virtual void AssociateContext( BaseConfigurationRecord configRecord )
method ofSystem.Configuration.ConfigurationElement
on the element. - Call
internal void CallInit()
method ofSystem.Configuration.ConfigurationElement
on the element. - 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.
来源:https://stackoverflow.com/questions/4902461/configurationelementcollection-with-a-number-of-configurationelements-of-differe