Click here to Skip to main content
15,886,637 members
Please Sign up or sign in to vote.
4.00/5 (1 vote)
See more:
I am using FileSystemWatcher to monitor any changes in the app.config file. And also, writing to the config file.

Here is my code :

MyApplication is the main project and DataCache is a clas library referenced in MyApplication.


C#
using System;
using System.Threading;
using System.IO;
namespace MyApplication
{  

  public class Program
  {
    public static string rootFolderPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
    public static string configFilePath = Path.Combine(rootFolderPath, "MyApplication.exe.config");static void Main(string[] args)
    {
        //First Time initializing the config file.
        ConfigFileMonitor.SetConfigFileAtRuntime(configFilePath);

        //Initializing the config file monitor on a separate thread.
        Thread monitorThread = new Thread(ConfigFileMonitor.BeginConfigFilesMonitor);
        monitorThread.Start();

        WriteToConfigFile();

    }

    static void WriteToConfigFile()
    {
        Console.WriteLine(String.Format("Hello {0} {1}", DataCache.Section1Data.FirstNameString, DataCache.Section1Data.LastNameString));

        string _firstName, _lastName = string.Empty;
        do
        {
            Console.WriteLine("");
            Console.Write("First Name : ");
            _firstName = Console.ReadLine();

            Console.Write("Last Name : ");
            _lastName = Console.ReadLine();

            if(_firstName.Length>0)
                DataCache.Section1Data.FirstNameString = _firstName;

            if(_lastName.Length>0)
                DataCache.Section1Data.LastNameString = _lastName;

            Console.WriteLine(String.Format("Hello {0} {1}", DataCache.Section1Data.FirstNameString, DataCache.Section1Data.LastNameString));
        }
        while (true);
    }
  }
}


using System;
using System.IO;

namespace MyApplication
{
    /// <summary>
    /// Monitors for any change in the app.config file
    /// </summary>
    public class ConfigFileMonitor
    {
         private static FileSystemWatcher _watcher;
         private static string _configFilePath = String.Empty;
         private static string _configFileName = String.Empty;

        /// <summary>
    /// Texts the files surveillance.
    /// </summary>
    public static void BeginConfigFilesMonitor()
    {
        try
        {
            string fileToMonitor = Program.configFilePath;
            if (fileToMonitor.Length == 0)
                Console.WriteLine("Incorrect config file specified to watch");
            else
            {
                _configFileName = Path.GetFileName(fileToMonitor);
                _configFilePath = fileToMonitor.Substring(0, fileToMonitor.IndexOf(_configFileName));
            }
            // Use FileWatcher to check and update only modified text files.
            WatchConfigFiles(_configFilePath, _configFileName);
        }
        catch (Exception e1)
        {
            Console.WriteLine(e1.Message);
        }
    }

    /// <summary>
    /// Watches the files.
    /// </summary>
    /// <param name="targetDir">The target dir.</param>
    /// <param name="filteredBy">The filtered by.</param>
    static void WatchConfigFiles(string targetDir, string filteredBy)
    {
        try
        {
            _watcher = new FileSystemWatcher();
            _watcher.Path = targetDir;
            _watcher.Filter = filteredBy;
            _watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite;
            _watcher.EnableRaisingEvents = true;

            _watcher.Changed += new FileSystemEventHandler(FileChanged);
            _watcher.WaitForChanged(WatcherChangeTypes.Changed);
        }
        catch (Exception e1)
        {
            Console.WriteLine(e1.Message);
        }
    }


    /// <summary>
    /// Handles the Changed event of the File control.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.IO.FileSystemEventArgs"/> instance containing the event data.</param>
    protected static void FileChanged(object sender, FileSystemEventArgs e)
    {
        try
        {
            _watcher.EnableRaisingEvents = false;

            string filechange = e.FullPath;

            Console.WriteLine("Configuration File: " + filechange + "changed");

            //Since the file is changed - We have reload the configuration settings again.
            SetConfigFileAtRuntime(Path.Combine(Program.rootFolderPath, "MyApplication.exe.config"));

            Console.WriteLine(String.Format("New Name : {0} {1}", DataCache.Section1Data.FirstNameString, DataCache.Section1Data.LastNameString));

        _watcher.EnableRaisingEvents = true;
        }
        catch (Exception e1)
        {
            Console.WriteLine(e1.Message);
        }
    }

    /// <summary>
    /// Sets the config file at runtime.
    /// </summary>
    /// <param name="configFilePath"></param>
    public static void SetConfigFileAtRuntime(string configFilePath)
    {
        string runtimeconfigfile;
        try
        {
            if (configFilePath.Length == 0)
                Console.WriteLine("Please specify a config file to read from ");
            else
            {
                runtimeconfigfile = configFilePath;
                _configFileName = Path.GetFileName(configFilePath);
                _configFilePath = configFilePath.Substring(0, configFilePath.IndexOf(_configFileName));
            }

            // Specify config settings at runtime.
            //System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            DataCache.DataConfigSection.config = System.Configuration.ConfigurationManager.OpenExeConfiguration(configFilePath);
            //Similarly you can apply for other sections like SMTP/System.Net/System.Web etc..
            //But you have to set the File Path for each of these
            //config.AppSettings.File = runtimeconfigfile;

            //This doesn't actually going to overwrite you Exe App.Config file.
            //Just refreshing the content in the memory.
            DataCache.DataConfigSection.config.Save(System.Configuration.ConfigurationSaveMode.Modified);

            //Refreshing Config Section
            //ConfigurationManager.RefreshSection("appSettings");
            System.Configuration.ConfigurationManager.RefreshSection("MySectionGroup/Section1");                

            DataCache.Section1Data.configSection1 = null;
        }
        catch (Exception e1)
        {
            Console.WriteLine(e1.Message);
        }
    }
 }
}

