October 30, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

VC++ Cures for .NET Configuration Change Headaches, Part 2

  • October 3, 2005
  • By Nick Wienholt
  • Send Email »
  • More Articles »

My previous article presented the various options for reloading changed configuration settings without restarting an application domain where covered. While 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, since 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. (Click here to download the accompanying sample code.)

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, as 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 derived 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 needs 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;

}

};

The final piece of the implementation is to respond to the change in the configuration file. The XmlNode pointer that has been stored as a member variable isn't much help, as the changes in the underlying XML configuration file would have invalidated the data it holds. You can use the name of the XML section to locate the appropriate node in a reloaded XML document, and once you've accomplished this, you can again use NameValueSectionHandler to re-parse the XML:

void watcher_Changed(Object* sender, FileSystemEventArgs* e)

{

//load configuration file

XmlDocument* doc = new XmlDocument();

doc->Load(_configFileName);

//find root of our config section

XmlNodeList* nodes = doc->GetElementsByTagName(_section->Name);

if (nodes->Count == 0)

return;

NameValueCollection* newConfigData =

dynamic_cast<NameValueCollection*>(

_handler->Create(_parent, _configContext, nodes->Item(0)));

//copy data into existing collection

_configData->Clear();

String* keys __gc[] = newConfigData->AllKeys;

for(int ix = 0; keys->Length > ix; ++ix)

{

String* key = keys[ix];

String* value = newConfigData->get_Item(key);

_configData->Add(key, value);

}

}

Notice that the new configuration data is actually copied into the original NameValueCollection. This ensures that code that is using the reloading configuration handler does not need to worry about when the configuration data changes—the data is automatically changed in the NameValueCollection object. If the code has retrieved a value out of the collection, it will not update this data.

To use a custom handler, you must nominate the type and assembly containing the handler at the top of the configuration file, as well as the name of the XML element that this handler will parse. The XML node containing the configuration data then needs to be placed beneath the configuration element:

<configuration>

<configSections>

<section name="reloadableSection"

type="ReloadableConfiguration.Handler,ReloadableConfiguration" />

</configSections>

<reloadableSection>

<add key="keynw" value="valuenw" />

</reloadableSection>

</configuration>

Retrieving data from the custom handler from application code can be accomplished in a single line of code:

NameValueCollection* config =

dynamic_cast<NameValueCollection*>

(ConfigurationSettings::GetConfig(S"reloadableSection"));

One Important Precaution

Configuration data that is retrieved from sections of the XML document that are not parsed by the custom handler are not reloaded when the configuration file changes. Outside of this restriction, the custom configuration file handler sample presented is a simple and effective technique to overcome the limitation of the .NET Framework's configuration file handling.

Download the Code

To download the sample code for this article, click here.

About the Author

Nick Wienholt is an independent Windows and .NET consultant based in Sydney, Australia. He is the author of Maximizing .NET Performance from Apress, and specializes in system-level software architecture and development with a particular focus on performance, security, interoperability, and debugging. Nick can be reached at NickW@dotnetperformance.com.






Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel