4

I'm writing a program that saves .NET application settings. All of these are user-scope instead of application scope. The code technically works but is very fragile and buggy; sometimes it only saves part of the settings instead of the whole thing. Because of this bug, I often have to re-enter all the configuration data even after making minor changes.

This problem seems random-- sometimes it happens, other times it doesn't, so I haven't been able to pin down a cause by detecting a pattern. However, I've noticed the app usually works the way it is supposed to if I re-enter all the data at once and then leave it alone without changing anything in config for a while.

All of the Properties.Settings.Default.* values that are being written come from parameters inside these user control instances (pga, pl, pd, etc.). Basically, the user controls are pages that are displayed one at a time (depending on which value the user selects from the tree menu) in the main Config class. The form data from the user controls is exposed to the Config class via internal-scope parameters. Each parameter matches the data type for the setting it corresponds to--the parameters are finished values so any necessary logic to produce them takes place in their user controls.

This is how the application saves/updates its config data:

 private void SaveConfig()
    {
        lock(saveLock){
            try
            {
                Properties.Settings.Default.GatewayIP_Primary = pga.GatewayIP;

                Properties.Settings.Default.AutoConnect = pg.AutoStart;
                Properties.Settings.Default.TranslateOAI = pg.Translate;
                Properties.Settings.Default.GatewayPort = pga.GatewayPort;
                Properties.Settings.Default.MariaDBPort = pd.MariaDBPort;

                Properties.Settings.Default.MariaDBPass = pd.MariaDBPass;
                Properties.Settings.Default.MariaDBUser = pd.MariaDBUser;
                Properties.Settings.Default.MariaDBHost = pd.MariaDBHost;


                Properties.Settings.Default.SaveClearLog = pl.AutoSaveLog;
                Properties.Settings.Default.LogSavePath = pl.LogFolderPath;
                Properties.Settings.Default.SaveTime = pl.LogSaveTime;

                Properties.Settings.Default.PulseFrequency = pg.PulseInterval;

                Properties.Settings.Default.AutoNightMode = pn.AutoNightMode;
                Properties.Settings.Default.NMSleep = pn.SleepTime;
                Properties.Settings.Default.NMWake = pn.WakeTime;

                Properties.Settings.Default.AdminExt = pn.NightModeAdminExt;
                Properties.Settings.Default.TrunkAccessCode = pga.TrunkCode;
                Properties.Settings.Default.LocalAreaCode = pg.LocalAreaCode;

                Properties.Settings.Default.ExpandedExt = pga.UseExpandedExtensions;

                Properties.Settings.Default.AllowDNDOverride = pg.DNDOverride;
                Properties.Settings.Default.TollStation=pga.TollStation;
                Properties.Settings.Default.MailboxNum=pga.MailboxPrefixCode;

                Properties.Settings.Default.EmailRecipients = pe.RecipientList;
                Properties.Settings.Default.EmailServer = pe.EmailHost;

                Properties.Settings.Default.ShowBalloons = pg.ShowBalloon;
                Properties.Settings.Default.EventThreshholdMinutes = pe.EventThreshhold;
                Properties.Settings.Default.ConnectTimeout = pga.ConnectTimeout;

                Properties.Settings.Default.UseGateway = pga.UseGateway;

                Properties.Settings.Default.Save();
                Properties.Settings.Default.Reload();
            }
            catch (Exception ex)
            {
                string err = "Encountered a problem whilst updating settings: " + ex.ToString();
                Reporter.WritetoSystemLog(err, 0);

            }
            finally
            {
                // Refresh the preferences.
                BufferTools t = new BufferTools();
                t.LoadSettings();

                MainWindow.main.AddtoLog("Refreshed configuration data.");
            }

        }// end of lock

        Close();
    }

At first, I thought a race condition was causing the form to close before all the values were saved so I tried putting the code inside a lock earlier today. However that didn't help.

Furthermore, I've only run this program from inside the debug/release dir in the project folders. This is still a work in progress and isn't ready for deployment. Might that have something to do with it? Perhaps the frequent builds are doing something to the saved settings.

Any ideas about what could be causing this?

Edit: This is the code that (re)loads the data from config into memory during startup. The app uses the Buffer.* values for everything instead of touching the config directly. This code is part of the BufferTools class, not Config:

 //################################################################################################
    /// <summary>
    /// Load the config data into buffer. This should run when the program starts up
    /// and whenever the user refreshes the config.  --Will Kraft (8/31/15).
    /// </summary>
    /// <returns>Load result.</returns>
    /// 
    internal bool LoadSettings(){
        try
        {
            Buffer.LastEventReceived = DateTime.Now;

            Buffer.appVersion = Properties.Settings.Default.Version;
            Buffer.GatewayIP_Primary = Properties.Settings.Default.GatewayIP_Primary;
            Buffer.GatewayIP_Secondary = Properties.Settings.Default.GatewayIP_Secondary;

            if (!string.IsNullOrEmpty(Properties.Settings.Default.GatewayPort))
                Buffer.GatewayPort = System.Convert.ToInt32(Properties.Settings.Default.GatewayPort);

            Buffer.ClientPort = System.Convert.ToInt32(Properties.Settings.Default.ClientPort);
            Buffer.AutoConnect = Properties.Settings.Default.AutoConnect;
            Buffer.TranslateOAI = Properties.Settings.Default.TranslateOAI;

            Buffer.TrunkAccessCode = Properties.Settings.Default.TrunkAccessCode;
            Buffer.AdminExt = Properties.Settings.Default.AdminExt;

            if (!string.IsNullOrEmpty(Properties.Settings.Default.DialOutNum))
                Buffer.DialOutNum = Int32.Parse(Properties.Settings.Default.DialOutNum);

            Buffer.NeedsDialOutNum = Properties.Settings.Default.NeedsDialOutNum;

            Buffer.MariaDBHost = Properties.Settings.Default.MariaDBHost;
            Buffer.MariaDBUser = Properties.Settings.Default.MariaDBUser;
            Buffer.MariaDBPass = Properties.Settings.Default.MariaDBPass;
            Buffer.MariaDBPort = Properties.Settings.Default.MariaDBPort;

            Buffer.LogLocation = Properties.Settings.Default.LogSavePath;
            Buffer.CleanupTimeStr = Properties.Settings.Default.SaveTime;
            Buffer.AutoSave = Properties.Settings.Default.SaveClearLog;

            Buffer.AutoNightMode = Properties.Settings.Default.AutoNightMode;
            Buffer.WakeTimeStr = Properties.Settings.Default.NMWake;
            Buffer.SleepTimeStr = Properties.Settings.Default.NMSleep;

            Buffer.cleanupHourMinute = Properties.Settings.Default.SaveTime;

            Buffer.ExpandedExt = Properties.Settings.Default.ExpandedExt;

            //Buffer.PulseInterval = System.Convert.ToDecimal(Properties.Settings.Default.PulseFrequency);
            Buffer.PulseInterval = Properties.Settings.Default.PulseFrequency;

            Buffer.PhoneSystemType = Properties.Settings.Default.SystemType;
            Buffer.MailboxNumber = Properties.Settings.Default.MailboxNum;
            Buffer.TollRestrictStation = Properties.Settings.Default.TollStation;
            Buffer.AllowDNDOverride = Properties.Settings.Default.AllowDNDOverride;

            Buffer.ShowBalloons = Properties.Settings.Default.ShowBalloons;
            Buffer.EventTimeThreshhold = Properties.Settings.Default.EventThreshholdMinutes;
            Buffer.ConnectTimeout = Properties.Settings.Default.ConnectTimeout;
            Buffer.LocalAreaCode = Properties.Settings.Default.LocalAreaCode;
            Buffer.MailHost = Properties.Settings.Default.EmailServer;

            if (Buffer.PulseInterval == 0)
                Buffer.PulseInterval = 1.0f;

            // load email recipients list. --Will Kraft (10/30/15).
            Buffer.EmailRecipients = new List<string>();
            Buffer.EmailRecipients.Clear();
            string[] addresses = Properties.Settings.Default.EmailRecipients
                .Split(Constants.BRIDGE_SEP);

            foreach (string addr in addresses)
            {
                if(!string.IsNullOrWhiteSpace(addr))
                    Buffer.EmailRecipients.Add(addr);
            }


            // Set maintenance timers.
            //SchedulerCore.SetupTimerEvents();

            string configPath = ConfigurationManager.
                OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath;
            Reporter.WritetoSystemLog("Configuration/settings file path: " + configPath, 2);


        }
        catch (ConfigurationErrorsException ex)
        {
            string err="The configuration file has syntax errors or is corrupt.";
            if (MainWindow.main != null)
            {
                MainWindow.main.ShowEventMessage(err, 0);
            }

            Reporter.WritetoSystemLog(err + "\r\n\r\nDetails:\r\n" + ex.ToString(), 0);
        }
        catch (Exception e)
        {
            Reporter.WritetoSystemLog("Configuration did not load. Details: " + e.ToString(), 0);
            return false;
        }

        return true;
    }
