Skip to main content

Configuration in .Net 2.0

11-Dec-2007 Update
I've updated this post to fix the broken images and replaced them with inline text for the example xml and accompanying C# code. This post has been by far the most hit on this blog and along with the comments about the missing images I thought it was time to update it!


Whilst recreating the examples below I zipped up the working source code and xml file and loaded this onto my Project Distributor site - please download it to get a full working custom configuration to play with! Just click on the CustomConfigExampleSource link on the right hand side, then the "Source" link to get the zip.

We are in the process of converting our codebase to .Net 2.0. We've used Enterprise Library to great effect so decided that we should continue with this in the form of the Jan 2006 release which targets 2.0 and I've got the job of porting our Logging, Data Access etc wrappers to EntLib 2.0.

...And so far so good - the EntLib docs aren't bad and the migration section is a real help in pointing out the changes, improvements and route you need to take to go from 1.1 to 2.0. The major change is that EntLib has dropped the Configuration application block as almost all of this functionality is provided out of the box by the .Net 2.0 System.Configuration namespace.

Tip: You now need to explicitly add a reference to the System.Configuration (named System.configuration.dll on disk) assembly from the .Net references list. If you don't you'll be scratching your head wondering where most of the classes are missing, specifically the ConfigurationManager class which is your main entry point now. The reason for this is backwards compatibility I believe - I did read a post about it and can't remember where now!

I must stress that the following applies for porting your custom xml configuration files from the EntLib Configuration Block. Configuration for the other blocks (Data Access, Exception Handling, Logging etc) has also moved on slightly and you need to rebuild your configuration files from scratch with the EntLib GUI.



Its all pretty straight forward stuff, once you've had a read through some of these excellent posts about Configuration and I hope to highlight and augment some of the steps required to convert over to 2.0 Configuration.

Tom Hollanders external files with EntLib
Paulo Reicherts Conchango Blog on Config Management in 2.0
Mark Gabarras on Collection tags
Custom Configuration Sections on The Code Project
Read/Write app.config files

There are two things you will need to do to move your custom xml configs to .Net Configuration 2.0.

1) Check and modify the xml structure - it needs to be attribute centric at the leaf elements and it also needs to enclose any lists in a dedicated parent node. Now both of these things are what you are normally doing right? ;-)

Below is the xml "before" and "after" to illistrate the changes you need to make.

Original xml....

<?xml version="1.0" encoding="utf-8" ?>

<CustomConfig>

<SomeSetting>

<Id>1</Id>

<SomeData>A</SomeData>

<MoreData>B</MoreData>

</SomeSetting>

<SomeSetting>

<Id>2</Id>

<SomeData>X</SomeData>

<MoreData>Y</MoreData>

</SomeSetting>

</CustomConfig>


Custom Configuration friendly xml...


<?xml version="1.0" encoding="utf-8" ?>

<CustomConfig>

<SomeCollection>

<SomeSetting id="1" someData="A" moreData="B"/>

<SomeSetting id="2" someData="X" moreData="Y"/>

</SomeCollection>

</CustomConfig>


2) You will need to re-create the class that this (de)serialises to and from. Special configuration attributes are now used to control how this works rather than Xml (although they must underpin it all). This is the main pain as you need to understand how these work (not too difficult) but its time spent to essentially gain nothing - its still Xml based configuration at the end of the day! Previously you could point xsd.exe or xmlobjgenerator at a schema and bingo you had a .Net class to use - super quick custom configs to plug straight into EntLib. There is a tool that will do something similar, SCDL so take a look at this first.

If you are determined to hand crank it then you are looking at a mapping exercise - how does your Xml map to a configuration class tree? Paulos post does a great job in identifying these entities but essentially it boils down to this and you will end up with,


  • 1 class that inherits directly from System.Configuration.ConfigurationSection - this corresponds to your root element
  • 1 class for each complex element (contains other elements or has attributes); this will inherit from ConfigurationElement.
  • If you have repeated elements (i.e a collection) you will need a class to represent this collection; this class will need to inherit from ConfigurationElementCollection and corresponds to your dedicated collection node.
  • The items in a list will need a class (as they are an element). These elements can be complex in structure and inherit from ConfigurationElement as any non list element would.

So onto the mapping itself and there are some simple rules to follow...

For each node in your Xml, its child elements and direct attributes of the node itself are ALL represented as properties in the class that represents it.

You must, at the minimum create a property getter for each of these and decorate the property with the attribute [ConfigurationProperty("nodename")] - this links the element/attribute in the Xml to the property in the class.

