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
You are the man! Thanks
Could you please update your links to images so that they can be displyed? Thanks a lot.
Tim
James
Your app.config:
<configSections>
<section name="Group" ... />
<section name="Group.Section" ... />
</configSecions>
...
<Group configSource="external.config" />
Your external.config:
<Group>
<Section />
</Group>
Best regards
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