How do I know what collections support , , ?

I recently received this question on the WCF Forums and promised a more in depth answer on my blog. For those of you that have read my forums response, a lot of this may seem familiar. In fact, I have copied much of this content from that response and filled it out here with some more details (essentially, the code snippets at the end).

First, understand that the behavior I describe below comes from System.Configuration and holds true for all .NET v2.0 configuration element collections.

 

The short answer to your question involves looking at the containing configuration element collection type. The <add>, <remove>, and <clear> element tags describe collection behaviors. In .NET v2.0 there are four predefined configuration element collection types: BasicMap, BasicMapAlternate, AddRemoveClearMap, and AddRemoveClearMapAlternate. For this discussion, consider BasicMap and BasicMapAlternate identical as well as AddRemoveClearMap and AddRemoveClearMap. (For those from the forums, I promised to describe the differences here, but have decided to dedicate a follow up post to configuration merging semantics.)

 

BasicMap collections do not support <remove> and <clear> tags, thus they can only include <add> tags. AddRemoveClearMap collections support all three tags. This begs the question, "How do I tell the difference?" Unforunately, you cannot from the configuration XML; however, you can from the configuration object model in code.

 

There are two different ways of getting a configuration element collection's collection type: The ConfigurationElementCollection.CollectionType property and the ConfigurationCollectionAttribute.CollectionType property. The first requires you have an instance of the specific ConfigurationElementCollection you wish to explore, while the second requires that you get the custom attributes off of the Type associated with the ConfigurationElementCollection. The second also requires that the developer that created the ConfigurationElementCollection you wish to inspect properly decorated their collection with the ConfigurationCollectionAttribute and kept the two in sync.

 

To add a bit more confusion into the mix, a configuration element collection can change the XML element names for any or all of the <add>, <remove>, and <clear> tags. You can see this in the WCF configuration when you look at bindings. In the following configuration snippet, the <binding> element really describes an <add> element where configuration element collection has changed the <add> element name to <binding>:

<configuration >
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name= "binding1" />
</basicHttpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Like the collection type, there are two different ways of getting a configuration element collection's XML element names. However, unlike the collection type, one is much (MUCH) easier. The ConfigurationElementCollection object contains three properties: AddElementName, RemoveElementName, and ClearElementName. However, all three of these properties are "protected internal", which means that using these properties requires utilizing Reflection. On the other hand, the ConfigurationCollectionAttribute has three public properties: AddItemName, RemoveItemName, and ClearItemsName. Again, the ConfigurationCollectionAttribute method requires that the developer that created the ConfigurationElementCollection you wish to inspect properly decorated their collection with the ConfigurationCollectionAttribute and properly set these values.

The code for these methods of inspection follow. First, let me include a single helper method that simply gets all types from an assembly (in this case System.ServiceModel.dll) that inherit from ConfigurationElementCollection:

        static List<Type> GetServiceModelConfigurationCollectionTypes()

        {

            List<Type> collectionTypes = new List<Type>();

            // Use the known public type System.ServiceModel.ServiceHost as an

            // anchor type from which to get a reference to the assembly.

            Assembly serviceModelDll = typeof(System.ServiceModel.ServiceHost).Assembly;

            foreach (Type type in serviceModelDll.GetTypes())

            {

                // Return all types that inherit from ConfiguraitonElementCollection.

                // Each method of inspection will filter out this list further

                // based on the limitations of that method.

                if (type.IsSubclassOf(typeof(ConfigurationElementCollection)))

                {

                    collectionTypes.Add(type);

                }

            }

  return collectionTypes;

        }

First, let me share the method of using the ConfigurationCollectionAttribute, the preferred method because of the simplicity of accessing this information. However, this requires the collection developer to properly decorate their collections (something I have done with the System.ServiceModel.Configuration namespace).

        static void DisplayCollectionPropertiesUsingAttributes()

        {

            List<Type> collectionTypes = Program.GetServiceModelConfigurationCollectionTypes();

            foreach (Type collectionType in collectionTypes)

            {

                // As long as a collection contains a ConfigurationCollectionAttribute,

                // this method will allow the appropriate inspection.

                object[] customAttributes = collectionType.GetCustomAttributes(typeof(ConfigurationCollectionAttribute), true);

                if (0 < customAttributes.Length)

                {

                    // Once acquiring the ConfigurationCollectionAttribute from the type,

                    // inspection is as easy as calling properties on the attribute.

                    ConfigurationCollectionAttribute collectionAttribute = (ConfigurationCollectionAttribute)customAttributes[0];

        Console.WriteLine("**************************************************");

                    Console.WriteLine("{0}", collectionType.FullName);

                    Console.WriteLine(" Collection Type: {0}", collectionAttribute.CollectionType);

                    Console.WriteLine(" Add Element: {0}", collectionAttribute.AddItemName);

                    Console.WriteLine(" Remove Element: {0}", collectionAttribute.RemoveItemName);

                    Console.WriteLine(" Clear Element: {0}", collectionAttribute.ClearItemsName);

                    Console.WriteLine(" Collection Item Type: {0}", collectionAttribute.ItemType.FullName);

                    Console.WriteLine("**************************************************");

                }

            }

        }

Next, let me share the brute force reflection method. Notice that this method requires instantiating the collection object, thus missing collections that have generic parameters (for example, all of the binding related collections in the System.ServiceModel.Configuration namespace).

        static void DisplayCollectionPropertiesUsingReflection()

        {

            List<Type> collectionTypes = Program.GetServiceModelConfigurationCollectionTypes();

            foreach (Type collectionType in collectionTypes)

            {

                // Since this method requires instantiating the collection, it cannot operate

                // on collections that are either abstract or contain generic parameters.

                if (!collectionType.IsAbstract &&

                    !collectionType.ContainsGenericParameters)

                {

                    // This method requires first instantiating an instance of the collection and

                    // getting PropertyInfo and MethodInfo references to call the non-public properties

                    // and methods.

                    ConfigurationElementCollection collectionInstance = (ConfigurationElementCollection)Activator.CreateInstance(collectionType);

                    PropertyInfo _addItemName = collectionType.GetProperty("AddElementName", BindingFlags.Instance | BindingFlags.NonPublic);

                    PropertyInfo _clearItemsName = collectionType.GetProperty("ClearElementName", BindingFlags.Instance | BindingFlags.NonPublic);

                    PropertyInfo _removeItemName = collectionType.GetProperty("RemoveElementName", BindingFlags.Instance | BindingFlags.NonPublic);

                    MethodInfo _createNewElement = collectionType.GetMethod("CreateNewElement",

                        BindingFlags.Instance | BindingFlags.NonPublic,

                        null,

                        new Type[0],

                        null);

                    // Next, this method retrieves the desired information through those PropertyInfo

                    // and MethodInfo references.

                    string addItemName = (string)_addItemName.GetValue(collectionInstance, null);

                    string clearItemsName = (string)_clearItemsName.GetValue(collectionInstance, null);

                    string removeItemName = (string)_removeItemName.GetValue(collectionInstance, null);

                    ConfigurationElement elementInstance = (ConfigurationElement)_createNewElement.Invoke(collectionInstance, null);

                    Console.WriteLine("**************************************************");

                    Console.WriteLine("{0}", collectionType.FullName);

                    Console.WriteLine(" Collection Type: {0}", collectionInstance.CollectionType);

                    Console.WriteLine(" Add Element: {0}", addItemName);

                    Console.WriteLine(" Remove Element: {0}", removeItemName);

                    Console.WriteLine(" Clear Element: {0}", clearItemsName);

                    Console.WriteLine(" Collection Item Type: {0}", elementInstance.GetType().FullName);

                    Console.WriteLine("**************************************************");

                }

            }

        }

I hope you find this at least somewhat helpful. I plan on following this post up next week with a post detailing the merge semantics of the WCF configuration collections. I hope that these two posts together help to give a more transparent picture of configuration collections in general, and specifically the WCF configuration collections.

Mark Gabarra

This posting is provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at http://www.microsoft.com/info/cpyright.htm