(1 item) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(6 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(4 items) |
|
(2 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(5 items) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(8 items) |
|
(2 items) |
|
(7 items) |
|
(2 items) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(3 items) |
|
(2 items) |
|
(2 items) |
|
(8 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(6 items) |
|
(1 item) |
|
(2 items) |
|
(5 items) |
|
(5 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(16 items) |
|
(10 items) |
|
(27 items) |
|
(15 items) |
|
(15 items) |
|
(13 items) |
|
(16 items) |
|
(15 items) |
(Huh!)
Philip Haack recently linked to Craig Andera's article showing a generic configuration section handler.
Craig's example is undoubtedly an elegant way of providing a configuration section handler. However, I don't really like configuration section handlers much. My explanation of why has turned out to be quite large. Regular readers of this blog will be unsurprised, I suspect...
In case you've not come across these things, XML Configuration Section handlers can be used to extract information from a .NET application configuration file.
Any application can have a configuration file. In the case of an ASP.NET application, it's the web.config
file, and in most other apps, it's called SomeApp.exe.config
, where SomeApp.exe
is the
filename of the main executable. (Incidentally, the main executable doesn't actually have to be a .NET app. If it's an
unmanaged app that uses .NET components via a COM Callable Wrapper supplied by the interop layer, the CLR will
still look for a config file named for the main EXE.)
Various .NET Framework features can be configured through this file, such as the assembly resolver, or ASP.NET. You can also put your own name-value pairs in the file, e.g.:
<configuration> <appSettings> <add key='dbconn' value='Data Source=dbsrv;Initial Catalog=mydb;Integrated Security=SSPI";'/> </appSettings> </configuration>
This can then be retrieved using code like this:
using System; using System.Configuration; class DbUtils { internal static string ConnectionString { get { if (connStr == null) { connStr = ConfigurationSettings.AppSettings["dbconn"]; } return connStr; } } private static string connStr; }
In this particular case there's no need for a configuration section handler - we can just use the built in
ConfigurationSettings
class's AppSettings
property to retrieve stuff from
the <appSettings>
element.
So why would you use a configuration file section handler when you can just use <appSettings>
?
There are two good reasons you might want to avoid the <appSettings>
element.
One is that if you are configuring some reusable subsystem which might appear in multiple applications,
it's hard to guarantee the uniqueness of the key names. Another is that you might have a considerable
number of settings to configure, and it can be more convenient and compact to put it in a more specialised
XML structure. For example, when you compare it with how ASP.NET settings look in a web.config
file, the <appSettings>
element looks pretty ungainly.
The configuration section handler system provides a standard way of defining new section types for your configuration files. It essentially lets you do exactly the same thing as all of the various parts of the .NET framework that use the config file. (Craig's article covers the details admirably, so I won't say any more here.)
The main thing I dislike about XML configuration section handlers is that you have to put a bunch of cruft into the
configuration file to tell .NET about the handler. This has to appear in the <configSections>
element
at the start of the application's config file. This feels like the wrong place to me - I think
it should be down to the code that reads the config section in question to specify that information, rather
than forcing every single application to include it in their config files.
The parts of the .NET framework that use the config file get away without forcing the user
to add a <configSections>
element because the machine-wide machine.config
file
contains a <configSections>
element listing all the default sections. But for
custom sections, then unless you have some way of changing the machine.config
file
(which is typically a bad idea) the custom configuration section handler will have to be listed in
the application's config file.
That's pretty clunky. What I want to be able to do is have a config file that looks more or less like this:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <MyStuff> <Foo>1.234</Foo> <Bar>A bunch of information</Bar> </MyStuff> </configuration>
(Note the lack of a <configSections>
element.) And I want my code to look
something like this:
public class MyStuff { public double Foo; public string Bar; } ... MyStuff myStuff = (MyStuff) GenericConfig.GetConfig(typeof(MyStuff));
In other words, I don't want to have to put extra cruft in my configuration file. And in theory this is
easy to achieve - just write your own code to parse the configuration file. (In the code snippet above,
that would be the GenericConfig
class, which has supplanted the system
ConfigurationSettings
class.) This is a much simpler mechanism to use - the API is
about the same as before - just a different class name - and you avoid the usual boilerplate that
configuration section handlers require in the configuration file.
Sadly it doesn't work.
The CLR gets upset if you put elements it doesn't know about in the configuration file. So this would
appear to force you to put a <configSections>
element in there even if you are
prepared to write your own code to parse the whole file.
You might hope to get away with it if you could be absolutely sure that nothing else will try to load
the configuration file. However, lots of things do try to load it, and not necessarily when you'd expect. For example,
the XmlSerializer
class appears to do so - if you have unrecognized elements in your config file, it throws
a System.Configuration.ConfigurationException
when you first instantiate
an XmlSerializer
of any kind. This isn't documented. Who knows what other parts of the
.NET Framework Class Libraries may implicitly try to load the configuration file and throw an exception if it's
not valid? (It's at moments like these that I sometimes pine for Java's checked exception mechanism... Or at
least I would if I believed it could solve this problem fully.) And in any case, if the configuration file looks
invalid, this is likely to disable other uses of the file, such as assembly version redirects. So this isn't really
a viable approach.
Unfortunately, there doesn't seem to be any programmatic way of adding extra configuration section handlers at runtime. This is a little curious, because it doesn't look like custom section handlers ever get loaded automatically - they have to be asked for at runtime before they are loaded. So it's not like you wouldn't have the opportunity to register one programmatically. But there doesn't seem to be any API for doing this. I suppose the reason for this is that the CLR wants to be able to decide whether the configuration file is valid when it first loads it.
One way of working around this is to put a second configuration file in your application. For example,
as well as a MyProgram.exe.config
file, there's nothing stopping you from also having a
MyProgram.exe.moreconfig
file. And the following class will let you extract data from it:
using System; using System.IO; using System.Xml; using System.Xml.Serialization; public class GenericConfig { public static object GetConfig(Type t) { // The simplest thing to do here would be presume that we can // use the name MyApp.exe.config in the AppBase directory. // However, it's possible for the creator of the AppDomain to // supply a different configuration file. ASP.NET uses a // different name, for example (web.config). NUnit also // supplies a non-default config file path when testing DLLs. // // So to be on the safe side, we strip off the extension // (typically ".config") from the current configuration file // path, and replace it with ".moreconfig" AppDomainSetup domainInfo = AppDomain.CurrentDomain.SetupInformation; string originalConfigFile = domainInfo.ConfigurationFile; string configFileName = Path.GetFileNameWithoutExtension(originalConfigFile) + ".moreconfig"; string configFile = Path.Combine( Path.GetDirectoryName(originalConfigFile), configFileName); XmlDocument configDocument = new XmlDocument(); configDocument.Load(configFile); string configElementName = t.Name; XmlNode configNode = configDocument.SelectSingleNode( "/configuration/" + configElementName); if (configNode == null) return null; XmlSerializer configSerializer = new XmlSerializer(t); return configSerializer.Deserialize(new XmlNodeReader(configNode)); } }
Now we can use the code snippet from earlier:
MyStuff myStuff = (MyStuff) GenericConfig.GetConfig(typeof(MyStuff));
This works just fine without any configuration section handlers, because we're no longer putting things in the file that the CLR thinks it controls. But it's not ideal - having an extra configuration file looks messy. It's also likely to confuse people first time they see it, as this is a pretty non-standard idiom.
You might look at that previous example, and think that it would probably be less effort to put the
<configSections>
boilerplate in the configuration file. After all, it's shorter than that
class above. Moreover, even if you add together Craig's section handler class and the necessary
boilerplate, it still comes out slightly shorter than my code...
In some cases, it will be simpler to live with the boilerplate. It really depends on where you want to put the burden of effort. If the relevant configuration settings will only ever be used by a single application, you may as well go for the configuration section handler. This alternative approach only pays dividends if the same configuration settings are used in multiple applications.
So if you're writing a component that will be used in multiple applications, and which requires configuration
settings, then my GenericConfig
class only needs to be put in that component the once. This will
then save developers who use that component from the hassle of putting in the
<configSections>
boilerplate for every application.
Of course the weirdness of having two configuration files may well be enough to put you off even in this case.
As it happens, I've never actually shipped anything using the code above. It's simply that I've found configuration section handlers slightly irritating for some time now, and this is as far as I've got with trying to come up with something better. I don't think I'm there yet.
But then should you be using the config file at all?
There is a tendency for the application configuration file to be overused. A lot of people try to put user settings in there. This is a really bad idea for two reasons.
First, to be able to save information in the application configuration file, users will require write access to it. Normal user accounts won't have that - application installation directories are locked down, and with good reason. So by using this technique for user settings, you would be forcing users to run as members of either the Power Users or the Administrators group. This is bad.
Second, it doesn't work in multi-user scenarios. And if you think these are edge cases that only concern relatively rare terminal services environments, think again. Desktop systems may well have multiple different people logging into them over time. In fact in Windows XP, they may even be logged in simultaneously. (Unless you're a member of a domain, in which case fast user switching is disabled. Grr!)
It's also not that easy - there's no API for modifying the config file. This is not an error of omission though - it's to discourage you from doing it, because it's a bad idea.
The config file is really only for settings chosen at deployment time which are unlikely to change thereafter. For example, they're probably a reasonable choice for database connection information, so that you can configure your development, test, staging, and production servers to point at different databases.
But for anything else, I'd recommend considering the Configuration Management Application Block for .NET.