Configuration Section Handlers

(by John Roberts for Blayd Software)

Note: this article relates to the .Net Framework version 2.0, some of the classes and class members referenced in this article were not available in earlier framework versions.

Introduction

Configuration settings for .Net Framework applications are stored, unsurprisingly, in configuration files, the settings for a Windows Forms application are stored in an app.config file and the settings for an ASP.Net web application are stored in (one or more) web.config files. In the case of a Windows Forms application, the "app" part of the configuration file name represents the name of the application's assembly, therefore the configuration file for an assembly named NumberCruncher.exe would be named NumberCruncher.exe.config. In ASP.Net web applications the configuration file is literally named web.config for all web applications, regardless of the name of the compiled web application's assembly.

Configuration settings are stored as XML, therefore, configuration files have a hierarchical structure. The configuration data is stored in a section element, which may or may not contain child section elements, a sectionGroup element may, optionally, be used to organise configuration section elements. Before a sectionGroup or section element can be used in a configuration file to store configuration data it has to be declared within a configSections element, if a sectionGroup or section is not declared it will not be recognised by the configuration system. A sectionGroup or section element can be declared in the machine.config file's configSections element if it is to be used by more than one application or it can be declared in the app.config file of the relevant application.

The configuration settings data contained in section elements is loaded by the .Net Framework configuration system using configuration section handler classes. A configuration section is associated with a specific configuration section handler class in the type attribute of the section's declaration in the configSections section. A configuration section element is associated with a single configuration section handler class but different instances of the same configuration section handler class may be associated with many configuration section elements. The .Net Framework configuration system uses the section element's name to access the data in the section's declaration, in the configSections section, to determine and initialize the relevant configuration section handler class and then instructs the handler to load the section's configuration settings data.

The configuration section handler classes that ship with the .Net Framework are contained in the System.Configuration namespace. Developers can use the configuration section handler classes defined in this namespace to load specific kinds of configuration section data from their configuration files or they can develop their own, custom, configuration section handler classes by extending the base classes contained in the System.Configuration namespace. Prior to version 2.0 of the .Net Framework, a configuration section handler class had to implement the IConfigurationSectionHandler interface. However the IConfigurationSectionHandler interface has now been deprecated, although it still remains in the framework as it is used internally. For .Net Framework version 2.0 and above the ConfigurationSection abstract base class must be extended to provide a custom configuration section handler. Extending the ConfigurationSection base class and using either the declarative (attribute) model or the programmatic model to define configuration section attributes is much more straight forward than implementing the IConfigurationSectionHandler interface used to be, because, amongst other things, it does not require any XML parsing in the derived class.

In this article we will be concentrating mainly on developing custom configuration section handlers, however, we will briefly look at some of the handlers that ship with the .Net Framework and also describe the settings required when declaring a configuration section handler.

.Net Framework Built-in Configuration Section Handlers

AppSettingsSection Configuration Section Handler

The configuration file section that is probably most familiar to .Net Framework developers is the appSettings section. This configuration section is easily accessible in code from the static ConfigurationManager.AppSettings property which returns a NameValueCollection derived class instance containing the key and value of each entry in the appSettings configuration section. The actual Type of the value returned from the AppSettings property is KeyValueInternalCollection which is an internal type that derives from the public NameValueCollection type. The AppSettings property casts this internal type to NameValueCollection and therefore it is safe to retrieve the value of the AppSettings property directly into a NameValueCollection field or local variable.

ExpandAn example of an appSettings configuration section:

The System.Configuration.AppSettingsSection class is declared as the configuration section handler for the appSettings configuration section in the machine.config file. Because, by default, the appSettings configuration section declaration is inherited by all app.config files on the machine you do not have to include a configSections element or an appSettings section declaration in your app.config or web.config file. If your configuration requirements are fairly straight forward, the appSettings section may be all you need, however, as it does not have a way of grouping settings, the appSettings section can become messy and confusing if you have a lot of configuration settings with groups of settings referring to different areas of functionality within your application.

