具有不同类型的ConfigurationElement的ConfigurationElementCollection

10

能否使用CollectionElementCollection来包含多种类型的CollectionElements,例如:

<collection>
    <add type="MyType1, MyLib" Type1SpecificProp="1" />
    <add type="MyType2, MyLib" Type2SpecificProp="2" />
</collection
我已经拥有实现这种解决方案所需的全部类:
class MyCollection : ConfigurationElementCollection { }
class MyElement : ConfigurationElement { }
class MyType1 : MyElement { }
class MyType2 : MyElement { }
...
etc

但是当我启动我的应用程序时,我遇到了以下可预测的错误:

Unrecognized attribute 'Type1SpecificProp'。

这是因为Type1SpecificProp定义在MyType1而不是MyElement中,特别是如果MyCollection具有以下方法:

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

换句话说,它返回基类,因此子类中的OnDeserializeUnrecognizedAttribute()永远不会被调用。

那么问题是:如何让子类自行解决未知元素?

3个回答

11

我也研究了这个问题。PolymorphicConfigurationElementCollection<T>似乎已经过时了

Rest Wing的解决方案很有前途,但不幸的是需要调用位于另一个命名空间中的内部方法。虽然可以通过反射实现,但在这种情况下,它不会因编码美观而受到好评。

我也使用了反射来查看源代码,并提出了以下解决方案:

[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; }
    }
}

即使已经在顶部通过属性指定了CollectionType,仍然需要覆盖CollectionType。如果不覆盖,基类的CollectionType仍然引用'AddRemoveClearMap',这不会触发所需的'CreateNewElement(string elementName)'函数,而是它的无参变体'CreateNemElement()'。出于同样的原因,被覆盖的IsElementName函数应该返回true。

请注意,我创建了一个ElementBaseConfig,它是MyType1Config和MyType2Config的基类,在其中可以定义一些共享属性。


2
你已经链接了3.1版本,这里是最新版本,并且不会过时。 - abatishchev
你说得对,我已经修改了我的回答以反映这一点。谢谢你的注意。 - user1088858

2
< p > Microsoft.Practices.EnterpriseLibrary.Common.Configuration.PolymorphicConfigurationElementCollection<T> 是 EntLib5 中的一个非常好用的工具。


我知道这个答案是一段时间前发布的,但如果您将其标记为被接受的答案,能否再详细说明一下? - goric
@goric:你需要继承它并覆盖RetrieveConfigurationElementType以在运行时替换类型。另请参阅NameTypeConfigurationElementCollection - abatishchev

1
需要创建特定类型的实例(MyType1MyType2),以便子类能够自行解析未知元素或属性。
由于CreateNewElement方法没有提供有关元素属性的任何信息,因此不应在该方法中进行特定类型实例化。
通过Reflector进行一些挖掘后,可以得到以下部分调用堆栈:
VariantCollection.MyCollection.CreateNewElement()
System.Configuration.ConfigurationElementCollection.CallCreateNewElement()
System.Configuration.ConfigurationElementCollection.OnDeserializeUnrecognizedElement(string elementName, XmlReader reader)

OnDeserializeUnrecognizedElement方法可以在MyCollection类中被重写,以创建特定类型的实例。不使用无参数的CallCreateNewElement方法,而是使用一个接收XmlReader的新方法:

  1. 读取属性type(确保其存在和有效性)。
  2. 创建指定类型的新元素。
  3. 对元素调用System.Configuration.ConfigurationElementinternal virtual void AssociateContext( BaseConfigurationRecord configRecord )方法。
  4. 对元素调用System.Configuration.ConfigurationElementinternal void CallInit()方法。
  5. 返回准备好的元素。

顺便说一下,如果不会有太多不同的集合元素,考虑使用类似以下的方法:

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

这样你就可以避免将MyCollection中的项目强制转换为特定类型。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接