Q: Analogue of PolymorphicConfigurationElementCollection for single property?

Topics: Enterprise Library Core, General discussion
Jul 6, 2007 at 3:44 PM
Hello, guys.
Have anyone run into the issue to make polymorphic single property (i.e. not a collection).
For collection we have PolymorphicConfigurationElementCollection and its implementation NameTypeConfigurationElementCollection.
So I can define a property of configurable type that can have polymorphic behavior - i.e. a collection stores objects of different types and its types is determined by information in configuration source. It's great.
Look at an example.
a .config file:
<foo>
<sources>
<source type="MyApp.MyCustomSource1, MyApp" name="MyCustomSource1"/>
<source type="MyApp.MyCustomSource2, MyApp" name="MyCustomSource2"/>
</sources>
</foo>
A configuration type can look as:
public class FooData: ConfigurationSection // or ConfigurationElement, it doesn't matter here
{
private const String sourcesConfigPropertyName= "sources";
ConfigurationProperty(sourcesConfigPropertyName, IsRequired = true)
public SourcesDataCollection Sources
{
get { return (SourcesDataCollection ) thissourcesConfigPropertyName; }
set { thissourcesConfigPropertyName = value; }
}
}
and type for collection should be as:
ConfigurationCollection(typeof(SourcesData), AddItemName = "source")
public class SourcesDataCollection : PolymorphicConfigurationElementCollection<SourcesData>
{}
public class SourcesData: NamedConfigurationElement
{
// common data for all types of soures
}
The type SourcesData is a base type for all types which instances can be stored in an instance of SourcesDataCollection.

public class MyCustomSource1: SourcesData
{
// specific data
}

I specially drop all code for assemblers, attributes (ConfigurationElementType), etc. Mainly I want to show polymorphism of a collection property.

So, back to my question :) . I'd like to make the same thing for single property:
public class FooData: ConfigurationSection
{
private const String someConfigPropertyName= "some";
ConfigurationProperty(someConfigPropertyName, IsRequired = true)
public SomeSingleData SomeSingleProperty
{
get { return (SourcesDataCollection ) thissomeConfigPropertyName; }
set { thissomeConfigPropertyName = value; }
}
}
public class SomeSingleData: ConfigurationElement
{
private const String commonConfigPropertyName= "common-property";
ConfigurationProperty(someConfigPropertyName, IsRequired = true)
public String Common
{
get { return (SourcesDataCollection ) thiscommonConfigPropertyName; }
set { thiscommonConfigPropertyName = value; }
}
}

And I'd like to set a specific type for object - value of property in .config-file:
<foo>
<some type="MyApp.MySomeSingleObject1, MyApp">
<common-property/>
<special-property-for-MySomeSingleObject1/>
</some>
</foo>
public class MySomeSingleObject1: SomeSingleData
{...}

and in other .config-file we can have default type (SomeSingleData):
<foo>
<some>
<common-property/>
</some>
</foo>

So, the question is
Is it possible to make such thing?

Thanks you in advance.
Jul 6, 2007 at 5:24 PM
Hi,

There is no out of the box support for this, but it can be done; you'll need to write some code though. You basically need to manage the property referencing your base type by yourself (i.e. not adding the ConfigurationProperty attribute so the config subsystem doesn't "know" about it and calls you when deserializing) and override the OnDeserializeUnrecognizedElement callback to retrieve the type, just like the polymorphic collection does, initialize the child element and update the collection of properties.

I'm thinking on posting sample code, but that might take a couple of days.

Regards,
Fernando
Jul 9, 2007 at 10:51 AM
Thank you Fernando for your help.

I've written such code.
.config-file:
<configuration>
<configSections>
<section name="appconfig" type="PolymorphicSingleProp.XConfigurationData, PolymorphicSingleProp" />
</configSections>

<appconfig>
<single type="PolymorphicSingleProp.SingleDataEx, PolymorphicSingleProp" provider="provider" />
</appconfig>
</configuration>