NameValueSectionHandler Configuration Section Handler

The example appSettings section above stores the fictitious settings for logging into an SMTP server to send email from the application and looks fine, however, when we add the settings for just one of the email types that we want to send from the application things start to get confusing.

ExpandAn example of an appSettings configuration section that stores multiple groups of data:

The example appSettings section now stores the data required to login to the SMTP server and the data required to send one specific type of email. We now have two distinct groups of configuration data in a single configuration section. As the application develops it is likely that we will want to add even more configuration settings and therefore we will end up with a single configuration section that is, potentially, confusing and difficult to maintain. A far better solution would be to have a configuration section for each group of settings data. We could still use the appSettings section for the login settings, for example and then use a different section for the email types, however, in this example we are going to use a configuration section for each group of settings.

ExpandAn example of an app.config file with two, user defined, configuration sections:

The app.config file now contains a configSections element in which we have declared two configuration sections, smtpHostSettings and contactUsSettings, for details of the available section element's attributes see the Declaring a Custom Configuration Section and Handler section below. The NameValueSectionHandler class is declared as the configuration section handler for both of the sections and because this is an application specific declaration we have to include the declaration in the app.config file. If we do not make the declaration here the two, custom, configuration sections will not be recognised by the configuration system and we will get an exception when the configuration data is loaded. The NameValueSectionHandler class, because it has been around for a while, implements the IConfigurationSectionHandler interface, rather than extend the ConfigurationSection base class, which also illustrates that, although depreciated, the interface is still present and can still be used, however, we recommend that you do not use depreciated types in new development projects.

ExpandRetrieving the settings from a user defined configuration section:

Accessing the settings data in the custom sections is fairly straight forward although, as with accessing any external data, you should code defensively when reading configuration settings, you can't be certain that the values are present in the configuration file and you can't be certain that they will contain what they should. The ConfigurationManager.GetSection method takes the name or the path of the configuration section, in the case of the example above the name of the section works fine, however, if the section was contained within a group, for example, we would need to specify the section's path e.g. "groupName/sectionName". The value returned from the GetSection method has to be cast to the relevant type, in the case of the example above we are casting to NameValueCollection as this is the base type returned when using the NameValueSectionHandler, the actual type returned is a private ReadOnlyNameValueCollection instance, however, we need to use its public, inherited NameValueCollection properties in order to access it. How do we know that the NameValueSectionHandler results in a NameValueCollection derived class being returned from the GetSection method? Good question, basically it is matter of trial and error, i.e. write the code generically using an Object as the return Type and then examine the return value in the debugger!. When using a configuration section handler that derives from the ConfigurationSection base class you can cast to the Type of the derived class, we will be discussing and demonstrating the use of ConfigurationSection derived section handlers later in the article. A cast is always required and therefore, if you don't want an exception, you should code defensively by using a conditional casting operation i.e. the as operator in C# or the TryCast function in VB. When we have the values from the configuration section it is then just a matter of retrieving each setting. In the example, we are retrieving the settings from the collection, specifying the key of each setting, when using a ConfigurationSection derived section handler class the settings are represented by the properties of the section handler class. Having seen how to use a configuration section handler that implements the (depreciated) IConfigurationSectionHandler interface and hinted about the alternative approach offered by the ConfigurationSection base class you are probably starting to appreciate why an alternative approach was required. However, we have a bit more ground to cover before we get to the topic of developing your own configuration section handler using the ConfigurationSection base class.

ConnectionStringsSection Configuration Section Handler

As mentioned earlier in this section the ConfigurationManager provides a convenience static property, AppSettings, that can be used to retrieve the appSettings configuration section data. ConfigurationManager also provides a static ConnectionStrings property that can be used to retrieve the connectionStrings configuration section data. The connectionStrings section is associated with the ConnectionStringsSection configuration section handler. The ConnectionStringsSection class is declared as the configuration section handler for the connectionStrings configuration section in the machine.config file. Because, by default, the connectionStrings configuration section declaration is inherited by all app.config files on the machine you do not have to include a configSections element or a connectionStrings section declaration in your app.config or web.config file.

The ConfigurationManager.ConnectionStrings property returns a ConnectionStringSettingsCollection instance which contains a ConnectionStringSettings instance for each connectionString section defined in the connectionStrings section of the configuration file. Retrieving the connection string settings is just a matter of accessing the relevant properties of the relevant ConnectionStringSettings instance.

If you need to specify database connection strings in your configuration file you may well have to include login details, including a password, in your connection string. We know that the recommended strategy is to use integrated security when accessing a database, however, out here in the real world that is not always possible. For example, if you have a website, using anonymous access, that requires a connection to a database, integrated security may not be practical or may not be supported by your hosting package, therefore, you will have to include the database user ID and password in the connection string. Even the simple email example used earlier in this section requires a password to be stored in the configuration file. Sensitive data such as passwords really should be protected when it is stored in a configuration file. The .Net Framework configuration system includes functionality to protect or encrypt sensitive configuration settings data and will automatically decrypt the data as it is loaded and before it is passed to the relevant configuration section handler. The encryption of configuration settings does not relate directly to the topic of this article and won't be covered here as we don't really have the space to do justice to the topic. If you require further information about encrypting configuration settings, you should consult the .Net Framework documentation or read the following Blayd Software article: Protected Configuration.

IgnoreSection Configuration Section Handler

The final built-in configuration section handler that we are going to discuss is the IgnoreSection configuration handler. When the .Net Framework configuration system processes a configuration file it completely parses the file and will throw an exception if it finds a configuration section that has not be declared in the configSections section of the current configuration file or one of the files above the current file in the configuration file inheritance chain. The IgnoreSection configuration section handler can be used to instruct the .Net Framework configuration system to bypass i.e. not to load and parse the configuration section that is associated with the IgnoreSection handler. To get the configuration system to ignore a configuration section you still have to declare the section in the configSections element, however, you associate the section with the IgnoreSection handler class to ensure that the configuration system bypasses the section and does not throw an exception during the parsing operation.

If you instruct the configuration system to ignore a section in the configuration file then you will have to load and parse the section and assign the configuration settings appropriately from your own code in order to make use of the configuration settings. The .Net Framework configuration system is very flexible and extensible in most areas, this article, for example, is discussing one example of the configuration system's extensibility, creating your own configuration section handlers, you can also create your own settings provider and protected configuration provider if your requirements cannot be fulfilled by the built-in providers. Ultimately however, there may be occasions when you want to or have to load and parse a configuration section directly from your own code and the IgnoreSection configuration section handler facilitates this by allowing you to instruct the .Net Framework configuration system to bypass or ignore the relevant section without throwing an exception.

Declaring a Custom Configuration Section and Handler

In order to include a user defined configuration section with a standard or custom configuration section handler in a configuration file you first have to declare the section and associate it with a configuration section handler in the configSections section element of the machine.config file, if the section is to be made available to all applications on the machine or the relevant app.config or web.config file.

ExpandTo declare a user defined configuration section, create a configSections section element in the relevant configuration file, if one does not already exist, then add a section element as follows:

A user defined configuration section is declared using the available attributes of the section element. The available attributes, their use and possible values are described below, you should note that all attribute values should be assigned as quoted strings, therefore, for example, a Boolean attribute has two possible values "true" or "false". In addition, attribute names and their values are case sensitive.