<?xml version="1.0"?>
<configuration>
  <configSections>
    <sectionGroup name="MySectionGroup">
      <section name="Section1"  type="DataCache.DataConfigSection, DataCache"     allowLocation="false" allowDefinition="Everywhere"/>
      <section name="Section2"  type="DataCache.DataConfigSection, DataCache"     allowLocation="false" allowDefinition="Everywhere"/>
    </sectionGroup>
  </configSections>
  <MySectionGroup>
    <Section1>
      <name
        firstName ="Pierce"
        lastName ="Brosnan"
        />
    </Section1>
  </MySectionGroup>

<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>

</configuration>


Here are the DataCache classes :

C#
using System;
namespace DataCache
{
    public sealed class DataConfigSection : System.Configuration.ConfigurationSection
    {
        public static System.Configuration.Configuration config = System.Configuration.ConfigurationManager.OpenExeConfiguration(
        System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetAssembly(typeof(DataConfigSection)).Location), "MyApplication.exe"));

    // Create a "name" element.
    [System.Configuration.ConfigurationProperty("name")]
    public NameElement Name
    {
        get
        {
            return (NameElement)this["name"];
        }
        set
        {
            this["name"] = value;
        }
    }

    // Define the "name" element
    // with firstName, secondName attributes.
    public class NameElement : System.Configuration.ConfigurationElement
    {
        [System.Configuration.ConfigurationProperty("firstName", DefaultValue = "abcd", IsRequired = true)]
        public String FirstName
        {
            get
            {
                return (String)this["firstName"];
            }
            set
            {
                this["firstName"] = value;
            }
        }

        [System.Configuration.ConfigurationProperty("lastName", DefaultValue = "xyz", IsRequired = true)]
        public String LastName
        {
            get
            {
                return (String)this["lastName"];
            }
            set
            {
                this["lastName"] = value;
            }
        }
    }
  }
}


namespace DataCache
{
    public class Section1Data
    {
        public static volatile DataConfigSection configSection1;
        private static object syncRoot = new System.Object();

    public const string Section1ConfigSectionName = "MySectionGroup/Section1";

    private Section1Data() { }

    public static DataConfigSection ConfigSection1
    {
        get
        {
            lock (syncRoot)
            {
                if (configSection1 == null)
                {
                    DataConfigSection.config = System.Configuration.ConfigurationManager.OpenExeConfiguration(System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetAssembly(typeof(DataConfigSection)).Location), "MyApplication.exe"));
                    configSection1 = (DataConfigSection)DataConfigSection.config.GetSection(Section1ConfigSectionName);                   
                }
            }
            return configSection1;
        }
    }
    public static string FirstNameString
    {
        get
        {
            return ConfigSection1.Name.FirstName.ToString();
        }
        set
        {
            ConfigSection1.Name.FirstName = value;
            DataConfigSection.config.Save();
        }
    }
    public static string LastNameString
    {
        get
        {
            return ConfigSection1.Name.LastName.ToString();
        }
        set
        {
            ConfigSection1.Name.LastName = value;
            DataConfigSection.config.Save();
        }
    }
  }
}


The issue is if there is any change made to the name from console input, FileChanged gets called once and subsequent changes do not fire it. Moreover, if changes are made manually to the config file, it works fine till and events keep firing till a change is made from console.

I am not sure why is this behaving weird. Can anyone help me here.

Thanks, Monica
Posted
Updated 14-Dec-12 1:13am
v2

The code in the FileSystemWatcher thread is not quite right as you are using both events and WaitForChanged. It really should be one or the other.

The FileSystemWatcher can be used in two modes
1) Asynchronously, i.e. set EnableRaisingEvents == true and attach handlers to the events.

2) Synchronously, i.e. set EnableRaisingEvents == false and call the WaitForChanged method to block the thread until something happens. The WaitforChangedResult structure contains the information about whatever caused the method to return.

In your case I don't think that you need the thread at all and the FileSystemWatcher can be operated in asynchronous mode. It would simply be initialised before the data entry loop containing Console.ReadLine() is started. The FileSystemWatcher events are notified on ThreadPool threads and so the messages would still be written to the console.

The problem with allowing more than one thread to write to the console, which is inherent in your design, is that the messages from the FileSystemWatcher can occur at any time and become mixed in with your prompts for user input. Rather than try to fix that I would suggest that some applications are more easily written with System.Windows.Forms where dedicated controls for input and output may be used.

Alan.
 
Share this answer
 
Comments
Monica Agrawal 15-Dec-12 5:25am    
Thanks. This worked using Asynchronous call. I removed WaitForChanged. The console sometimes throws an error for accessing the file being used by another process. So I removed that from the FileChanged. Thanks again :)
from the code, my guess is ;

_watcher.EnableRaisingEvents = true;

is not getting fired which is in the method

protected static void FileChanged(object sender, FileSystemEventArgs e)

this could be happening due to a exception caused at one of the below lines:

================================================
C#
SetConfigFileAtRuntime(Path.Combine(Program.rootFolderPath, "MyApplication.exe.config"));

            Console.WriteLine(String.Format("New Name : {0} {1}", DataCache.Section1Data.FirstNameString, DataCache.Section1Data.LastNameString));


================================================

hope this helps.
disclaimer : just my guess , based on a visual code review
 
Share this answer
 
Comments
Monica Agrawal 14-Dec-12 13:30pm    
But I am not getting exceptions when I put the breakpoint in the catch blocks. Could this be a cross-threading issue ?

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900