// this type for ability to call DeserializeElement of ConfigurationElement (it's internal method, so we have to get proxy)
public class SerializableConfigurationElement: ConfigurationElement
{
public void DeserializeElement(XmlReader reader)
{
base.DeserializeElement(reader, false);
}
}

// this is my custom section implementation
public class XConfigurationData: ConfigurationSection
{
private const string singlePropertyName = "single";
private const string typeAttribute = "type";

// Formerly it had this attribute ConfigurationProperty(singlePropertyName, IsRequired = false)
public SingleData Single
{
get { return (SingleData) basesinglePropertyName; }
set { basesinglePropertyName = value; }
}

protected override bool OnDeserializeUnrecognizedElement(string elementName, System.Xml.XmlReader reader)
{
if (elementName == "single")
{
Type configurationElementType = RetrieveConfigurationElementType(reader);
SerializableConfigurationElement currentElement = (SerializableConfigurationElement)Activator.CreateInstance(configurationElementType);
currentElement.DeserializeElement(reader);
ConfigurationProperty property = new ConfigurationProperty(elementName, configurationElementType);
base.Properties.Add(property );

return true;
}
else
return base.OnDeserializeUnrecognizedElement(elementName, reader);
}

protected Type RetrieveConfigurationElementType(XmlReader reader)
{
Type configurationElementType = null;
if (reader.AttributeCount > 0)
{
for (bool go = reader.MoveToFirstAttribute(); go; go = reader.MoveToNextAttribute())
{
if (typeAttribute.Equals(reader.Name))
{
Type providerType = Type.GetType(reader.Value, false);
if (providerType == null)
break;

configurationElementType = providerType;
break;
}
}

if (configurationElementType == null)
{
throw new ConfigurationErrorsException("No type attribute");
}

reader.MoveToElement();
}
return configurationElementType;
}
}

public class SingleData: SerializableConfigurationElement
{
ConfigurationProperty("type", IsRequired=true)
public String Type
{
get{return (String) base"type";}
set{base"type" =value;}
}
}

public class SingleDataEx: SingleData
{
ConfigurationProperty("provider", IsRequired=true)
public String Provider
{
get{return (String) base"provider";}
set{base"provider" =value;}
}
}

It works :)
But if we want to reproduce this approach for many uses I see some problems..
First of all, in comparison with the polymorphic collection we have to our configuration element/section types to inherit some special type which implements basic logic of OnDeserializeUnrecognizedElement. And we have to write two such types: one derived from ConfigurationElement and the other derived from ConfigurationSection:

public class XConfigurationSection: ConfigurationSection
{}
public class XConfigurationSection: ConfigurationElement
{}
These types will have the method :
protected bool OnDeserializeUnrecognizedElementImpl(string elementName, System.Xml.XmlReader reader)
{
Type configurationElementType = RetrieveConfigurationElementType(reader);
SerializableConfigurationElement currentElement = (SerializableConfigurationElement)Activator.CreateInstance(configurationElementType);
currentElement.DeserializeElement(reader);
ConfigurationProperty property = new ConfigurationProperty(elementName, configurationElementType);
base.Properties.Add(property );

return true;
}
and an implementation of method RetrieveConfigurationElementType (I'll skip it, it's the same as in my first example).

So, when I want to create several elements with polymorphic single propertyes I'll write some such code:
public class XSubsystem1Data: XConfigurationSection
{
protected override bool OnDeserializeUnrecognizedElement(string elementName, System.Xml.XmlReader reader)
{
if (elementName == "property1" || elementName == "property2")
return OnDeserializeUnrecognizedElementImpl(elementName, reader);
else
return base.OnDeserializeUnrecognizedElement(elementName, reader);
}
}
public class XSubsystem2Data: XConfigurationElement
{
if (elementName == "property3")
return OnDeserializeUnrecognizedElementImpl(elementName, reader);
else
return base.OnDeserializeUnrecognizedElement(elementName, reader);
}

Ok. Of couse this code is not good as code with using polymorphic collection.

If you have any ideas how to make my examples better or correct any mistake feel free to do it :)