If you want to create configurations in code to save you will need to add property setters too.

For classes that represent a collection of repeated elements you are forced to implement two methods, GetElementKey and CreateElement, these return the indexer key property of an item in the collection and create a new item for the collection respectively. I would recommend also adding an indexer method to allow you to access the items by index number (see example below).

You must add an attribute to the collection class definition itself to tell the configuration framework what type of items it is a collection of - this attribute "ConfigurationCollection" has a magic ingredient, AddItemName as a named parameter in the constructor - more on this later.

So putting it all together you get this class for the xml shown above,



using System;

using System.Configuration;

namespace CustomConfigs

{

public class CustomConfig : ConfigurationSection

{

[ConfigurationProperty("SomeCollection")]

public SomeCollection Settings

{

get { return this["SomeCollection"] as SomeCollection; }

}

}

/// <summary>

/// NOTE: AddItemName parameter set to allow a custom name for the leaf

/// element name. By default this is "add", setting AddItemName allows

/// you to change the name to "prettify" the xml

/// </summary>

[ConfigurationCollection(typeof(SomeSetting), AddItemName="SomeSetting")]

public class SomeCollection : ConfigurationElementCollection

{

///<summary>

///When overridden in a derived class, creates a new

///<see cref="T:System.Configuration.ConfigurationElement"></see>.

///</summary>

///

///<returns>

///A new <see cref="T:System.Configuration.ConfigurationElement"></see>.

///</returns>

///

protected override ConfigurationElement CreateNewElement()

{

return new SomeSetting();

}

///<summary>

///Gets the element key for a specified configuration element when overridden in a derived class.

///</summary>

///

///<returns>

///An <see cref="T:System.Object"></see> that acts as the key for the specified

///<see cref="T:System.Configuration.ConfigurationElement"></see>.

///</returns>

///

///<param name="element">The <see cref="T:System.Configuration.ConfigurationElement">

///</see> to return the key for. </param>

protected override object GetElementKey(ConfigurationElement element)

{

return ((SomeSetting) element).Id;

}

/// <summary>

/// This is a custom indexer to allow retrieval of a setting

/// at a specfic index in the collection - a useful addition!

/// </summary>

/// <param name="index"></param>

/// <returns></returns>

/// <remarks>No index range checking is performed...</remarks>

public SomeSetting this[int index]

{

get { return BaseGet(index) as SomeSetting; }

}

}

public class SomeSetting : ConfigurationElement

{

[ConfigurationProperty("id", IsRequired = true, IsKey = true)]

public int Id

{

get { return Convert.ToInt32(this["id"]); }

}

[ConfigurationProperty("someData")]

public string SomeData

{

get { return Convert.ToString(this["someData"]); }

}

[ConfigurationProperty("moreData")]

public string MoreData

{

get { return Convert.ToString(this["moreData"]); }

}

}

}


By default the leaf element name is "add" - I have overridden this by specifying a value for the "AddItemName" parameter. The <add> element really acts as a directive to the configuration framework to create an instance of the item the collection holds (with your CreateElement method) and populate it with the attributes and child elements within. You can override the name of this "directive" by using the AddItemName parameter in the Configuration so <add> can become anything you like, in this case <SomeSetting>.

Splitting the configuration files
The final thing I was interested in achieving was to hold my custom configurations in their own dedicated file - this compartmentalisation being good for readability and security as ACL's can be applied to individual files.


So how did we do this? I first looked at the ConfigurationManager.OpenExeConfiguration method - it looked like a good bet as it takes a path but as the name suggests its actually the path to an executable (dll or exe) to which it will append .config to. If you want to store your configuration files in a seperate folder this method will not work so it was back to the drawing board! Toms post had the answer - configSource property of the section element in the app/web.config.

A custom configuration stored within the app.config...

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

<configSections>

<!-- the name must match a custom element below which contains

the actual config (interal) or a link to the config in an

external config file (using configSource) -->

<section name="CustomConfig"

type="CustomConfigs.CustomConfig, CustomConfigs"/>

</configSections>

<!-- stored internal to app.config -->

<CustomConfig>

<SomeCollection>

<SomeSetting id="1" someData="A" moreData="B"/>

<SomeSetting id="2" someData="X" moreData="Y"/>

</SomeCollection>

</CustomConfig>

</configuration>

Using the configSource attribute to relocate to an external file...

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

<configSections>

<!-- the name must match a custom element below which contains

the actual config (interal) or a link to the config in an

external config file (using configSource) -->

<section name="CustomConfig"

