Extraction of configuration from custom configuration file






4.95/5 (7 votes)
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. UsesIValueExtractor
specified by client. Configuration class is created viaActivator.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
<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:
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):
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:
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:
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:
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:
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:
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:
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:
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.