allowDefinition
The allowDefinition attribute is optional and only applies to ASP.Net applications. The attribute specifies which configuration file or files the section can be used in, the possible values are as follows:
Everywhere
Allows the section to be used in any configuration file. This is the default value i.e. if the allowDefinition attribute is not specified Everywhere is assumed.
MachineToApplication
Allows the section to be used in the following configuration files: machine.config, the root web.config file or the web.config file for an application.
Note: Both the machine.config and the root web.config files are located in the following folder:
%SystemRoot%\Microsoft.NET\Framework\versionNumber\Config
MachineToWebRoot
Allows the section to be used in the machine.config or the root web.config file.
MachineOnly
Allows the section to be used only in the machine.config file.
allowExeDefinition
The allowExeDefinition attribute is optional and only applies to Windows Forms applications. The attribute specifies which configuration file or files the section can be used in, the possible values are as follows:
MachineToApplication
Allows the section to be used in either the machine.config or the app.config file. This is the default value i.e. if the allowExeDefinition attribute is not specified MachineToApplication is assumed.
MachineToRoamingUser
Allows the section to be used in the following configuration files: machine.config, app.config or the user.config file in the roaming user folder.
MachineToLocalUser
Allows the section to be used in the following configuration files: machine.config, app.config or the user.config file in either the local or roaming user folder.
MachineOnly
Allows the section to be used only in the machine.config file.
allowLocation
The allowLocation (Boolean) attribute is optional and only applies to ASP.Net applications. The attribute specifies whether the section can be used within the location element. The default value is "true".
name
The name attribute is mandatory and specifies the name of the configuration section element i.e. the section element name as it appears in the body of the configuration file.
restartOnExternalChanges
The restartOnExternalChanges (Boolean) attribute is optional and only applies to Windows Forms applications. The attribute specifies whether the application should restart if the data for the configuration section changes. The default value is "true".
type
The type attribute is mandatory and specifies the assembly qualified name of the configuration section handler class. The value takes the following form:
Fully qualified class name, assembly file name, version, culture, public key token
The configuration section handler class may be in the assembly that is loading the configuration or it may be in an external assembly. If the configuration section handler class is in an external assembly the .Net Framework runtime must be able to locate the assembly. Place an external assembly in the same folder as the configuration file (the bin folder for an ASP.Net application), the Global Assembly Cache or the %SystemRoot%\Microsoft.NET\Framework\version folder for the machine.config or root web.config files.

Custom Configuration Section Handlers

If none of the built-in .Net Framework configuration section handler classes meet your requirements you can design your own configuration section structure and then develop a section handler class to process the section's data at runtime. As described earlier in this article, the .Net Framework's configuration system associates a configuration section with an XML element in the configuration file, in addition, a configuration section may have one or more child XML elements. The ConfigurationSection abstract base class should be extended to provide a configuration section handler, an instance of the handler class maps to the relevant configuration section XML element. The name of the section handler class does not have to match the name of the relevant XML element, as a section handler class relates to a particular type of configuration section structure rather than a specific configuration section, the association with a specific configuration section is made in the configSections declaration. A configuration section element, represented by a class derived from the ConfigurationSection class, may have one or more child XML elements, each represented by a class that derives from the ConfigurationElement abstract base class. The ConfigurationSection class itself derives from the ConfigurationElement class and can, therefore, be thought of as specialized version of a configuration element i.e. a section element. However, in reality, a section element can be represented by a single, simple, XML tag or it can have a more complex structure containing other section and/or single XML elements. The actual configuration settings data in a configuration section is represented by one or more instances of the ConfigurationProperty class stored in the ConfigurationElement instance's ConfigurationPropertyCollection (known as the property bag). Individual configuration properties can be accessed, from a derived class, using the protected ConfigurationElement indexer (Item) property or the entire collection can be accessed using the protected ConfigurationElement.Properties property. A ConfigurationProperty instance may represent an attribute of a configuration section or a simple XML element or it may represent a child XML element or it may represent a collection of child XML elements. To allow calling code to get and optionally set the configuration property values, the ConfigurationSection derived class should have properties that wrap the underlying ConfigurationProperty instances. For example, to implement a configuration property named "attOne" the ConfigurationSection derived custom section handler class would create, either declaratively or programmatically, a ConfigurationProperty instance named "attOne" and would also implement a public property named "AttributeOne" to get and optionally set the value of the underlying ConfigurationProperty instance. We will use the simple configuration section below to try to make sense of all this and to clarify which class is used to represent which configuration entity.