type="CustomConfigs.CustomConfig, CustomConfigs"/>

</configSections>

<CustomConfig configSource="Config\custom_config_example.xml" />

</configuration>

Note 1: When you locate the custom configuration in an external file you must ensure that the section name matches the root node name otherwise you will get a ConfigurationErrorsException thrown with the message "The format of a configSource file must be an element containing the name of the section.".

Note 2: You cannot apply this to a section group (I believe after doing a bit of digging) which is a shame however even splitting just the sections out really streamlines your app/web.configuration and as mentioned does have these benefits,

  • Readability
  • Portable, decoupled configurations
  • Improved Security through individual file ACL's
  • Modification does not trigger an automatic Web Application restart event (you also need to so some other configuration - check this article out for more info on disabling application restarts)

Accessing your Custom Configuration

To load your custom configuration you need to use the System.Configuration.ConfigurationManager helper. This provides a method called "GetSection" - this will load your configuration into your configuration entity class.

CustomConfig config = ConfigurationManager.GetSection("CustomConfig") as CustomConfig;

...and you can access the properties such as the "Settings" collection etc...simple!

I hope this has been helpful to you if you are just getting your head round .Net 2.0 and the Enterprise Library. There is so much great information out in the Blogosphere but its kinda dotted around and fragmented so hopefully this assembles everything to need to know about custom configurations. As ever, errors, mistakes etc please leave a comment and I'll try to respond!

Comments

Anonymous said…
Thanx a lot. Finally i found an article that is relevant.
Anonymous said…
Thanks so much, I spent an awful amount of time trying to figure out how to do this.

You are the man! Thanks
Anonymous said…
You did a great job presenting the .NET 2.0 configuration changes. Very helpful! Thanks.
Anonymous said…
Great article.

Could you please update your links to images so that they can be displyed? Thanks a lot.

Tim
Unknown said…
Hi, sorry I have moved ISP and lost the images in the article. I will get the images hosted on with my new ISP and update the broken links.

James
Anonymous said…
This is exactly what I was looking for. Thanks so much for giving this information. It really helped us.
Unknown said…
Thanks the article helped me a lot. But you can emulate section group in external file.
Your app.config:

<configSections>
<section name="Group" ... />
<section name="Group.Section" ... />
</configSecions>
...
<Group configSource="external.config" />

Your external.config:
<Group>
<Section />
</Group>

Best regards
Unknown said…
Hi,

I did a bit of digging and it looks like you cannot apply configSource to a section (backing up my previous assertion of this - Note 2 at the end of the original post).

More info can be found here...
http://weblogs.asp.net/cibrax/archive/2007/07/24/configsource-attribute-on-system-servicemodel-section.aspx

It also goes into details about how to split WCF service model configurations into separate config files...useful.

Regards,

James
Anonymous said…
thanx, nice article

Popular posts from this blog

Walk-Thru: Using Wolfpack to automatically deploy and smoke test your system

First, some history... The advent of NuGet has revolutionised many many aspects of the .Net ecosystem; MyGet, Chocolatey & OctopusDeploy to name a few solutions building upon its success bring even more features to the table. I also spotted that NuGet could solve a problem I was having with my OSS System Monitoring software Wolfpack ; essentially this is a core application framework that uses plugins for extension ( Wolfpack Contrib ) but how to unify, standardise and streamline how these plugins are made available? NuGet to the rescue again - I wrapped the NuGet infrastructure (I deem NuGet to be so ubiquitous and stable that is has transcended into the software "infrastrucuture" hall of fame) with a new OSS project called Sidewinder . Sidewinder allows me to wrap all my little extension and plugins in NuGet packages and deploy them directly from the Wolfpack application - it even allows me to issue a new version of Wolfpack and have Wolfpack update itself, sweet huh

Castle/Windsor schema enables Visual Studio intellisense

There has been a lot of noise recently about Inversion of Control (IoC) with .Net recently (stop sniggering at the back java guys!).... I've been using IoC via the Spring.NET framework for over 2 years now - it's a completely different approach to coding and once you get your head around it everything just falls into place and development is a real joy again. As I mention, Spring.NET is my framework of choice but a recent change in employer has seen me bump up against Castle/Windsor . First impressions are that I like it - it's not as powerful or feature rich as Spring but that's not always a bad thing! The one thing I did miss though was Visual Studio intellisense when editing the configurations - Spring has an online schema that can be associated with a Spring configuration. This got me thinking - if the VS intellisense can be hooked into that easily why not create one for Windsor configuration? So I did...you can download it from my new google code site here . Remem