Click here to Skip to main content
15,895,807 members
Articles / Programming Languages / C#

Extraction of configuration from custom configuration file

Rate me:
Please Sign up or sign in to vote.
4.95/5 (7 votes)
14 Oct 2014CPOL4 min read 17.2K   22   5
Library for extracting configuration settings from custom configuration file is provided.

Updates

Updated Oct 13, 2014 - added set functionality, so you can now write values to configuration file.

Introduction

App.config is very useful in most of .NET applications. Although this file is enough for many applications, there are cases when it's reasonable to divide your configuration into several files. There also can be a situation when it's more natural to present configuration as plain text rather than xml file. Current library is created for easy extraction of configuration in this case and allows you to change extraction behavior so that you can extract configuration from custom configuration files of different structure.

Background

The idea of the library is rather simple: client has some class whose properties (which should be extracted from configuration file) are marked by special attribute. Configuration extractor then creates the instance of the class and extracts values for these properties. This idea has much in common with that of ConfigurationPropertyAttribute. You can find more info on usage of ConfigurationPropertyAttribute at MSDN and Code Project.

Code structure

There are 4 essential parts of the ConfigurationExtractor library:

  • ConfigurationValueAttribute. Allows you to mark properties you want to be extracted from configuration file. You can set alias for the property and type of converter to convert from string representation.
  • ConfigurationExtractor. Generic type that allows you to create an instance of your configuration class. Uses IValueExtractor specified by client. Configuration class is created via Activator.CreateInstance() so it must have default constructor.
  • IValueExtractor. Allows you to create key-value pairs from the given text where key is property name or alias and value is it's value in string representation.
  • IStringConverter. Provides a conversion from string representation to actual value. Concrete implementations of this interface must have a default constructor.

Library contains several implementations of IValueExtractor for value extraction from plain text (PlainTextExtractor), plain text divided into several sections (PlainTextSectionExtractor) and xml structured text (XmlExtractor). It also contains some implementations of IStringConverter for the most typical conversion to Int32, Int64, Double, Decimal, DateTime etc. Client can create his own implementations (some examples are shown in Example console application shipped with the source code and in Using the code section).

Here are some examples of what is meant by different types of text in configuration file.

Plain text:

a=123
b=456

Plain text with sections:

[sectionA]
a=123

[sectionB]
b=456

XML

XML
<config>
<a>123</a>
<b>456</b>
</config>

Using the code

Here are some examples of using the code. The same examples can be found in Example application in source code.

First, simple configuration is considered. Simple here means that configuration consists of some values of standard types. For example:

C#
class Config
{
    [ConfigurationValue(LongName = "BoolVal", ShortName = "BVal", 
      ConverterType = typeof(StringBoolConverter), DefaultValue = "False")]
    public bool BoolValue { get; set; }

    [ConfigurationValue(LongName = "DoubleVal", ShortName = "DVal", 
      ConverterType = typeof(StringDoubleConverter), DefaultValue = "36.6")]
    public double DoubleValue { get; set; }
}

The instance of this class can be achieved the following way (given the plain text configuration file):

C#
ConfigurationExtractor<Config> converter = 
  new ConfigurationExtractor<Config>(new PlainTextExtractor());

string confFile = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), 
                                         "PlainTextConfig.cfg");
var conf = converter.Convert(System.IO.File.ReadAllText(confFile));

Plain text configuration file for this case can contain the following text:

BoolVal=True
DVal=12.2

If some more complex configuration is needed than client has to modify value extraction by creating new implementation of IStringConverter. For example, you can easily create list of elements for your configuration. Consider the following configuration class:

C#
class ExtendedConfig
{
    [ConfigurationValue(LongName = "MonthsList", ShortName = "M", 
      ConverterType = typeof(StringMonthsConverter), DefaultValue = "Jan,Sep")]
    public List<Months> Months { get; set; }
}

enum Months
{
    Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
}

You can extract list of values using following configuration:

M=Jan,Feb,Oct,Sep

and following converter:

C#
public class StringMonthsConverter: IStringConverter
{
    static Dictionary<string, Months> _corresp = new Dictionary<string, Months>
    {
        {"Jan", Months.Jan},
        {"Feb", Months.Feb},
        {"Mar", Months.Mar},
        {"Apr", Months.Apr},
        {"May", Months.May},
        {"Jun", Months.Jun},
        {"Jul", Months.Jul},
        {"Aug", Months.Aug},
        {"Sep", Months.Sep},
        {"Oct", Months.Oct},
        {"Nov", Months.Nov},
        {"Dec", Months.Dec},
    };

    public object Convert(string obj)
    {
        string[] strings = obj.Split(',');

        return strings.Select(x => _corresp[x]).ToList();
    }
}

Extraction itself remains the same:

C#
ConfigurationExtractor<ExtendedConfig> converter = 
  new ConfigurationExtractor<ExtendedConfig>(new PlainTextExtractor());

string confFile = System.IO.Path.Combine(
  System.IO.Directory.GetCurrentDirectory(), "ExtendedConfig.cfg");
var conf = converter.Convert(System.IO.File.ReadAllText(confFile));

The most complex case is when you have some complex types in configuration class. In this case values inside complex type can be also regarded as configuration values. For example:

