My previous article presented the various options for reloading changed configuration settings without restarting an application domain where covered. Although none of the solutions was a silver bullet, the article showed the Configuration Application Block from Microsoft’s Enterprise Library to be a viable option. However, because the Configuration Application Block is not a simple replacement for the System.Configuration namespace’s built-in configuration infrastructure, it imposes a lot of re-work on existing applications.
This article presents a Visual C++ assembly with a custom configuration handler that makes achieving reloadable configuration information much easier.
The .NET Framework has a highly extensible configuration system. You can nominate a custom type that will parse a section of XML in the configuration file and return an Object pointer that represents some form of configuration information. The application code that uses this information can then cast the Object pointer to the appropriate type and use it as required. This form of configuration extension is enabled through custom configuration file handlers, and it has many obvious advantages over rewriting an entire configuration system.
The .NET Framework Library ships with a number of custom configuration handlers, such as NameValueSectionHandler, which returns a NameValueCollection pointer as its configuration data, and DictionarySectionHandler, which returns a Hashtable pointer. Both of these types suffer from the same issue as ConfigurationSettings::AppSettings, in that data changes in the configuration file are not reloaded until an application domain is recycled. However, these built-in types can be leveraged to build a custom configuration handler that will reload configuration data when the underlying configuration file changes.
The most obvious form of extension would be to derive a new type from one of the built-in handlers that provides reload support. NameValueCollection is the most likely candidate for derivation because its usage pattern closely matches that of AppSettings. Unfortunately, the virtual method that needs to be overridden to hook in the reloading behavior is marked with the final method attribute, which means that you cannot derive from it. Rather than use derivation, you can store a NameValueCollection pointer as a member variable and build reloading on top of its XML-parsing functionality.
To implement a custom handler, you need to implement the IConfigurationSectionHandler interface. This interface has a single method—Create—that the configuration infrastructure uses to retrieve the configuration data from the custom handler and pass it to the application code that requires the data. A skeleton handler would look like the following code snippet:
public __gc class Handler: public IConfigurationSectionHandler { public: virtual Object* Create(Object* parent, Object* configContext, XmlNode* section) { return 0; } };
The handler would use the XmlNode pointer that is passed in as a parameter to parse the XML contained in this node and then return an Object pointer that represented the de-serialized form of this data. The reloading handler has no special requirements for the XML or the configuration data holder, and you can build the implementation entirely on top of the NameValueSectionHandler, as shown here:
public __gc class Handler: public IConfigurationSectionHandler { private: NameValueSectionHandler* _handler; NameValueCollection* _configData; public: virtual Object* Create(Object* parent, Object* configContext, XmlNode* section) { //load original config data; _handler = new NameValueSectionHandler(); NameValueCollection* configData = dynamic_cast<NameValueCollection*> (_handler->Create(parent, configContext, section)); _configData = new NameValueCollection(configData); return _configData; } };
This code simply passes on all the work to a NameValueSectionHandler member variable and returns whatever this object parses from the XML. On top of this functionality, the custom handler needs to monitor changes to the configuration file and get the NameValueSectionHandler member variable to reload its data when it detects a change. The FileSystemWatcher class can monitor the configuration file, and when the data is reloaded, it will need the original values passed in to the Create method and, hence, need to be stored in member variables. The handler now looks like this (new code in italics):
public __gc class Handler: public IConfigurationSectionHandler { private: NameValueSectionHandler* _handler; NameValueCollection* _configData; Object* _parent; Object* _configContext; XmlNode* _section; String* _configFileName; void watcher_Changed(Object* sender, FileSystemEventArgs* e) { } public: virtual Object* Create(Object* parent, Object* configContext, XmlNode* section) { //setup config file watch _configFileName = AppDomain::CurrentDomain-> SetupInformation->ConfigurationFile; FileSystemWatcher* watcher = new FileSystemWatcher(Path::GetDirectoryName(_configFileName), Path::GetFileName(_configFileName)); watcher->EnableRaisingEvents = true; watcher->Changed +=new FileSystemEventHandler(this, &Handler::watcher_Changed); //store values for re-read _parent = parent; _configContext = configContext; _section = section; //load original config data; _handler = new NameValueSectionHandler(); NameValueCollection* configData = dynamic_cast<NameValueCollection*> (_handler->Create(parent, configContext, section)); _configData = new NameValueCollection(configData); return _configData; } };