Upgrade application settings in registry in C Sharp

Z ZděchovNET
Verze z 19. 7. 2017, 08:20, kterou vytvořil Chronos (diskuse | příspěvky) (Založena nová stránka s textem „Standard way to store applications settings in Windows applications is to put them to Windows Registry. Windows Registry is unified system tree-based datab…“)
(rozdíl) ← Starší verze | zobrazit aktuální verzi (rozdíl) | Novější verze → (rozdíl)
Skočit na navigaci Skočit na vyhledávání

Standard way to store applications settings in Windows applications is to put them to Windows Registry. Windows Registry is unified system tree-based database for storing settings.

In comparison Unix-like systems doesn't have such standardized unified way for storing settings and their textual configuration files are simply great mess. There are uncountable different file formats and it is nearly impossible to manage them uniformly. OS and application settings should be definitely reachable by separate OS API and not as workaround by filesystem API.

Unfortunately default way to store application settings in C#/.NET is textual XML file user.config stored in application user directory. By default you can only store static entries with that and they are generated in design time as fixed C# class file. If you want store new settings entries to configuration file in run-time you need to do it more complicated way. Also designer doesn't allow to create tree like structure but just list of items.

And there is also one bad decision to force application developers to store application settings in directories names according executable file name plus hash and version number. This leads to need of migrate settings from previous version and multiple copies of configuration files per each version and per executable which is different for debug and release. .NET provides method Upgrade to support migration of settings with bit of manual work. Such method needs to be called first before any other application settings access.

private void UpgradeApplicationSettings()
{
  // Load configuration from previous versions
  if (!Properties.Settings.Default.Upgraded)
  {
    Properties.Settings.Default.Upgrade();
    Properties.Settings.Default.Upgraded = true;

    // Do some custom settings conversions 

    Properties.Settings.Default.Save();
  }
}

There is also similar bad decision with using Windows registry. Default registry class for accessing user settings is Application.UserAppDataRegistry. But this classes points to registry keys with version identification HKEY_CURRENT_USER\Software\Company\Product\Version. Again this is bad because it means that for every new version of the application there will be stored copy of application settings. This is a problem for several reasons and one is that it will simply take more space in registry database and storage, and also settings need to be migrated manually. In fact if you want to use multiple versions of same product then you still may want to change settings only once to be applied to multiple versions. Not changing same settings manually for all installed version of the same application. So older approach to have one registry key for multiple versions of same application was much easier to maintain from developer and user perspective. But as Application.UserAppDataRegistry is just read only property and can't be changed then we need to follow this new standard and provide Upgrade mechanism in similar way as for applications settings in file. Unfortunately .NET framework doesn't provide such method so we have to do it by ourselves. For that purpose we need also check and set Upgraded value and also provide method for recursive copy of given key. Previous version can be simply found from sorted version list. Similar upgrade needs to be done also for Application.CommonAppDataRegistry where application settings shared between all users is stored.

public void CopyRegistry(RegistryKey src, RegistryKey dst)
{
    // copy the values
    foreach (var name in src.GetValueNames())
    {
        dst.SetValue(name, src.GetValue(name), src.GetValueKind(name));
    }

    // copy the subkeys
    foreach (var name in src.GetSubKeyNames())
    {
        using (var srcSubKey = src.OpenSubKey(name, false))
        {
            var dstSubKey = dst.CreateSubKey(name);
            CopyRegistry(srcSubKey, dstSubKey);
        }
    }
}

private void UpgradeRegistrySettings()
{
    RegistryKey regKey = Application.UserAppDataRegistry.CreateSubKey("");
    if (regKey == null) regKey = Application.UserAppDataRegistry.CreateSubKey("");
    if (regKey.GetValue("Upgraded") == null)
    {
        string parentKey = Application.UserAppDataRegistry.Name.Substring(0, Application.UserAppDataRegistry.Name.LastIndexOf("\\"));
        parentKey = parentKey.Substring(parentKey.IndexOf("\\") + 1);
        RegistryKey parentRegKey = Registry.CurrentUser.OpenSubKey(parentKey, false);
        if (parentRegKey != null)
        {
            List<string> subKeys = new List<string>(parentRegKey.GetSubKeyNames());
            var versions = new List<Version>();
            foreach (var key in subKeys)
            {
                versions.Add(new Version(key));
            }
            versions.Sort();
            versions.Reverse();

            if (versions.Count > 1)
            {
                RegistryKey srcKey = Registry.CurrentUser.OpenSubKey(parentKey + "\\" + versions[1].ToString());
                CopyRegistry(srcKey, regKey);
            }
        }
        regKey.SetValue("Upgraded", true);
    }
}

So best and most standardized and robust way to store application settings on Windows are definitely Windows registry. But more then .NET approach to store everything separately for each assembly version, its much better to have shared settings across multiple versions and keep back compatibility. If anyone want to run multiple versions of same application then one can use same approach as for running multiple instances of same application with different settings. Usually if application wants to support such feature then it can be executed with some command line parameter like /config <name>. In such case registry can be stored in per-config fashion.

In summary Microsoft should allow developers to choose if they want separate per-version application settings and if they want use standard registry API or limited design time settings, or another file-based non-standard format like INI/XML/JSON or something custom for dynamic run-time settings values.