C# - User Settings broken

12,437

Solution 1

The way to programmatically recover is to do what you did manually - delete the user settings file. Then call Settings.Reset. (You could also write a new user settings file with default values instead of deleting it, but if you're using the configuration manager properly that's essentially the same thing.)

This is a pretty rare occurrence, but it's not totally unheard of. Not only can your program crash while writing the user settings file, the file itself is user-writeable, so other programs the user runs could mess with it.

To avoid this particular vulnerability, persist user settings in a durable store with transactional integrity, i.e. a database. (You'll still have vulnerabilities, just not this one.) That's a lot of work for what in most cases will be a marginal improvement in reliability. But "in most cases" doesn't mean "in all cases;" yours may warrant it.

Solution 2

Here's a solution that does not require you to exit the application with kudos to Jarle (http://www.codeproject.com/Articles/30216/Handling-Corrupt-user-config-Settings?msg=3608682#xx3608682xx). Early on, before Settings ever gets called, use this

    public static bool CheckSettings()
    {
        var isReset = false;

        try
        {
            ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal);
        }
        catch (ConfigurationErrorsException ex)
        {
            string filename = string.Empty;
            if (!string.IsNullOrEmpty(ex.Filename))
            {
                filename = ex.Filename;
            }
            else
            {
                var innerEx = ex.InnerException as ConfigurationErrorsException;
                if (innerEx != null && !string.IsNullOrEmpty(innerEx.Filename))
                {
                    filename = innerEx.Filename;
                }                   
            }

            if (!string.IsNullOrEmpty(filename))
            {
                if (System.IO.File.Exists(filename))
                {
                    var fileInfo = new System.IO.FileInfo(filename);
                    var watcher
                         = new System.IO.FileSystemWatcher(fileInfo.Directory.FullName, fileInfo.Name);
                    System.IO.File.Delete(filename);
                    isReset = true;
                    if (System.IO.File.Exists(filename))
                    {
                        watcher.WaitForChanged(System.IO.WatcherChangeTypes.Deleted);
                    }
                }
            }
        }

        return isReset;
    }

Essentially, rather than relying on Sittings to throw the error, read the file with the ConfigurationManager, that way the system's version never gets into a bad state.

Solution 3

[STAThread]
private static void Main(string[] args)
{
    try
    {
        // ...
    }
    catch (System.Configuration.ConfigurationErrorsException ex)
    {   
        var config = ((System.Configuration.ConfigurationErrorsException)ex.InnerException).Filename;
        // notify user, ask them to restart
        System.IO.File.Delete(config);
        Application.Exit();
    }
}
Share:
12,437

Related videos on Youtube

jonathanpeppers
Author by

jonathanpeppers

Xamarin MVP and lead developer on the popular apps Hanx Writer and Draw a Stickman. I work at Microsoft on the Xamarin.Android team.

Updated on September 20, 2020

Comments

  • jonathanpeppers
    jonathanpeppers over 3 years

    We had a rare exception occur when reading the standard .Net user settings (this are the ones found in "project properties" in VS 2008):

    System.Configuration.ConfigurationErrorsException was caught
      Message="Configuration system failed to initialize"
      Source="System.Configuration"
      BareMessage="Configuration system failed to initialize"
      Line=0
      StackTrace:
           at System.Configuration.ConfigurationManager.PrepareConfigSystem()
           at System.Configuration.ConfigurationManager.GetSection(String sectionName)
           at System.Configuration.PrivilegedConfigurationManager.GetSection(String sectionName)
           at System.Diagnostics.DiagnosticsConfiguration.GetConfigSection()
           at System.Diagnostics.DiagnosticsConfiguration.Initialize()
           at System.Diagnostics.DiagnosticsConfiguration.get_IndentSize()
           at System.Diagnostics.TraceInternal.InitializeSettings()
           at System.Diagnostics.TraceInternal.get_Listeners()
      InnerException: System.Configuration.ConfigurationErrorsException
           Message="Unexpected end of file has occurred. The following elements are not closed: setting, SettingsTest.Properties.Settings, userSettings, configuration. Line 7, position 1. (C:\\Documents and Settings\\USER\\Local Settings\\Application Data\\Hitcents\\SettingsTest.vshost.exe_Url_ghwhc20utv4toanuinmj0pfsljthcugo\\1.0.0.0\\user.config line 7)"
           Source="System.Configuration"
           BareMessage="Unexpected end of file has occurred. The following elements are not closed: setting, SettingsTest.Properties.Settings, userSettings, configuration. Line 7, position 1."
           Filename="C:\\Documents and Settings\\USER\\Local Settings\\Application Data\\Hitcents\\SettingsTest.vshost.exe_Url_ghwhc20utv4toanuinmj0pfsljthcugo\\1.0.0.0\\user.config"
           Line=7
           StackTrace:
                at System.Configuration.ConfigurationSchemaErrors.ThrowIfErrors(Boolean ignoreLocal)
                at System.Configuration.BaseConfigurationRecord.ThrowIfParseErrors(ConfigurationSchemaErrors schemaErrors)
                at System.Configuration.BaseConfigurationRecord.ThrowIfInitErrors()
                at System.Configuration.ClientConfigurationSystem.OnConfigRemoved(Object sender, InternalConfigEventArgs e)
           InnerException: System.Xml.XmlException
                Message="Unexpected end of file has occurred. The following elements are not closed: setting, SettingsTest.Properties.Settings, userSettings, configuration. Line 7, position 1."
                Source="System.Xml"
                LineNumber=7
                LinePosition=1
                SourceUri=""
                StackTrace:
                     at System.Xml.XmlTextReaderImpl.Throw(Exception e)
                     at System.Xml.XmlTextReaderImpl.Throw(String res, String arg)
                     at System.Xml.XmlTextReaderImpl.Throw(Int32 pos, String res, String arg)
                     at System.Xml.XmlTextReaderImpl.ThrowUnclosedElements()
                     at System.Xml.XmlTextReaderImpl.ParseElementContent()
                     at System.Xml.XmlTextReaderImpl.Read()
                     at System.Xml.XmlTextReader.Read()
                     at System.Xml.XmlTextReaderImpl.Skip()
                     at System.Xml.XmlTextReader.Skip()
                     at System.Configuration.XmlUtil.StrictSkipToNextElement(ExceptionAction action)
                     at System.Configuration.BaseConfigurationRecord.ScanSectionsRecursive(XmlUtil xmlUtil, String parentConfigKey, Boolean inLocation, String locationSubPath, OverrideModeSetting overrideMode, Boolean skipInChildApps)
                     at System.Configuration.BaseConfigurationRecord.ScanSectionsRecursive(XmlUtil xmlUtil, String parentConfigKey, Boolean inLocation, String locationSubPath, OverrideModeSetting overrideMode, Boolean skipInChildApps)
                     at System.Configuration.BaseConfigurationRecord.ScanSections(XmlUtil xmlUtil)
                     at System.Configuration.BaseConfigurationRecord.InitConfigFromFile()
                InnerException: 
    

    *NOTE: this is re-created from a test app.

    I pulled up the user.config file, and half of it was missing.

    I expect our application was terminated abruptly for some reason or another.

    This seems very rare, here is how we interact with the settings:

    //How we read
    Settings settings = Settings.Default;
    _ourStaticMemberVariable = settings.OurValue;
    
    //How we save
    Settings settings = Settings.Default;
    settings.OurValue = "Our Value";
    settings.Save();
    

    Is there anything wrong with how we're using it? Both calls have a try-catch that place some default values, but the values need to be able to reset from our application.

    When in this state, our application cannot save new settings--and I cannot figure out a good way to programmatically recover. I had to manually find the user.config and delete it.

    I also tried calling Settings.Reset(), etc. but get the same exception.

    Any ideas on how to fix this? Or are we better off writing our own settings system or saving persistent settings in another way?

    EDIT: A workaround is to delete the file from code, if you get a ConfigurationErrorsException.

    Anyone know how to get the full path of the user.config file?

  • jonathanpeppers
    jonathanpeppers about 14 years
    How do I get the path to the user settings file? A database is overkill for my scenario.
  • technophile
    technophile about 14 years
    Note that the InnerException, of type System.Configuration.ConfigurationErrorsException, has a Filename property...
  • jonathanpeppers
    jonathanpeppers about 14 years
    I guess I'll have to make a helper method to pull the filename out of the inner exception here.
  • Robert Rossney
    Robert Rossney about 14 years
    string filename = ((ConfigurationErrorsException)e.InnerException).Filename; should do it.
  • Alex_P
    Alex_P over 12 years
    Calling Reset() after deleting the file will result in the same exception thrown. You have to restart the application after deleting the config file. Details here: connect.microsoft.com/VisualStudio/feedback/details/497189/…
  • jonathanpeppers
    jonathanpeppers over 12 years
    This is exactly what I did, except I re-ran my application as a new process rather than exiting.
  • tofutim
    tofutim over 10 years
    restart the application is not so great... wonder if that can be fixed
  • Just Rudy
    Just Rudy almost 7 years
    In my case, I checked out the inner exception as suggested by @technophile . I opened the path, deleted the file there, and was then able to build successfully.
  • Steve
    Steve about 6 years
    whats the FileWatcher for?? I thought Delete will not return until the file is removed
  • tofutim
    tofutim about 6 years
    you'd be surprised