As my previous article dealt on the .NET configuration system, so shall this one. For today, I’d like to talk about the concept and implementation of named configuration elements, and how they can make implementing your configuration infrastructure simpler.
What Are Named Configuration Elements?
Named configuration elements are configuration elements that use an assigned name as their keys. They are able to exist as part of a collection of other named configuration elements, and have children of their own as well.
The idea for named configuration elements comes from the way configuration is handled with Microsoft’s Enterprise Library. I very much enjoyed the way they had their configuration entities set up, and I decided the approach would be useful in contexts outside the library.
The following code shows what a base named configuration element looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | /// <summary> /// Provides a <see cref="ConfigurationElement"/> where its name is used as its key. /// </summary> public class NamedConfigurationElement : ConfigurationElement { /// <summary> /// The schema name for this element's identifying attribute which holds a literal name for /// this element. /// </summary> private const string NAME_ATTRIBUTE = "name" ; /// <summary> /// Gets or sets the identifying name for this configuration element. /// </summary> [ConfigurationProperty(NAME_ATTRIBUTE, IsKey= true , IsRequired= true )] [StringValidator(MinLength=1)] public string Name { get { return ( string ) base [NAME_ATTRIBUTE]; } set { base [NAME_ATTRIBUTE] = value; } } /// <summary> /// Creates a <see cref="ConfigurationProperty"/> object for the <see cref="Name"/> /// configuration property, to be used when creating this configuration element using /// the programmatic model. /// </summary> protected static ConfigurationProperty CreateNameProperty() { return new ConfigurationProperty(NAME_ATTRIBUTE, typeof ( string ), String.Empty, null , null , ConfigurationPropertyOptions.IsKey | ConfigurationPropertyOptions.IsRequired); } } |
Let’s take a moment to go over what I’m doing here.
You may notice immediately that this class is not abstract
. Thus, while it is very much intended for more complex elements to derive from this class, it can also be used as it is in its current form.
Because this class is meant to be used as a base for other elements, however, it needs to support both coding models used to define a configuration infrastructure. More information regarding the two different configuration coding models can be found in my previous article.
Thus, you’ll see that the lone public property (Name
), is decorated with an attribute. If a subclass is derived from this class, then, and the developer wishes to use the declarative coding model, then they will be able to do so successfully, given the fact that the base properties are attributed.
Take note, however, that decorating the property with an attribute does not automatically imply that the declarative model must or even will be used. If a subclass of this class wishes to use the programmatic model instead, then they would be able to do so with no problem.
The only thing that could come up as a problem if the programmatic coding model was used, however, is that they would need to provide an initialized ConfigurationProperty
object in their overridden ConfigurationPropertyCollection
member. There’s a good chance they might not have the source for this class in front of them, and we don’t want them to have to break out any sort of source review tool either.
Therefore, a static helper method is provided in the form of the CreateNameProperty
. When a subclass is initializing their property collection, they can simple call this method, and they will have the initialized ConfigurationProperty
object that they require.
Named Element Collections
While named configuration elements are nice and all, they aren’t very useful if we don’t have the infrastructure that can host them in a collection. That’s where the NamedElementCollection<TElement>
type comes into play.
Let’s take a look at what this collection class looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | /// <summary> /// Provides a collection of <see cref="NamedConfigurationElement"/> objects. /// </summary> /// <typeparam name="TElement"> /// The type of configuration element contained by the collection. /// </typeparam> [ConfigurationCollection( typeof (NamedConfigurationElement))] public class NamedElementCollection<TElement> : ConfigurationElementCollection where TElement : NamedConfigurationElement, new () { /// <inheritdoc/> protected override ConfigurationElement CreateNewElement() { return new TElement(); } /// <inheritdoc/> protected override object GetElementKey(ConfigurationElement element) { TElement typedElement = element as TElement; // If this returns null, the built-in class this derives from will throw and exception // anyway, complaining about the wrong type. So we let that handle it. return null == typedElement ? null : typedElement.Name; } } |
This rather simple class is all we need then to put these named configuration elements to use. In my actual code, I have this type derived from a more specialized base class (ConfigurationElementCollection<TElement, TKey>
), which provides some more functionality as well as implementing the IEnumerable<T>
interface, however we don’t need that for the purposes of this article.
You’ll notice that the class itself is attributed with a ConfigurationCollectionAttribute
. The documentation available from Microsoft typically shows that attribute being used where the type is being declared as a member, not on the class itself.
Well, interestingly enough, Microsoft almost always decorates the custom collection classes at their definition, like how I am doing it, as opposed to the former method. By doing it at the class level, it seems that we can alleviate the people putting to use from having to fulfill on that requirement; however, they certainly can re-declare the attribute on the member if they need to change its configuration.
Putting It to Use (Declaratively)
Let’s quickly design a configuration infrastructure using these elements, and then see what the resulting configuration would look like.
This infrastructure I want is ultimately going to use the programmatic model, however, let’s first take a look at one designed to use the declarative model. One benefit of using the declarative model is I don’t need to do subclass my NamedConfigurationElement
class if I don’t need to add any additional functionality to it.
So, let’s skip straight to our custom configuration section class using the declarative model:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | /// <summary> /// Provides a configuration section for hosting a number of configured categories used by the /// application somehow. /// </summary> public sealed class CategoriesSection : ConfigurationSection { /// <summary> /// The schema name for the child collection element containing all of the categories. /// </summary> private const string CATEGORIES_CHILD = "categories" ; /// <summary> /// Gets the collection of categories. /// </summary> [ConfigurationProperty(CATEGORIES_CHILD)] public NamedElementCollection<NamedConfigurationElement> Categories { get { return (NamedElementCollection<NamedConfigurationElement>) base [CATEGORIES_CHILD]; } } // I normally have some additional helper methods here for every section I write, but they don't // affect the topic at hand. } |
And that’s it! We don’t have to do anything else. Let’s look at what our configuration in XML would look like, assuming the name for our section is declared as “categoriesSection”:
1 2 3 4 5 6 | <categoriesSection> <categories> < add name= "FirstCategory" /> < add name= "SecondCategory" /> </categories> </categoriesSection> |
Pretty slick.
Putting It to Use (Programmatically)
Let’s design our infrastructure using the programmatic model instead. Using the programmatic model requires us to create a subclass for each individual element, as they won’t override their ConfigurationPropertyCollection
on their own.
Because our configuration is intended to host category elements, and because we’re using the programmatic model, we’ll need to define aCategoryElement
class. Since we’re going ahead and defining a class, let’s add an additional capability to these category elements in that each one can host one or more subcategories.
Next, let’s look at our CategoryElement
class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | /// <summary> /// Provides a configuration element for a category. /// </summary> public sealed class CategoryElement : NamedConfigurationElement { /// <summary> /// The schema name for the child collection containing all of the subcategories. /// </summary> private const string SUBCATEGORIES_CHILD = "subCategories" ; private static readonly Lazy<ConfigurationPropertyCollection> _Properties = new Lazy<ConfigurationPropertyCollection>(InitializeProperties, LazyThreadSafetyMode.PublicationOnly); /// <summary> /// Gets the collection of subcategories. /// </summary> public NamedConfigurationElement<CategoryElement> Subcategories { get { return (NamedConfigurationElement<CategoryElement>) base [SUBCATEGORIES_CHILD]; } } /// <inheritdoc/> protected override ConfigurationPropertyCollection Properties { get { return _Properties.Value; } } /// <summary> /// Creates a <see cref="ConfigurationPropertyCollection"/> object containing all configuration /// properties belonging to this element. /// </summary> private static ConfigurationPropertyCollection InitializeProperties() { return new ConfigurationPropertyCollection { new ConfigurationProperty(SUBCATEGORIES_CHILD, typeof (NamedConfigurationElement<CategoryElement>), null , ConfigurationPropertyOptions.None), CreateNameProperty() }; } } |
Very simple, not much is needed to explain here, although if you’re asking any questions about any particular approach made above, I’d refer you to my previous article. Let’s then finally look at our CategoriesSection
, which has been modified to use the programmatic model:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | /// <summary> /// Provides a configuration section for hosting a number of configured categories used by the /// application somehow. /// </summary> public sealed class CategoriesSection : ConfigurationSection { /// <summary> /// The schema name for the child collection element containing all of the categories. /// </summary> private const string CATEGORIES_CHILD = "categories" ; private static readonly Lazy<ConfigurationPropertyCollection> _Properties = new Lazy<ConfigurationPropertyCollection>(InitializeProperties, LazyThreadSafetyMode.PublicationOnly); /// <summary> /// Gets the collection of categories. /// </summary> public NamedElementCollection<NamedConfigurationElement> Categories { get { return (NamedElementCollection<NamedConfigurationElement>) base [CATEGORIES_CHILD]; } } /// <summary> /// Creates a <see cref="ConfigurationPropertyCollection"/> object containing all configuration /// properties belonging to this element. /// </summary> private static ConfigurationPropertyCollection InitializeProperties() { return new ConfigurationPropertyCollection { new ConfigurationProperty(CATEGORIES_CHILD, typeof (NamedElementCollection<NamedConfigurationElement>), null , null , null , ConfigurationPropertyOptions.None) }; } // I normally have some additional helper methods here for every section I write, but they don't // affect the topic at hand. } |
Not too much needed to be added here, just the necessary infrastructure for overriding the Properties
method.
Let’s then wrap up things by taking a look at what the configuration may look like:
1 2 3 4 5 6 7 8 9 10 11 | <categoriesSection> <categories> < add name= "FirstCategory" > <subCategories> < add name= "Frisky" /> < add name= "EasilyJealous" /> </subCategories> </ add > < add name= "SecondCategory" /> </categories> </categoriesSection> |
Very nice, in my opinion.