Polymorphic Configuration Element Collection

May 21, 2008 at 11:19 AM
Edited May 21, 2008 at 11:20 AM

I have worked with entlib 3.1 for some time now but now I need to implement something and I'm stuck somewhere in the middle.
I've got a configration section like the following:

<DataExchange enabled="true">
          <add name="FileLogHandler"
           handlerType="LogHandler.FileLogHandlerImpl, LogHandler"
           handlerDataType="LogHandler.FileLogHandlerData, LogHandler"

      <add name="EventLogHandler"
           handlerType="LogHandler.EventLogHandlerImpl, LogHandler"
           handlerDataType="LogHandler.EventLogHandlerData, LogHandler"

As you can see I need the polymorphic configuration for the handler collection.
I tried to mimic the Logging application block(listeners) the  and I created all the necessary clases for my configuration. The handlers have a start and a stop method.
A windows service is going to load them and call the corresponding start or stop on each handler.
Classes that I created match exactly the Logging Application Block listeners model:
    DataExchangeConfigSection inherits ConfigurationSection

The missing part now is how do I get the colletion of all handlers in the code.
Say I'm in the OnStart() method of the windows service, how do I get the collection of handlers in order to "for each" it and call the start method on each handler?

I hope I was clear on the requirement. A sample of polymorphic configuration would help me a lot.


May 21, 2008 at 2:55 PM
Edited May 21, 2008 at 2:56 PM


The collection of trace listeners is really not the best example for this if you're starting from scratch. The reason for this is that this collection needs to accommodate trace listeners that don't know about EntLib, so the mechanism is a bit more complex. Data isn't a very good example either.

Now, there are two separate aspects to deal with. One is to serialize and deserialize the collection of heterogenous configuration objects, the other is to translate configuration objects into runtime objects.

For serializing and deserializing the collection, you would:

  • Define a base configuration object class, like HashProviderData
  • Define a custom provider configuration object class, like CustomHashProviderData. This step is required by the next step, and is as simple as copying any custom provider data and change the parent class.
  • Create a collection property on the configuration section with type NameTypeConfigurationElementCollection using the two types above as the generic parameters. Eg.

   53         [ConfigurationProperty(hashProvidersProperty, IsRequired= false)]

   54         public NameTypeConfigurationElementCollection<HashProviderData, CustomHashProviderData> HashProviders

   55         {

   56             get { return (NameTypeConfigurationElementCollection<HashProviderData, CustomHashProviderData>)base[hashProvidersProperty]; }

   57         }   

  • Define the actual provider classes and their matching configuration objects, and annotate the provider classes with the ConfigurationElementType attribute pointing to the corresponding configuration object class, as in

   26     public class HashAlgorithmProviderData : HashProviderData


   28     [ConfigurationElementType(typeof(HashAlgorithmProviderData))]

   29     public class HashAlgorithmProvider : IHashProvider, IInstrumentationEventProvider





When it comes to actually creating the objects, a different mechanism kicks in. There is no support for creating top level collections of providers, but there is support for creating a single provider based on a configuration object. This mechanism relies on the Assembler attribute on each concrete configuration object pointing to an assembler type that creates the represented object. You have to define a subclass of AssemblerBasedObjectFactory with proper generic parameters. There's a AssemblerBasedCustomFactory, inheriting from AssemblerBasedObjectFactory, that adds the ability to look-up a configuration object given a name, but if you want to build a collection you don't need that additional behavior. You can use this factory class to create the objects for you by supplying the appropriate configuration objects, which you would likely retrieve from the collection in the configuration section. How to fit this factory in your block is really up to you, because it depends on the functionality provided by your block.

Sample code for the mechanism described above:

   25     [Assembler(typeof(HashAlgorithmProviderAssembler))]

   26     public class HashAlgorithmProviderData : HashProviderData


  105     public class HashAlgorithmProviderAssembler : IAssembler<IHashProvider, HashProviderData>

  106     {

  107         // snipped

  117         public IHashProvider Assemble(IBuilderContext context, HashProviderData objectConfiguration, IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache)

  118         {

  119             HashAlgorithmProviderData castedObjectConfiguration

  120                 = (HashAlgorithmProviderData)objectConfiguration;


  122             IHashProvider createdObject

  123                 = new HashAlgorithmProvider(

  124                     castedObjectConfiguration.AlgorithmType,

  125                     castedObjectConfiguration.SaltEnabled);


  127             return createdObject;

  128         }

  129     }

Having said this, you can use the App Block factory to create most of the code for you, including the design time support. While it will try to create a standard block, with factories to create a single provider, it's probably worth using.

Hope this helps,