Wednesday, October 12, 2011

Refreshing Settings and dll.config Files

Having worked on plugin-based architecture and projects involving a number of assemblies there has always been a bit of a bad smell to .Net's implementation for configuration settings files; In particular, the use of Settings.Default. I really like how Settings.Default wraps configuration settings presenting them as strongly typed values. What I hate about it is how it uses the DefaultSettingValueAttribute to specify the default value in the event that a setting cannot be loaded. Why is Settings.Default a problem?

1) Because it hides problems with configuration. Essentially it means if you don't have a configuration setting specified, it will use the attributed value by default. This can lead to spitting venom at the computer as you try to spot a type-o in a the configuration XML six months after the fact when that mail server name changes and you realize it's been using the attributed value and absolutely refuses to accept your configuration setting.

2) Because at runtime you may want to have a service running and be able to change configuration settings and have the service pick up those changes. Unfortunately you're out of luck because while user settings can be reloaded, application settings cannot. The service needs to be stopped and restarted to accept the new configuration settings.

3) Because in cases where you have support assemblies in your project, the pain comes in that while it might be nice and sensible to have each assembly's settings located in their .dll.config file (which Visual Studio produces for you) by default, Settings.Default will only look at settings in the calling application's .exe.config file, even if you place the .dll.config file in the runtime folder. This means if you have 30 configuration settings across six assemblies, you're manually copying across 6 configuration sections and 30 settings into the app's .exe.config file. Now you may be perfectly able to build an NAnt task to do this, but it leaves you with a painfully large and convoluted configuration mess in the .exe.config file.

Now all of these problems can be avoided if you choose to load and parse configuration settings yourself using ConfigurationManager and the like. However, you lose that nice encapsulation that Settings does give you. There may also be solutions on the web for one or more of these issues, but I never was able to find one.

So I set about to change that.

Source Code

SettingsExtension.cs is an extension method you can utilize to refresh any Settings instance from it's assembly's .config file. This means if you have a DLL containing a Settings & associated app.config section and deploy the .dll.config file, you can call the Refresh() method to retrieve the values at any time from the .dll.config file. It works for .exe.config files as well, however calling it on a DLL's Settings will not refresh from .exe.config settings if you've modified settings there. The DLL only discovers its own configuration file.

What this means is that if you have a project with an EXE and X number of supporting DLLs, and some of those DLLs want to use configuration settings, you can copy the .dll.config files into your deployment and as long as they have a call to Settings.Default.Refresh() (I.e. from a static Constructor) you can ensure that that DLL will always use the values from its .dll.config file.

Additionally if you want to get creative, you can now set up a File Watcher on a .exe.config file (or .dll.config files) and when it detects a change, call the .Refresh() on the Settings.Default instance to reload those changes.

Looking through the code it should be pretty easy to see what it's doing. It looks for the Setting's assembly's config file, applicationSettings section group, and section for settings, then uses reflection to go through all known settings properties and update them with whatever is found in the file.

An added feature is support for an optional Refresh Listener which the refresh method can communicate back statuses about what it is processing and if it encounters any errors. Exceptions are non-fatal, the tool will report them back and continue. The reporting includes a message and a TraceLevel as an indication of what went wrong. Simply register a method using SettingsExtensions.InitializeListener() method in order to receive messages.

I've tested the implementation with most Settings-supported data types but there might be a few that need some special parsing.

No comments:

Post a Comment