Custom ConfigurationSettings through configuration resource embedding






4.21/5 (8 votes)
May 4, 2004
3 min read

76622

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? ;)