C#
class ComplexConfig_Text
{
    [ConfigurationValue(LongName = "connection", ShortName = "con", 
      ConverterType = typeof(ConnectionConfigConverter), 
      DefaultValue = PlainTextExtractor.DefaultValue)]
    public ConnectionConfig ConnectionConfig { get; set; }
    [ConfigurationValue(LongName = "user", ShortName = "user", 
      ConverterType = typeof(UserConfigConverter), 
      DefaultValue = PlainTextExtractor.DefaultValue)]
    public UserConfig UserConfig { get; set; }
}

class ConnectionConfig
{
    [ConfigurationValue(LongName = "Address", 
      ShortName = "Adr", DefaultValue = "localhost")]
    public string RemoteAddress { get; set; }
}

class UserConfig
{
    [ConfigurationValue(LongName = "UserID", ShortName = "user", 
      ConverterType = typeof(StringStringSpecialConverter), 
      DefaultValue = StringStringSpecialConverter.Empty)]
    public string UserID { get; set; }
    [ConfigurationValue(LongName = "Password", ShortName = "pwd", 
      ConverterType = typeof(StringStringSpecialConverter), 
      DefaultValue = StringStringSpecialConverter.Empty)]
    public string Password { get; set; }
}

(Note that PlainTextExtractor.DefaultValue is used for default value of ConnectionConfig and UserConfig. This default value is simply an empty string, so when default value is applied this configuration classes are created as if there were no configuration for them. This allows RemoteAddress, UserID, Password to get their initial values as specified in ConfigurationValueAttribute.)

Configuration file for this case can be plain text with sections:

[connection]
Address=google.com

[user]
UserID=Admin
Password=12345

One needs to use PlainTextSectionExtractor to extract value for ComplexConfig_Text and to create special converters for extraction of ConnectionConfig and UserConfig. Since ConnectionConfig and UserConfig are configuration classes themselves, these converters should use ConfigurationExtractor with PlainTextExtractor to extract their values. Here how it looks like:

C#
class ConnectionConfigConverter : IStringConverter
{
    public object Convert(string obj)
    {
        return new ConfigurationExtractor<ConnectionConfig>(
                       new PlainTextExtractor()).Convert(obj);
    }
}

class UserConfigConverter : IStringConverter
{
    public object Convert(string obj)
    {
        return new ConfigurationExtractor<UserConfig>(
                      new PlainTextExtractor()).Convert(obj);
    }
}

The extraction of ComplexConfig_Text looks the following way:

C#
ConfigurationExtractor<Example.Text.ComplexConfig_Text> converter = 
  new ConfigurationExtractor<Example.Text.ComplexConfig_Text>(
  new PlainTextSectionExtractor());

string confFile = System.IO.Path.Combine(
  System.IO.Directory.GetCurrentDirectory(), "ComplexTextConfig.cfg");
var conf = converter.Convert(System.IO.File.ReadAllText(confFile));

Set functionality

(Updated October 13, 2014)

In addition to "get" functionality I have also added "set" functionality, so extracted configuration can also be converted back to string and written down to config file. Interface IConverter<T> now has new method ConvertBack which converts values back to their string representations. ConfigurationExtractor<T> class now implements IConverter<T> interface.

Example of usage new functionality:

C#
ConfigurationExtractor<Example.Text.ComplexConfig_Text> converter = 
  new ConfigurationExtractor<Example.Text.ComplexConfig_Text>(
  new PlainTextSectionExtractor());

// Configuration file to read from
string confFile = System.IO.Path.Combine(
  System.IO.Directory.GetCurrentDirectory(), "ComplexTextConfig.cfg");
// Configuration file to write to
string confFile_out = System.IO.Path.Combine(
  System.IO.Directory.GetCurrentDirectory(), "ComplexTextConfig_out.cfg");

// Read config from file
var conf = converter.Convert(System.IO.File.ReadAllText(confFile));

// Write config to file
System.IO.File.WriteAllText(
  confFile_out, converter.ConvertBack((Example.Text.ComplexConfig_Text)conf));

Since method names didn't change and no methods/properties were removed, new version should be compatible with previous one.

 

Discussion

 

Solution provided in this article is not very general but I like it for it's simplicity. It's easy to extend it's functionality to gain desirable data in the form you want it. In addition it provides you with unified way to deal with external configuration files (as opposed to app.config which is considered as internal). Some functionality is worth being added to this library: it provides only "get" functionality and it would be nice to have some "set" one, i.e. the possibility to rewrite properties in the configuration file.

(Update from October 13, 2014)

"Set" functionality was added. Just to add couple of words about the library - I used it at work for some projects and it seems to be useful, main benefit it gives is a unification of methods used by you or members of your team for extracting configuration files.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Junior)
Russian Federation Russian Federation
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
fredatcodeproject15-May-13 1:03
professionalfredatcodeproject15-May-13 1:03 
GeneralRe: My vote of 5 Pin
Serdar Kurbanov15-May-13 19:57
Serdar Kurbanov15-May-13 19:57 
GeneralMy vote of 4 Pin
fredatcodeproject14-May-13 6:15
professionalfredatcodeproject14-May-13 6:15 
GeneralRe: My vote of 4 Pin
Serdar Kurbanov14-May-13 8:58
Serdar Kurbanov14-May-13 8:58 
GeneralRe: My vote of 4 Pin
Serdar Kurbanov14-May-13 9:40
Serdar Kurbanov14-May-13 9:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.