Search code examples
c#.netwindowsthread-safetytopshelf

Loaded settings from JSON are not current/updated/correct when they are accessed by thread


I have a service, using topshelf, that uses FileSystemWatcher to look for created files and I do something with them.

Service Settings are stored and loaded in a JSON file.

When the service is started, settings are loaded from file and the settings class values are populated.

When debugging, it works fine, when in release and run as a service, it's using the default values for the TempFolder property.

Why is this failing and how do properly load the settings?

    [JsonObject(MemberSerialization.OptIn)]
    public class MonitorSettings
    {
        public readonly string SettingsFileName = "Settings.json";

        private string SettingsFilePath;

        [JsonProperty]
        public string FolderToMonitor { get; set; } = @"C:\temp\";

        [JsonProperty]
        public string FileNameSearchWildcard { get; set; } = @"*_something.txt";

        [JsonProperty]
        public string TempFolder { get; set; } = @"C:\Temp_Fix_This_Path\";

        public MonitorSettings()
        {
            SettingsFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.None), SettingsFileName);
        }

        public bool Load()
        {
            if (!File.Exists(SettingsFilePath))
            {
                Save();
                return false;
            }
            var json = File.ReadAllText(SettingsFilePath);
            JsonConvert.PopulateObject(json, this);
            return true;
        }

Contents of the json file

{
  "FolderToMonitor": "C:\\results",
  "FileNameSearchWildcard": "*_blah.txt",
  "TempFolder": "D:\\Temp\\temp"
}

Service start:


private volatile MonitorSettings _Settings;

public bool Start()  //entry point Service Start
        {
            _CTS = new CancellationTokenSource();
            Task.Run(()=>StartService(_CTS.Token));
            return true;        
        }

        public async Task StartService(CancellationToken token)
        {
            _Settings = new MonitorSettings();
            if (!_Settings.Load())
            {
                WriteEventLog($"Settings file not setup. Go To %AppData% and edit: {_Settings.SettingsFileName}",EventLogEntryType.Error,0);
                throw new Exception($"Settings File Not Found, Go To AppData and edit {_Settings.SettingsFileName}");
            }
            CheckSettings(_Settings);
            

            if (!Directory.Exists(_Settings.TempFolder))
            {
                Directory.CreateDirectory(_TempFolder);
            }

            WriteEventLog($"Starting {appName}. Monitoring: {_Settings.FolderToMonitor} | Copying to: {_Settings.TempFolder}", EventLogEntryType.Information,1); //TempFolder is not the value from the JSON file

            try
            {
                using (var watcher = new FileSystemWatcher())
                {
                    watcher.Path = _Settings.FolderToMonitor;
                    watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
                    watcher.IncludeSubdirectories = false;//true;
                    watcher.Created += Watcher_Changed;
                    //start it
                    watcher.EnableRaisingEvents = true;
                    while (!token.IsCancellationRequested) { await Task.Delay(TimeSpan.FromSeconds(15), token); };
                }
            }
            finally
            {
                WriteEventLog($"{appName} Service Stop Requested.", EventLogEntryType.Information, 1);
            }
        }

Solution

  • The immediate issue was using Environment.SpecialFolder.ApplicationData to save the settings file to which is user-specific.

    Changing to Environment.SpecialFolder.CommonApplicationData is non user specific and is a "common" location for this file and more appropriate for this case.