Will Kraft
  • 553
  • 1
  • 8
  • 23
  • Where is the save to: Memory, file, database? If it is just in memory, then restarting the worker process would likely flush the settings as it resets the memory. – JB King Dec 01 '15 at 16:26
  • Do you need to save it with administrator privileges. – bilpor Dec 01 '15 at 16:28
  • 1
    I assume the changes are being written directly to the app.config file the current build is using. By design, the app shouldn't require admin rights. – Will Kraft Dec 01 '15 at 16:29
  • do u think the settings are not saved when u restart the applicaiton from VS ? – Kapoor Dec 01 '15 at 16:31
  • @Kapoor: Not sure. The new settings seem to be permanent between builds if I change them during runtime. If this bug triggers, only some of the settings get saved, the rest revert to default. – Will Kraft Dec 01 '15 at 16:33
  • 1
    well have u taken a look at user specific config file that is created ? every time you rebuild it will create a new folder, including the version of the software. I suggest run your application from exe instead of VS, and observe the behavior. the user specific config is saved at a path that u can get like this `ConfigurationManager.OpenExeConfiguration(...).FilePath; ` – Kapoor Dec 01 '15 at 16:37
  • The bug still happens even when I run the exe directly outside of VS. However, I have only ever run it from the debug or release folders inside the project dir. Do you think the settings will persist if I were to run the exe from a folder that is less prone to change? – Will Kraft Dec 01 '15 at 17:10
  • @John Carpenter: After writing the config changes, the app calls BufferTools.LoadSettings, which updates the app's current configuration with the new preferences. This is not supposed to run until after the changes have been saved. The config should refresh no matter what, which is why I put it inside the finally block. Also, if the solution app.config overwrites the saved settings, then why do the settings last between builds? I'm kind of confused.... why does .NET use an *.exe.config file if it saves the settings elsewhere (like the AppData/local folder)? – Will Kraft Dec 01 '15 at 17:12
  • @WillKraft: I'm also having trouble finding a resource for the class `BufferTools`. Is this a custom class that you wrote? Could you post the code? – Frank Bryce Dec 01 '15 at 17:25
  • @WillKraft: Regarding your configuration questions, I searched and found http://stackoverflow.com/a/1016728/3960399. It looks like your application CANNOT edit app.config at runtime but it can update settings. I deleted my earlier comments since my guesses were wrong. – Frank Bryce Dec 01 '15 at 17:30
  • @JohnCarpenter: Yes, BufferTools is a custom class. It handles loading configuration settings (and other unrelated things). Basically, the app does not work directly from the saved settings but loads a copy of them into memory at startup and whenever the config gets updated. I've added the configuration load code in Buffertools to my original post. – Will Kraft Dec 01 '15 at 17:46
  • @WillKraft: Question: why are you calling the `Reload()` method on `Properties.Settings.Default`? My second idea at a solution: your comment says that you call the `LoadSettings()` method on your `BufferTools` class, and you also call it on start up. Could this be the source of the race condition? Do you by chance need a lock in your `BufferTools` `LoadSettings()` function? – Frank Bryce Dec 01 '15 at 18:16
  • A tutorial I followed a long time ago said that was necessary for reloading the settings after saving them. I've just done it that way ever since (Back when I started this project I re-used older code to save time). I've tried commenting that line out but it didn't make any difference on fixing this bug. I tried placing a lock in the LoadSettings code too but that didn't help either. LoadSettings only runs at startup and whenever the configuration gets updated. I'm beginning to wonder if this is a bug in .NET. – Will Kraft Dec 02 '15 at 15:52

0 Answers0