Auto realod application config in .NET

Reflect config file changes in application in a runtime

  • Share

In .NET working with configuration is really easy and there are bunch of .NET classes that rely on config values so you do not have to specify values in their constructors. For me it a common practice to instantiate database connections of WCF client by only creating an instance with parameterless constructor while keeping values in config file.

In case you have to use these kind of classes in a long running process, like windows service on which other processes are depending on. Even a fast restart may cause some issues if service is heavily loaded.

ConfigurationManer class has implemented method RefreshSection which will reload the section of the config file on demand from code. Unfortunately there is no method to reload whole config file.

It is almost impossible that in your app you will use all sections, so lets focus reloading only the ones you will need. You would have to keep this list of sections somewhere. You could hard-code, but let's avoid that as it is never a good practice. Instead of hard coding I decided to keep it in config file as well, so you can even add more sections to be reloaded on file change.

I used appConfig to add one more key which will be used by monitor class instance.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="monitredSections" value="appSettings"/>
  </appSettings>
</configuration>
    

No to monitor file I used FilesystemWatcher class to raise an event when file is changed and load sections defines in monitredSections key as comma separated values.

Note

FilesystemWatcher class has a known issue of raising more than one event when file is changed. Even if you set NotificationFilter it still raises more than one event when file is changed. To avoid multiple events I used time difference to check last changed and ignore event if last one was raised less than 5 seconds ago.

    public class ConfigMonitor
    {
        FileSystemWatcher watcher;
        DateTime lastChange;

        public ConfigMonitor()
        {
            lastChange = DateTime.MinValue;
            string configFile = string.Concat(System.Reflection.Assembly.GetEntryAssembly().Location, ".config");
            if (File.Exists(configFile))
            {
                watcher = new FileSystemWatcher(Path.GetDirectoryName(configFile), Path.GetFileName(configFile));
                watcher.EnableRaisingEvents = true;
                watcher.Changed  = watcher_Changed;
            }
        }

        void watcher_Changed(object sender, FileSystemEventArgs e)
        {
            if ((DateTime.Now - lastChange).Seconds > 5) //Avoid code executing multiple times
            {
                IEnumerable<string> sections = ConfigurationManager.AppSettings["monitredSections"].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                if (sections.Any())
                {
                    foreach (string section in sections)
                    {
                        ConfigurationManager.RefreshSection(section);
                    }
                }
                lastChange = DateTime.Now;
            }
        }
    }

    

This code will work fine in case you are changing the file on the same machine. However, sometime you might want to modify file on your local machine where you have all the tools like Visual Studio for example which most likely you will not have on the production server when service might be running.

In case you decide to replace the file with the copy from your machine over Remote Desktop Connection, there might be file in use exception as Windows will steel keep the lock on file while FilesystemWatcher is raising events.

For this case you need to check if the file is free for reading. This can be simply dine by trying to open file for reading with a FileStream.

    public class ConfigMonitor
    {
        FileSystemWatcher watcher;
        DateTime lastChange;

        public ConfigMonitor()
        {
            lastChange = DateTime.MinValue;
            string configFile = string.Concat(System.Reflection.Assembly.GetEntryAssembly().Location, ".config");
            if (File.Exists(configFile))
            {
                watcher = new FileSystemWatcher(Path.GetDirectoryName(configFile), Path.GetFileName(configFile));
                watcher.EnableRaisingEvents = true;
                watcher.Changed += watcher_Changed;
            }
        }

        void watcher_Changed(object sender, FileSystemEventArgs e)
        {
            if ((DateTime.Now - lastChange).Seconds > 5 && !IsFileLocked(e.FullPath)) //Avoid code executing multiple times
            {
                IEnumerable<string> sections = ConfigurationManager.AppSettings["monitredSections"].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                if (sections.Any())
                {
                    foreach (string section in sections)
                    {
                        ConfigurationManager.RefreshSection(section);
                    }
                }
                lastChange = DateTime.Now;
            }
        }

        private bool IsFileLocked(string filePath)
        {
            FileStream stream = null;
            try
            {
                stream = File.OpenRead(filePath);
            }
            catch (IOException)
            {
                return true;
            }
            finally
            {
                if (stream != null)
                    stream.Close();
            }
            return false;
        }
    }
    

References

  • Share

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.

Comments for this article

comments powered by Disqus