Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Custom Configuration Sections FTW

DZone's Guide to

Custom Configuration Sections FTW

·
Free Resource
As a .NET developer you will probably create hundred of configuration files over the course of your career. Most of the time when we use a configuration file it is for simple things like a database connection string. However, sometimes configuration data needs to be relational and can require a more complex structure than traditional name value pairs. For example, lets say that I am developing a data import tool which uses the configuration file to store data mapping information. Here is an example of what my configuration file might look  like:
<import>
<jobs>
<job name="Foo">
<fieldMappings>
<mapping source="column1" destination="TimeStamp"/>
<mapping source="column2" destination="Name"/>
</fieldMappings>
</job>
<job name="Bar">
<fieldMappings>
<mapping source="column1" destination="DateTime"/>
<mapping source="column2" destination="IPAddress"/>
</fieldMappings>
</job>
</jobs>
</import>
Yes, the configuration data I just defined is simple XML and I could parse it on my own. However, I really do not want to have to do all of this XML parsing by myself. After all the purpose of creating a custom configuration section is to leverage the pre-existing configuration classes and let .NET do all the heavy lifting for me. By using the built-in classes I can easily enumerate my configuration data by looping over the data and accessing the object properties like this:
ImportElement import = (ImportElement)ConfigurationManager.GetSection("import");
foreach (JobElement job in import.Jobs)
{
foreach( FieldMappingElement mapping in job.FieldMappings)
{
...
mapping.Destination = mapping.Source;
...
}
}
The first step in creating our custom configuration section is to define the ImportElement class. This is the root node of the configuration section. The class is very simple and only contains a property to access the jobs which reside underneath it:
public class ImportElement : ConfigurationSection
{
// Fields
private static readonly ConfigurationProperty jobs =
new ConfigurationProperty("jobs", typeof(JobCollection), null, ConfigurationPropertyOptions.IsRequired);

// Methods
public DataMapping()
{
base.Properties.Add(jobs);
}

// Properties
[ConfigurationProperty("jobs", IsRequired = true)]
public JobCollection Jobs
{
get
{
return (JobCollection)base[jobs];
}
}
}
The important thing to notice is that the ImportElement class inherits from System.Configuration.ConfigurationSection. The ConfigurationSection class knows how to parse XML and shields us from doing any of the dirty work. In general, the less XML parsing I have to do, the happier I am. However if you have a strange fetish for XML parsing then please stop reading this article here and uninstall Visual Studio from your machine. Next, download the JDK, install Eclipse and start writing Java code. Once you are fed up with manual XML parsing please come back and finish the rest of this tutorial. :-)

Anyway, getting back to the tutorial…the ImportElement class also exposes a property called Jobs which is of type JobCollection. The JobCollection class is also defined as a custom class. However, JobCollection will inherit from ConfigurationElementCollection instead of ConfigurationSection because it is a collection of JobElements:
[ConfigurationCollection(typeof(JobElement), AddItemName = "job", CollectionType = ConfigurationElementCollectionType.BasicMap)]
public class JobCollection : ConfigurationElementCollection
{
// Methods
protected override ConfigurationElement CreateNewElement()
{
return new JobElement();
}

protected override object GetElementKey(ConfigurationElement element)
{
return ((JobElement)element).Name;
}

// Properties
new public JobElement this[string jobName]
{
get
{
return (JobElement)base.BaseGet(jobName);
}
}
}
Notice that the ConfigurationCollection attribute is used to declare that this collection contains a list of JobElement(s). Since the Job Element also contains a list of field mappings you would repeat these steps to create the JobElement and FieldMappingCollection classes. With that said, lets move on to the FieldMapping class which contains a few attributes for the source and destination columns used in the import process:
public class MappingElement : ConfigurationSection
{
// Fields
private static readonly ConfigurationProperty _destination =
new ConfigurationProperty("destination", typeof(string), string.Empty, ConfigurationPropertyOptions.IsRequired);
private static readonly ConfigurationProperty _source =
new ConfigurationProperty("source", typeof(string), string.Empty, ConfigurationPropertyOptions.IsRequired);

// Methods
public MappingElement()
{
base.Properties.Add(_source);
base.Properties.Add(_destination);
}

// Properties
[ConfigurationProperty("destination", IsRequired = true)]
public string Destination
{
get
{
return (string)base[_destination];
}
}

[ConfigurationProperty("source", IsRequired = true)]
public string Source
{
get
{
return (string)base[_source];
}
}
}
Now I would like to point on that I am using simple string properties in my MappingElement class. However, you can use integers, enums, datetimes or whatever else you need in order to achieve your goals. As long as you can serialize and deserialize your values then the sky is the limit.

At this point we have built all of our configuration classes. However, we still need to tell the application how to associate our custom configuration classes with the corresponding section of our config file. This is accomplished by adding a little code to the configSections element of the application config file:
<configuration>
<configSections>
<section name="import" type="MyProject.Configuration.Import, MyProject"/>
</configSections>
...
</configuration>
The name attribute should match the root node of your custom config section. The type attribute contains two values separated by a comma. The first part is the fully qualified classname that represents the root element in your custom config section. The second part of the attribute is the assembly name where the code resides. Once you have the configSections defined you are ready to roll!
Topics:

Published at DZone with permission of Michael Ceranski, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}