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;
}