Proper way to upgrade WPF program settings on program update (Desktop Bridge/Project Centennial)

665 views Asked by At

When it was a clickonce program it worked, but then I made an appxpackage and exported it as a centennial app for windows store and the upgrade does not work any more.

Right now I have in App.xaml.cs

protected override void OnStartup(StartupEventArgs e) {
   if (myprog.Properties.Settings.Default.UpgradeRequired)
   {    
      myprog.Properties.Settings.Default.Upgrade();
      myprog.Properties.Settings.Default.UpgradeRequired = false;
      myprog.Properties.Settings.Default.Save();
   }

With UpgradeRequired as a bool in user settings. Is that the right place?

I am getting settings reset on each version update. Now I have several of these directories

C:\Users\me\AppData\Local\progname\prog.exe_Url_randomChars

each with several different version of program settings. So after the upgrade another one of those is created, instead a subfolder with x.x.x.x of the current version.

As before, on each version release I increase version in Assembly Information the Assembly Version, File Version, and now I have the same numbers in AppxManifest.xml. I keep the last number group of the version to 0 as advised, and just increase the 3rd number group.

Is there something I am missing?

3

There are 3 answers

0
Trympet On

You could load the previous user.config file into current settings. This is just a workaround, and can be used to transition to ApplicationData.LocalSettings.

public static void Init()
{
    if (myprog.Properties.Settings.Default.UpgradeRequired)
    {    
        LoadPreviousSettings(myprog.Properties.Settings.Default);
        myprog.Properties.Settings.Default.UpgradeRequired = false;
        myprog.Properties.Settings.Default.Save();
    }
}

private static void LoadPreviousSettings(params ApplicationSettingsBase[] applicationSettings)
{
    const string companyName = "YOUR_COMPANY_NAME_HERE";
    var userConfigXml = GetUserConfigXml(companyName);
    Configuration config = ConfigurationManager.OpenExeConfiguration(
        ConfigurationUserLevel.PerUserRoamingAndLocal);
    foreach (ApplicationSettingsBase setting in applicationSettings)
    {
        try
        {
            // loads settings from user.config into configuration
            LoadSettingSection(setting, config, userConfigXml);
            config.Save(ConfigurationSaveMode.Modified);
            ConfigurationManager.RefreshSection("userSettings");
        }
        catch (FileNotFoundException)
        {
            // Could not import settings.
            // Perhaps user has no previous version installed
        }
        setting.Reload();
    }

}

private static void LoadSettingSection(ApplicationSettingsBase setting, Configuration config, XDocument userConfigXml)
{
    string appSettingsXmlName = setting.ToString();
    var settings = userConfigXml.XPathSelectElements("//" + appSettingsXmlName);
    config.GetSectionGroup("userSettings")
        .Sections[appSettingsXmlName]
        .SectionInformation
        .SetRawXml(settings.Single().ToString());
}

private static XDocument GetUserConfigXml(string companyName)
{
    var localPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + $@"\{companyName}";
    // previous package folder
    var previousLocal = GetDirectoryByWriteTime(localPath, 1);
    // previous version, e.g. 1.2.0
    var prevousVersion = GetDirectoryByWriteTime(previousLocal, 0);
    // application settings for previous version
    return XDocument.Load(prevousVersion + @"\user.config");
}

private static string GetDirectoryByWriteTime(string path, int order)
{
    var direcotires = new DirectoryInfo(path).EnumerateDirectories()
        .OrderBy(d => d.LastWriteTime)
        .Reverse()
        .ToList();
    if (direcotires.Count > order)
    {
        var previous = direcotires[order];
        return previous.FullName;
    }
    throw new FileNotFoundException("Previous config file not found.");
}
0
Stefan Wick  MSFT On

UWP and Desktop Bridge apps need to store their settings in ApplicationData.LocalSettings:

https://learn.microsoft.com/en-us/windows/uwp/design/app-settings/store-and-retrieve-app-data#local-app-data

0
Lola On

There is a working answer here.

Basically you need to create a duplicate version using UWP's ApplicationData.Settings and then loading it at the beginning of the app. It is very straightforward when your settings are strings, bools, etc. But not so if you have unique settings.

To elaborate more from the answer in the link, when you have settings consisting of custom types/classes, when creating UWP's duplicate version, you can use Newtonsoft.Json to serialise the custom setting:

try
{
    ApplicationData.Current.LocalSettings.Values[value.Name] = value.PropertyValue;
}
catch
{
    string serialised = JsonConvert.SerializeObject(value.PropertyValue);
    ApplicationData.Current.LocalSettings.Values[value.Name] = serialised;
}

Then when loading your custom setting:

if (s.Name == "MyCustomSetting")
{
    var deserialised = JsonConvert.DeserializeObject<MyCustomClass>((string)setting.Value);
    s.PropertyValue = deserialised;
}