Configuration Settings Block

Topics: Building and extending application blocks
Nov 14, 2011 at 6:27 PM

I am trying to use EntLib 5.0 to create a common config file for all my applications. I am trying to do a POC and I created a Common.Config in a directory, the contents of which is -

<configuration>
    <configSections>
    </configSections>
    <appSettings>
        <add key="Test" value="TestValue" />
    </appSettings>
</configuration>

And I am trying to use this in an app.config of a console application. After configuring it in EntLib console, here's how my app.config for the ConsoleApplication Looks like-

 <?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="enterpriseLibrary.ConfigurationSource" type="Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ConfigurationSourceSection, Microsoft.Practices.EnterpriseLibrary.Common, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" />
    </configSections>
    <enterpriseLibrary.ConfigurationSource selectedSource="Common Config">
        <sources>
            <add name="System Configuration Source" type="Microsoft.Practices.EnterpriseLibrary.Common.Configuration.SystemConfigurationSource, Microsoft.Practices.EnterpriseLibrary.Common, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
            <add name="Common Config" type="Microsoft.Practices.EnterpriseLibrary.Common.Configuration.FileConfigurationSource, Microsoft.Practices.EnterpriseLibrary.Common, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                filePath="C:\Documents and Settings\user1\My Documents\Visual Studio 2010\Projects\CommonConfig\Test1\Test1\Common.config" />
        </sources>
    </enterpriseLibrary.ConfigurationSource>
</configuration>

 

And when I do this in my ConsoleApplication's program.cs

  var x = ConfigurationManager.AppSettings.Count;

 

I get zero. I have also tried configuring my Common.Config as a ParentSource and it does not seem to work.. Please assist.

 

Nov 15, 2011 at 8:21 PM

Whoa! Nobody..

Nov 16, 2011 at 4:02 AM

Since you've placed your configuration in an Enterprise Library ConfigurationSource you will have to access that configuration through Enterprise Library.

.NET's ConfigurationManager doesn't know what you've done.  :)  (You can actually access two sets of of appSettings one via ConfigurationManager/app.config and the other through FileConfigurationSource/Common.Config.)

To get at your Common.config appSettings use:

IConfigurationSource configSource = ConfigurationSourceFactory.Create();

var appSettings = configSource.GetSection("appSettings") as AppSettingsSection;
var x = appSettings.Settings.Count;


--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com

Nov 17, 2011 at 9:12 PM
Edited Nov 17, 2011 at 9:43 PM

I can't thank you enough Randy.

Another question. Is there a way to read custom section values also? I have custom sections, section groups:

<section name="MachineEnvs" type="System.Configuration.NameValueFileSectionHandler, System, Version=1.1.4322.2032, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
    <sectionGroup name="TopLevel">
      <section name="appSettings" type="System.Configuration.NameValueFileSectionHandler, System, Version=1.1.4322.2032, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
    </sectionGroup>

And the corresponding values are
<MachineEnvs>
<add key = "1" value = "One"/>
</MachineEnvs>

<TopLevel>
<appSettings>
<add key = "1" value = "ONE"/>
</appSettings>
</TopLevel>

I can do the following and load the section. But don't know how to cast or parse them. Intellisense is not helping me..

var appSettings = configSource.GetSection("Dev/appSettings" );

If I try to read this as AppSettingsSection I get a null for appSettings

Nov 18, 2011 at 3:33 AM

It looks like this configuration approach is left over from .NET 1.1 (when the out of the box configuration options were fairly limited).  These days, for custom configuration I would recommend creating your own custom configuration sections.  This can be made even easier by using a tool such as Configuration Section Designer.

That aside my config looks like this:

<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="MachineEnvs" type="System.Configuration.NameValueFileSectionHandler, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
    <sectionGroup name="TopLevel">
      <section name="appSettings" type="System.Configuration.NameValueFileSectionHandler, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
    </sectionGroup>
  </configSections>

  <MachineEnvs>
    <add key="1" value="One"/>
  </MachineEnvs>

  <TopLevel>
    <appSettings>
      <add key = "1" value = "ONE"/>
    </appSettings>
  </TopLevel>

</configuration>

And the code to read it looks like this:

var machineEnvs = ConfigurationManager.GetSection("MachineEnvs") as NameValueCollection;
var topLevelSettings = ConfigurationManager.GetSection("TopLevel/appSettings") as NameValueCollection;

Hope that helps,

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com

Nov 18, 2011 at 3:46 PM
Edited Nov 18, 2011 at 3:50 PM

Thanks again Randy.

But the thing is, the custom sections are part of the Common.Config file and I guess I can not read them by ConfigurationManager of .Net. I have been using ConfigurationManager to read the custom sections all this while, I would like to put those custom sections in common.config via EntLib 5.0 and try to read them via IConfigurationSource. Is that possible?

If I do this -

IConfigurationSource configSource = ConfigurationSourceFactory.Create();
var appSettings = configSource.GetSection("MachineEnvs") as NameValueCollection;

I get cannot convert type System.Configuration.ConfigurationSection to NameValueCollection. I cant cast this as AppSettingsSection either since appSettings becomes null.

Nov 19, 2011 at 7:53 PM
Edited Nov 20, 2011 at 11:32 PM

OK, I see you want to go through IConfigurationSource to the external file.

The problem is that the .NET Configuration functionality was dramatically changed for version 2.0.  
Now configuration sections need to inherit from ConfigurationSection instead of implement IConfigurationSectionHandler
as the NameValueFileSectionHandler type does.  Instead the section is treated as a DefaultSection even though the section information
has been set up. 

As I mentioned the preferred method would be to create a strongly typed custom configuration sections for the configuration information.

Another approach would be to manually parse the information.  Since the structure is well known this shouldn't be an issue.

In this example example I create an extension method to parse the XML configuration and return a NameValueCollection.  In that way
code shouldn't need to change and the configuration doesn't need to change.

public static class DefaultSectionExtension
{
    private const string AddElement = "add";
    private const string RemoveElement = "remove";
    private const string ClearElement = "clear";

    private const string KeyAttributeName = "key";
    private const string ValueAttributeName = "value";
        
    private const char Separator = '/';

    /// <summary>
    /// Parses the XML of a DefaultSection and converts it to a NameValueCollection.
    /// This method assumes that the configuration section follows the add/remove/clear model 
    /// used in appSettings.
    /// </summary>
    /// <param name="configSource">The configuration source to retrieve the section from</param>
    /// <param name="sectionName">The section name to retrieve.  This can be a section group format
    /// E.g. mySection/MyNestedSection/myAppSettings.
    /// </param>
    /// <returns>NameValueCollection with the key/value pairs contained in the section.
    /// Returns null if the section could not be found.  
    /// If the section is empty then a NameValueCollection with no items is returned.
    /// </returns>
    public static NameValueCollection GetNameValueCollectionBySection(this IConfigurationSource configSource, 
        string sectionName)
    {
        Microsoft.Practices.Unity.Utility.Guard.ArgumentNotNull(configSource, "configSource");
        Microsoft.Practices.Unity.Utility.Guard.ArgumentNotNullOrEmpty(sectionName, "sectionName");

        DefaultSection section = configSource.GetSection(
                sectionName.Split(Separator).First()
            ) as DefaultSection;

        return GetNameValueCollection(section, sectionName);
    }

    private static NameValueCollection GetNameValueCollection(DefaultSection defaultSection, 
        string relativeSectionXPath)
    {
        if (defaultSection == null)
        {
            return null;
        }

        NameValueCollection collection = new NameValueCollection();

        XDocument xdoc = XDocument.Parse(defaultSection.SectionInformation.GetRawXml());

        foreach (var element in xdoc.XPathSelectElements(Separator + relativeSectionXPath).Elements())
        {
            if (IsElementFound(element, AddElement))
            {
                collection.Add(element.Attribute(KeyAttributeName).Value, element.Attribute(ValueAttributeName).Value);
            }
            if (IsElementFound(element, RemoveElement))
            {
                collection.Remove(element.Attribute(KeyAttributeName).Value);
            }
            if (IsElementFound(element, ClearElement))
            {
                collection.Clear();
            }
        }

        return collection;
    }

    private static bool IsElementFound(XElement element, string attributeToMatch)
    {
        return (string.Compare(element.Name.LocalName, attributeToMatch, StringComparison.Ordinal) == 0);
    }
}

 Then for the following configuration (actually, since they are a DefaultSection type you don't even need to specify them in the <configSections>):

<MachineEnvs>
  <add key = "1" value = "One"/>
  <add key="2" value = "two" />
  <remove key="1" />
  <clear />
  <add key="3" value = "three" />
</MachineEnvs>

<TopLevel>
  <appSettings>
    <add key = "1" value = "ONE"/>
  </appSettings>
</TopLevel>

<AnotherTopLevel>
  <SecondLevel>
    <appSettings>
      <add key = "111" value = "OneOneOne"/>
      <add key = "1121" value = "OneOneTwoOne"/>
      <add key = "11111" value = "OneOneOneOneOne"/>
    </appSettings>
  </SecondLevel>
</AnotherTopLevel>

Then you can get the configuration information using the following:

    var configSource = ConfigurationSourceFactory.Create();

    var machineEnvs = configSource.GetNameValueCollectionBySection("MachineEnvs");
    var topLevelAppSettings = configSource.GetNameValueCollectionBySection("TopLevel/appSettings");
    var secondLevelAppSettings = configSource.GetNameValueCollectionBySection("AnotherTopLevel/SecondLevel/appSettings");
    var nullSettings = configSource.GetNameValueCollectionBySection("TopppppppLevel/appSettings"); // not found

I hope that helps.

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com

Dec 5, 2011 at 8:32 PM

Thank you Randy. Sorry could not respond sooner since I was out on a vacation. Actually I am moving to Custom configurations per .Net 4.0 so all is good now. I just have one other question.

If I have an appSettings section in my common.config and also in app.config and if I would like to override the common.config in my app.config what should I do?

I have this in common.config - <add key = "Environment" value = "Prod">

And if I add this in my local app.config <add key = "Environment" value = "Dev">

I would like the Environment to be dev. My entlib configuration is still the same as in post #1 above.

 

Thanks in advance!

 

Dec 6, 2011 at 12:42 AM

If you are just talking about overriding specific appsettings then you can use an extension method as above that will first inspect app.config and 
then fall back to the configuration source.  Something like:

        public static string GetAppSettingValue(this IConfigurationSource configSource, string appSettingKey)
        {
            var value = ConfigurationManager.AppSettings[appSettingKey]; 
            
            if (string.IsNullOrEmpty(value)) 
            { 
                var appSettings = configSource.GetSection("appSettings") as AppSettingsSection; 
                value = appSettings[appSettingKey]; 
            }

            return value;
        }

You could also use similar logic to override the entire section.  

E.g. if the main app.config appSettings.Count > 0 then return that appSettingsSection...otherwise return the AppSettingsSection from the IConfigurationSource.

If you are talking about somehow overriding an entire configuration based on a setting I would recommend changing the app.config configuration source to point to a different file
(since you would have to change app.config to modify the appSettings anyway).

If you want to support different configurations for different environments take look at 
Scenario 7: Managing Configuration in Different Deployment Environments in Appendix D - Enterprise Library Configuration Scenarios
from the Enterprise Library Developers Guide.

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com