65.9K
CodeProject is changing. Read more.
Home

Custom ConfigurationSettings through configuration resource embedding

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.21/5 (8 votes)

May 4, 2004

3 min read

viewsIcon

76622

downloadIcon

680

Selective .config/embedded xml config resources reading

Introduction

It's quite convenient to put some application's settings kept together in a separate easy to edit xml file. There are a number of .NET projects allowing doing that - like Console Application, Web Application, Services etc. These have a separate precreated .config file attached on the project's creation stage. In general, this is either App.config (like ordinary Console Application would have), or Web.config in case of Web Application. What would you do if you have Class Library? This kind of projects doesn't have it's configuration file - even if you'll attach it manually, you'll never find it under the bin folder named *.dll.config.

Let's specify the sample problem: there is a solution containing some projects. Additionally, you have a .config file shared among the projects within your solution. - this could be a web application with some main startup project, configured by Web.config, and a couple of class libraries. Class libraries loaded by main project during it's execution, have a straightforward access to configuration file. What if we'll have to load some class libraries by a separate third party services/applications? Here we go - we're missing the domain of main app execution as well as configuration binded to it.

If application's configuration isn't supposed to be changed during the runtime of it's components, there is a way to workaround the problem - simply by adding shortcuts on .config file to class libraries (on the solution's level), setting Build Action to Embedded Resource for these shortcuts and reading embedded resource during runtime after the ordinary ConfigurationSettings.AppSettings failed to return it's NameValueCollection.

Basics

The best way to fix the problem described above is to build a wrapper pretty transparent for the rest of the solution's code. I've named it CustomConfigurationSettings. There should also be a method to pool up data from assembly resources and transform it to name-value collection in order to preserve as much as possible of transparence for the rest of the application.

The code

As soon as the main task is an easy and fast xml (configuration) parsing, I choose forward-only XmlTextReader for the job. The goal is to read xml, transform it to collection presentation and store for further use. Let's put the process on points and review:

1. check if ordinary ConfigurationSettings is available

      private static NameValueCollection GetNvcConfig() {
        NameValueCollection nvc = null;
        if(ConfigurationSettings.AppSettings.Count>0) {
          nvc = ConfigurationSettings.AppSettings;
        }else{
          //read embedded resource
        }
        return nvc;
      }
      
2. get embedded configuration stream
      private static Stream GetEmbeddedConfigStream() {
        Stream stream = null;
        Assembly assembly = Assembly.GetExecutingAssembly();
        string [] resNames = assembly.GetManifestResourceNames();
        if(resNames.Length>0) {
          //this can be changed to named resource pooling 
          //(instead of selecting the first one)
          stream = assembly.GetManifestResourceStream(resNames[0]);
        }
        return stream;
      }
      
3. read configuration stream and transform it's content to NameValueCollection representation
 private static NameValueCollection 
  GetConfigStreamData(Stream stream) {
        int level = -1;
        string name = SE;
        NameValueCollection nvc = new NameValueCollection();
        XmlTextReader xReader = new XmlTextReader(stream);
        while(xReader.Read()) {
          if(xReader.NodeType==XmlNodeType.Element){
            //IsEmptyElement - something like <abc/>
            if(xReader.IsEmptyElement){
              if(level>xReader.Depth){
                name=name.Substring(0, name.LastIndexOf(NS));
                level = xReader.Depth;
              }else
              if(level==xReader.Depth){
                name=name.Substring(0, name.LastIndexOf(NS));
                level = xReader.Depth-1;
              }
            }else{
              //this is a section for handling nodes like <abc></abc>
              if(level<xReader.Depth){
                name+=(name.Length>0?NS:SE)+xReader.Name;
                level=xReader.Depth;
              }else
              if(level>xReader.Depth){
                name=name.Substring(0, name.LastIndexOf(NS));
                level=xReader.Depth;
              }else{
                name=name.Substring(0, 
                  name.LastIndexOf(NS)+1)+xReader.Name;
              }
            }
            if(xReader.HasAttributes){
              string key=xReader.GetAttribute(ATT_KEY);
              string val=xReader.GetAttribute(ATT_VAL);
              if(key!=null && val!=null){
                nvc[name+NS+key]=val;
              }
            }
          }else
          if(xReader.NodeType==XmlNodeType.EndElement){
            if(level>xReader.Depth){
              name=name.Substring(0, name.LastIndexOf(NS));
              level=xReader.Depth;
            }
          }
        }
        return nvc;
      }
    

- as you can see, name-value collection keys will have [itm1]/[itm2].../[itmN] form. Empty elements at the leaves of xml hierarchy (containing desired attributes) will not form key names. Key values generation logic stands on not empty xml elements (IsEmptyElement==false) and their depth in configuration xml content.

The actual values for configuration.appSettings nodes level will have <configuration/appSettings/[key_attribute_name]> as key, and key_attribute_value as the value of corresponding key. So having prefix part (configuration/appSettings) for certain configuration section (not necessary appSettings, this could be any section if needed) allow us to gain NameValueCollection for the level we're looking for (by iterating through keys with certain prefixes).

4. store overall configuration collection in some static variable (we don't want to read assembly resources again and again for every conf read call, so let's cache it)

protected static readonly NameValueCollection 
  _nvcConfig = GetNvcConfig();

5. filter appSettings node (on demand)

    private static NameValueCollection GetAppSettingsConfig() {
      NameValueCollection nvc = new NameValueCollection();
      if(ConfigurationSettings.AppSettings.Count>0){
        //ofcourse if ordinary appSettings available, let's read 'em
        nvc = _nvcConfig;
      }else{
        foreach(string key in _nvcConfig.Keys){
          //filter overall configuration bunch of 
          //_appSettings_path subject - 
          //either the default value (set within 
          //CustomConfigurationSettings class) or 
          //could be passed in constructor
          if(key.StartsWith(_appSettings_path)){
            //cut prefix (no use of now)
            nvc[key.Substring(_appSettings_path.Length+1)] = 
              _nvcConfig[key];
          }
        }
      }
      return nvc;
    }

6. and here we go...

    public static NameValueCollection AppSettings {
      get{
        if(_appSettings==null){
          _appSettings = GetAppSettingsConfig();
        }
        return _appSettings;
      }
    }

Afterword

Wondering... are you still reading this? ;)