ExpandConfiguration section example:

In the example above, the customSection XML element would be represented by a section handler class derived from the ConfigurationSection class. The handler class would have three properties, each represented by an underlying instance of the ConfigurationProperty class. Two of the properties would represent the attributes of the customSection XML element, attOne and attTwo, one property would represent the customElement child XML element. The three ConfigurationProperty instances, representing the configuration section properties would be contained in the (inherited) ConfigurationPropertyCollection of the ConfigurationSection derived class. The customElement child XML element would be represented by a class derived from the ConfigurationElement class and would have two properties, each represented by an underlying instance of the ConfigurationProperty class. The properties would represent the two attributes of the customElement XML element, attThree and attFour. The two ConfigurationProperty instances, representing the attributes of the customElement XML element would be contained in the (inherited) ConfigurationPropertyCollection of the ConfigurationElement derived class.

A configuration section that contains more than one child XML element can be handled by extending the ConfigurationElementCollection abstract base class. By default, classes that extend the ConfigurationElementCollection base class represent a collection of ConfigurationElement instances implemented with add, remove and clear directives that are used to modify any inherited properties and specify new ones.

The .Net Framework provides two options for developing a custom configuration section handler, the declarative or attribute model and the programmatic model. Both models require the development of a ConfigurationSection derived handler class. The declarative model requires you to decorate your handler's properties with the relevant attributes to instruct the .Net Framework configuration system to create the actual ConfigurationProperty instances. The programmatic model requires you to create the actual ConfigurationProperty instances in the handler's code. The declarative model is probably the simplest way to create a custom configuration section handler, however, the programmatic model offers more control and flexibility. We will examine and demonstrate both models in the next two sections of this article.

Whichever model you choose to implement you should take advantage of the classes contained in the System.Configuration namespace to validate your configuration property settings. Validation of configuration properties can be implemented either with attributes or by directly assigning a validation class instance when initializing an instance of the ConfigurationProperty class. The use of validation attributes is limited to the declarative model, validation attributes do not work on property wrappers implemented using the programmatic model.

Configuration property validation classes:

IntegerValidator and IntegerValidatorAttribute
Provides validation of an Int32 configuration property value. Checks an integer against a specified minimum and maximum value. The range can be specified as either inclusive i.e. the specified value must be between the specified minimum and maximum or exclusive i.e. the specified value must not be between the specified minimum and maximum.
LongValidator and LongValidatorAttribute
Provides validation of an Int64 configuration property value. Checks a long integer against a specified minimum and maximum value. The range can be specified as either inclusive i.e. the specified value must be between the specified minimum and maximum or exclusive i.e. the specified value must not be between the specified minimum and maximum. When using LongValidator you can also specify a resolution value that must be matched for the validation to pass.
RegexStringValidator and RegexStringValidatorAttribute
Provides validation of a String configuration property value based on the rules provided by a regular expression.
StringValidator and StringValidatorAttribute
Provides validation of a String configuration property value. Checks either the minimum length or the minimum and maximum length of the specified value. You can also specify a string of characters that are not allowed in the specified value i.e. invalid characters.
TimeSpanValidator and TimeSpanValidatorAttribute
Provides validation of a TimeSpan configuration property value. Checks a time span against a specified minimum and maximum value. The range can be specified as either inclusive i.e. the specified value must be between the specified minimum and maximum or exclusive i.e. the specified value must not be between the specified minimum and maximum. When using TimeSpanValidator you can also specify a resolution in seconds value that must be matched for the validation to pass.

