C#: Watching an app.config file for changes (and responding)

Hello World,

We had this windows service that had configuration in it’s app.config that governed it’s behaviour. A requirement from this service was that it should watch the config file and respond to changes made without having to be re-started (a restart would re-load the app domain and re-read the config anyways)
Of several possible solutions, my approach was to watch the file for changes using .NET’s FileSystemWatcher class and then consume it’s OnChange event to refresh/reload config values.

Draft 1: A prototype console application that implements the above solution
   1:  class Program
   2:      {
   3:          private static bool _stopService = false;
   4:   
   5:          static void Main(string[] args)
   6:          {
   7:              Console.WriteLine("Starting Worker Process");
   8:              Thread myWorkerThread = new Thread(Run) { Name = "Run Worker Thread" };
   9:              myWorkerThread.Start();
  10:              Console.WriteLine("Press any key to stop...");
  11:              Console.ReadLine();
  12:              _stopService = true;
  13:          }
  14:   
  15:          static void Run()
  16:          {
  17:              string assemblyDirectory = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
  18:              NotifyFilters notifyFilters = NotifyFilters.LastWrite; 
  19:   
  20:              FileSystemWatcher fileSystemWatcher = new FileSystemWatcher() { 
										Path = assemblyDirectory, 
										NotifyFilter = notifyFilters, 
										Filter = "*.config" };
  21:              fileSystemWatcher.Changed += OnChanged;
  22:              fileSystemWatcher.EnableRaisingEvents = true;
  23:   
  24:              Console.WriteLine("Watching for changes...");
  25:              while (!_stopService)
  26:              {
  27:                  Thread.Sleep(5 * 1000);
  28:              }
  29:          }        
  30:   
  31:          static void OnChanged(object source, FileSystemEventArgs e)
  32:          {
  33:              Console.WriteLine("Change event handler invoked...");
  34:              Console.WriteLine("Value before refresh: {0}", ConfigurationManager.AppSettings["myKey"]);
  35:              ConfigurationManager.RefreshSection("appSettings");
  36:              Console.WriteLine("Value after change: {0}", ConfigurationManager.AppSettings["myKey"]);
  37:          }
  38:      }
 
The application config file looks like this:
   1:  <xml version="1.0" encoding="utf-8" ?>
   2:  <configuration>
   3:    <appSettings>
   4:      <add key="myKey" value="testValue"/>
   5:    <</appSettings>
   6:  <</configuration>

Notes on the implementation:

  1. By launching the FileSystemWatcher on a worker thread, I am attempting to simulate the scenario in the actual service, so ignore those bits for now
  2. Line number 22 flags the FileSystemWatcher to raise events, needless to say, if you miss setting this flag, the events would never be raised
  3. You can get details of what has triggered the event in terms of full path of the object that was watched, change type (viz. changed, deleted, created etc.) through the FileSystemEventArgs

Issues:
When run, if you modify the app.config in your program’s output directory and save it, you’ll see the following output:

Did you see? The OnChanged event has been raised twice for a single change. I began researching this and found that this is a known behaviour (by design) as documented stackoverflow and here

Now, the resolution (at least the one I got to work):
Draft 2: In the OnChanged event, use a try finally block to set EnableRaisingEvents to false (in the try) and then set it back to true in the finally block:

   1:  class Program
   2:      {
   3:          private static bool _stopService = false;
   4:          private static FileSystemWatcher _fileSystemWatcher;
   5:   
   6:          static void Main(string[] args)
   7:          {
   8:              Console.WriteLine("Starting Worker Process");
   9:              Thread myWorkerThread = new Thread(Run) { Name = "Run Worker Thread" };
  10:              myWorkerThread.Start();
  11:              Console.WriteLine("Press any key to stop...");
  12:              Console.ReadLine();
  13:              _stopService = true;
  14:          }
  15:   
  16:          static void Run()
  17:          {
  18:              string assemblyDirectory = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
  19:              NotifyFilters notifyFilters = NotifyFilters.LastWrite; 
  20:   
  21:              _fileSystemWatcher = new FileSystemWatcher() { 
			Path = assemblyDirectory, 
			NotifyFilter = notifyFilters, 
			Filter = "*.config" };
  22:              _fileSystemWatcher.Changed += OnChanged;
  23:              _fileSystemWatcher.EnableRaisingEvents = true;
  24:   
  25:              Console.WriteLine("Watching for changes...");
  26:              while (!_stopService)
  27:              {
  28:                  Thread.Sleep(5 * 1000);
  29:              }
  30:          }        
  31:   
  32:          static void OnChanged(object source, FileSystemEventArgs e)
  33:          {
  34:              try
  35:              {
  36:                  _fileSystemWatcher.EnableRaisingEvents = false;
  37:                  Console.WriteLine("Change event handler invoked...");
  38:                  Console.WriteLine("Value before refresh: {0}", ConfigurationManager.AppSettings["myKey"]);
  39:                  ConfigurationManager.RefreshSection("appSettings");
  40:                  Console.WriteLine("Value after change: {0}", ConfigurationManager.AppSettings["myKey"]);
  41:              }
  42:              finally
  43:              {
  44:                  _fileSystemWatcher.EnableRaisingEvents = true;
  45:              }
  46:          }
  47:      }

So now the output looks like this:

Great! Another day saved!

Happy Coding!

Advertisements
Posted in .NET, C#

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

  • Comic for July 25, 2017
    Dilbert readers - Please visit Dilbert.com to read this feature. Due to changes with our feeds, we are now making this RSS feed a link to Dilbert.com.
%d bloggers like this: