Using FileSystemMonitoring for reading changes in app.config and writing to app.config realtime

白昼怎懂夜的黑 提交于 2021-02-19 03:16:56

问题


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.

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 :

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


回答1:


Short version would be:

this.Watcher = new FileSystemWatcher();
this.Watcher.Path = this.Dir;
this.Watcher.Filter = this.File;
this.Watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
this.Watcher.EnableRaisingEvents = true;
this.Watcher.Changed += this.OnFileChange;

However, here is a bit more complex (and ugly) sample, taken out from the live source in which some additional processing (besides just reading the file) is required.

public partial class FileModule
{
    private ConcurrentDictionary<string, InputFileInfo> inputFileList = new ConcurrentDictionary<string, InputFileInfo>();

    public FileModule()
    {
        this.InitializeInputFileWatchers();
    }

    [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
    public void EnableOrDisableRaisingEventsForFileWatchers(bool enable)
    {
        foreach (var el in this.inputFileList)
        {
            el.Value.Watcher.EnableRaisingEvents = enable;
        }
    }

    [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
    private void InitializeInputFileWatchers()
    {
        for (int i = 0; i < this.InputFiles.Count; i++)
        {
            if (File.Exists(this.InputFiles[i]))
            {
                InputFileInfo info = new InputFileInfo();
                info.Fullpath = ((FileModuleSettings)this.Settings).InputFiles[i];
                info.Watcher.Changed += this.OnFileChange;

                this.inputFileList.AddOrUpdate(info.Fullpath, info, (e, v) => { return info; });
            }
        }
    }

    [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
    private void OnFileChange(object source, FileSystemEventArgs e)
    {
        InputFileInfo info;
        if (this.inputFileList.TryGetValue(e.FullPath, out info))
        {
            DateTime lastWriteTime = System.IO.File.GetLastWriteTime(e.FullPath);
            if (info.LastHandledChange != lastWriteTime)
            {
                TimeSpan span = lastWriteTime.Subtract(info.LastHandledChange);
                if (span.Days == 0 && span.Hours == 0 && span.Minutes == 0 && span.Seconds == 0 && span.TotalMilliseconds < this.MinimumFileChangePeriod)
                {
                    // Event ignored due to required minimum file change period;
                }
                else
                {
                    info.LastHandledChange = lastWriteTime;
                    this.inputFileList.AddOrUpdate(e.FullPath, info, (a, v) => { return info; });

                    lock (this.readLockerObject)
                    {
                        this.ReadFile(e.FullPath);
                    }
                }
            }
        }
    }

    private bool ReadFile(string filepath, int count, bool directReading)
    {
        StreamReader sr = this.OpenStreamReader(file);

        if (sr != null)
        {
            string line;
            string[] split;
            int signalId;
            double value;

            while ((line = sr.ReadLine()) != null)
            {
                // do sth. with line
            }
        }
    }
}

internal class InputFileInfo : IDisposable
{
    public string Dir { get; private set; }

    public string File { get; private set; }

    public FileSystemWatcher Watcher { get; private set; }

    public DateTime LastHandledChange { get; set; }

    private string fullpath;

    public string Fullpath
    {
        get
        {
            return this.fullpath;
        }

        set
        {
            this.fullpath = BaseHelper.GetFullFilePath(value);
            this.Dir = Directory.GetParent(this.fullpath).ToString();
            this.File = this.fullpath.Replace(this.Dir + "\\", string.Empty);
            this.Watcher = new FileSystemWatcher();
            this.Watcher.Path = this.Dir;
            this.Watcher.Filter = this.File;
            this.Watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
            this.Watcher.EnableRaisingEvents = true;
        }
    }

    public void Dispose()
    {
        if (this.Watcher != null)
        {
            this.Watcher.Dispose();
            this.Watcher = null;
        }
    }
}



回答2:


You have very complex code. My code for watcher is about 10 lines.

There is no need to offload initialization of watcher to another thread. Initialize it on main thread. The watcher watches files in background thread and fires the event when change occurs so there is no need to call WaitForChanged() method.

I've checked my working code and compared it with yours and the only two differences are: 1. My filters are: NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName 2. I don't call WaitForChanged() method 3. I have [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] applied to all methods which interact with watcher.

Furthermore, make sure the file you are monitoring is not in one of the system directories... The watcher does not work well with those files. I banged my head up the wall for half a day long because I was trying to watch file on C:.



来源:https://stackoverflow.com/questions/13876372/using-filesystemmonitoring-for-reading-changes-in-app-config-and-writing-to-app

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!