As mentioned earlier, a ConfigurationSection derived custom configuration section handler class should implement a property to wrap each of its ConfigurationProperty instances. The validation classes detailed above can be used to validate the property values before they are set. The validation strategy that you use i.e. validation attribute or validation class depends on how you create the ConfigurationProperty instances that are to represent the configuration settings. If you are using the declarative model in your ConfigurationSection derived class you should decorate each of the property wrappers with a validation attribute. If you are using the programmatic model in your ConfigurationSection derived class you should assign an instance of the relevant validation class to each of the ConfigurationProperty instances as they are created. When implementing a validation strategy there is one important point to note, the base classes load the actual settings from the configuration file and assign the values to the relevant ConfigurationProperty instances, the property setters in the ConfigurationSection derived section handler class will not be called during this process. Therefore, if you want to validate configuration property values as they are loaded from the configuration file, you must either decorate the properties, in the ConfigurationSection derived class, that are representing ConfigurationProperty instances, with a validation attribute or assign an instance of the relevant validation class to each ConfigurationProperty instance as it is created. Implementing validation in a property setter will not work as the setter will not be called during the load process. The relevant property getter will be called when a configuration property is read from code and the property setter will be called when a configuration property is updated from code but during the initial load, parse and set process the validation class specified in the ConfigurationProperty instance, either directly or via an attribute, is used to verify configuration settings.

If your validation requirements cannot be met using the existing validation classes in the System.Configuration namespace and they will almost certainly not be in a surprising number of cases, you will have to develop your own validation and/or validation attribute class or classes. A custom configuration property validation class is created by extending the ConfigurationValidatorBase abstract base class and implementing your own Validate method to check the contents of the relevant configuration property. When using the programmatic model you pass an instance of the ConfigurationValidatorBase derived class to the constructor of the relevant ConfigurationProperty instances when you create them in code. The configuration system will call the Validate method inherited by the derived class before it assigns the configuration property's value. When using the declarative model, the configuration system uses the values specified in the validation attribute to initialize the relevant validation class and then calls its Validate method to perform the validation check. Therefore, a configuration property must be decorated with a validation attribute in order for the validation class to be utilized. If the custom validation class does not require any runtime parameters in order to perform the validation you can use the ConfigurationValidatorAttribute class directly to decorate the relevant configuration property passing the Type of the custom validation class to the attribute's constructor e.g. [ConfigurationValidator(typeof(CustomValidator))] where CustomValidator is the Type of the ConfigurationValidatorBase derived class. However, in order to facilitate reuse, the custom validation class will require parameters specified at runtime to perform the validation check e.g. the maximum number of characters allowed in a configuration property. To provide runtime parameter values to the validation class you will need to develop your own validation attribute class by extending the ConfigurationValidatorAttribute base class and then use the derived instance's properties to initialize the relevant validation class in the ValidatorInstance property e.g. the derived attribute class could have a MaximumLength property, the value of which is passed to the constructor of the validation class in the ValidatorInstance property.

Declarative Model Configuration Section Handler

When using the declarative or attribute model to create a configuration section handler we need to extend the ConfigurationSection abstract base class to implement a handler for the configuration section XML element. We then need to create a property getter and optionally a setter for each of the configuration settings in the configuration section XML element. A configuration setting may be an XML attribute of the section XML element or it may be an XML child element of the section XML element or it may be a collection of XML child elements of the section XML element, however, in each case a property is required in the ConfigurationSection derived class to represent the ConfigurationProperty instance that will be created by the .Net Framework configuration system to hold the configuration settings data. Each of the ConfigurationSection derived class properties that is representing a configuration property should be decorated with the ConfigurationPropertyAttribute or the ConfigurationCollectionAttribute, in the case of a collection property, to define the property as a configuration property wrapper. Any properties that require their values to be verified should also be decorated with the relevant validation attribute. To demonstrate the declarative model we will create a configuration section handler to process a configuration section with several attributes and one child element which also has several attributes, some of which are optional.

ExpandProposed configuration section to be processed by the declarative model configuration section handler:
ExpandDeclarative model configuration section handler:

The SmtpSettingsSection class extends the ConfigurationSection abstract base class to provide the section handler for the smtpSettings configuration section shown above. All of the properties are decorated with the ConfigurationPropertyAttribute to define them as configuration properties and where applicable one of the configuration settings validation attributes to enable the base class to initialize the relevant validation class. All of the properties, bar one, map to the similarly named XML attributes of the smtpSettings XML element. The exception is the MailTo property which maps to the mailTo child XML element of the smtpSettings XML element. We need another class derived from the ConfigurationElement abstract base class to implement the full functionality of this property as it is a complex type, rather than a simple or primitive string or integer.

ExpandMailToSettings class that provides the MailTo property implementation:

The MailToSettings class is similar to the SmtpSettingsSection class, however, it directly extends the ConfigurationElement abstract base class to provide the properties of the nested mailTo XML element. The other difference is that two of the XML attributes, bcc and cc, of the mailTo XML element are optional and therefore may not be included in the configuration file settings. This presents us with a slight problem because we need to validate the settings if they are present in the configuration file but we also need to be able to ignore them and return a default value of empty if they are not present in the configuration file. To achieve this we need to develop two more classes, the OptionalRegexStringValidator class that can be used to verify the settings if they are present but ignore them i.e. not throw an exception, if they are missing and the OptionalRegexStringValidatorAttribute class that can be used in the declarative model to provide the base class with the value required to initialize an instance of the OptionalRegexStringValidator class at runtime.

ExpandOptionalRegexStringValidator class that provides optional regular expression validation:
ExpandOptionalRegexStringValidatorAttribute class that provides declarative initialization of the OptionalRegexStringValidator class:

The OptionalRegexStringValidator class extends the RegexStringValidator class, which extends the ConfigurationValidatorBase abstract base class. We need to provide optional verification, so we override the Validate method and call the base class version if our override is passed a property value and just let the method return if the value passed in was null or empty. The OptionalRegexStringValidatorAttribute class extends the ConfigurationValidatorAttribute base class to provide an attribute that can be used in the declarative model to create an instance of the OptionalRegexStringValidator class to validate the configuration property that is decorated with the attribute. Our first thought when creating the attribute class was to extend the existing RegexStringValidatorAttribute class, however it is sealed and cannot therefore be extended.

In order to use our proposed smtpSettings configuration section and its associated handler we need to declare the section and handler. For this sample or demonstration the configuration section handler and associated classes are contained in the Blayd.Samples namespace and are located in the same Windows Forms assembly as the code that utilizes the settings.

ExpandConfiguration section and section handler declaration:
ExpandTo make use of the settings in the smtpSettings configuration section the code would be similar to the following:

The configuration section is retrieved in a call to the static ConfigurationManager.GetSection method and cast to the Type of the section handler class i.e. SmtpSettingsSection. From there on it could not be simpler, because of the type safety and the default values provided by the SmtpSettingsSection class, the calling code is not required to check for null, cast to the required type or to parse the integer. However, in a real application, we would recommend that you assign default values to the (string) fields in a constructor to avoid problems if the configuration section cannot be located in the configuration file.

Programmatic Model Configuration Section Handler

When using the programmatic model to create a configuration section handler we need to extend the ConfigurationSection abstract base class to implement a handler for the configuration section XML element. We then need to create a property getter and optionally a setter for each of the configuration settings in the configuration section XML element. A configuration setting may be an XML attribute of the section XML element or it may be an XML child element of the section XML element or it may be a collection of XML child elements of the section XML element, however, in each case a property is required in the ConfigurationSection derived class to represent the ConfigurationProperty instance that we will be creating to hold the configuration settings data. To demonstrate the programmatic model we will create a configuration section handler to process a configuration section with several attributes and a collection of child elements which also have several attributes, some of which are optional.

ExpandProposed configuration section to be processed by the programmatic model configuration section handler:
ExpandProgrammatic model configuration section handler:

The SmtpSettingsSection class extends the ConfigurationSection abstract base class to provide the section handler for the smtpSettings configuration section shown above. When using the programmatic model we have to create the ConfigurationProperty instance in our own code and in the example above we have declared a set of static fields to hold the underlying configuration properties, these fields are initialized in the static constructor. When creating the ConfigurationProperty instance from code there is bit more work to do compared to the attribute model but we do gain full control over the process. We add our ConfigurationProperty fields to the collection managed by the ConfigurationElement base class in the instance constructor. All of the properties, bar one, map to the similarly named XML attributes of the smtpSettings XML element. The exception is the Recipients property which maps to the recipients child XML element of the smtpSettings XML element. This element is mapped to an add, remove, clear map collection class named RecipientCollection and derived from the ConfigurationElementCollection class, which holds instances of the RecipientElement class. The RecipientElement class is derived from the ConfigurationElement abstract base class and represents the attributes of each of the add child XML elements of the recipients XML element.

ExpandRecipientElement class that provides the add element property implementation of the recipients element:
ExpandRecipientCollection class that provides the Recipients property implementation and will be populated, by the configuration system, with instances of the RecipientElement class:

The RecipientElement class, which extends the ConfigurationElement base class initializes its static ConfigurationProperty instances in the static constructor and makes use of the OptionalRegexStringValidator class that was developed for the declarative or attribute model example shown earlier in this article. The RecipientCollection class extends the ConfigurationElementCollection abstract base class to represent the recipients XML element in the configuration file. The ConfigurationElementCollection class also extends the ConfigurationElement base class which includes its own string indexer, therefore, we have to hide the base class property by declaring the RecipientCollection string indexer as new. Other than that there is nothing particularly remarkable about either class, what is clever, however, is how the .Net Framework configuration system puts all of this together to load and parse the configuration settings, create the collection instance and then populate it with instances of the RecipientElement class.

In order to use our proposed smtpSettings configuration section and its associated handler we need to declare the section and handler. For this sample or demonstration the configuration section handler and associated classes are contained in the Blayd.Samples namespace and are located in the same Windows Forms assembly as the code that utilizes the settings.

ExpandConfiguration section and section handler declaration:
ExpandTo make use of the settings in the smtpSettings configuration section the code would be similar to the following:

The configuration section is retrieved in a call to the static ConfigurationManager.GetSection method and cast to the Type of the section handler class i.e. SmtpSettingsSection. From there on it could not be simpler, because of the type safety and the default values provided by the SmtpSettingsSection class, the calling code is not required to check for null, cast to the required type or to parse the integer. However, in a real application, we would recommend that you assign default values to the (string) fields in a constructor to avoid problems if the configuration section cannot be located in the configuration file. In the above example, we have simply output the recipient details to the console because, in a real application, we would not usually store this type of data again in instance fields, it makes more sense to retrieve the details of the relevant recipient as and when it is needed.

Conlusion

The configuration section handler classes that ship with the .Net Framework are fine if your configuration requirements are straight forward, indeed the AppSettingsSection handler for the appSettings configuration section may be all you need in a lot of situations. If, however, you need to store more complex configuration settings data you will almost certainly have to develop your own handler classes. The ConfigurationElement, ConfigurationSection and the other associated base classes available as from version 2.0 of the .Net Framework make the development of custom configuration section handlers much easier than it was in earlier framework releases. You can, for example, get a long way with the declarative or attribute model which may save you quite a lot of coding and if you require more flexibility or control you have the option of the programmatic model. However, whichever strategy you choose, you may well end up with a lot of configuration related classes in your solution, so it make sense to consider the design of your configuration settings carefully and to try to develop handlers that can be used in more than one solution.