Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make a string persistent in a .NET application

I am trying to make a string persistent in a .NET application written in C++ under VS 2008 (namely, it is a file path). I just need to read it at application launch, and write it when leaving.

I am struggling to find the proper way to do this. Web search directed me to the ConfigurationSettings and ConfigurationManager objects. It seems that the first is read-only, and the second is not found in the Configuration reference (framework 3.5).

I know that I can perform explicit reads/writes to the registry or to external files, but I would prefer a more standard way. I don't expect this to require more than two lines of code.

Am I on the right track ?

like image 534
Yves Daoust Avatar asked May 11 '19 16:05

Yves Daoust


2 Answers

With VS2008 and .NET framework 3.5 you cannot create a manifest that would be needed to modify the app.config if the application is installed in a subfolder of %ProgramFiles% or %ProgramFiles(x86)% as they are specially protected by the OS, and you cannot write to the HKLM-node of the registry without being an elevated process.

I think I would code the default value and a boolean telling whether the application is running in portable mode to app.config. Read the default value from app.config, overwrite the variable with the value from user.config (if it exists), and write the value to user.config if in portable mode and to app.config if in non-portable mode.

In custom classes, independantly from whatever poor support comes from the framework (no write access to app.config, no hybrid mode)...

app.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v2.0.50727"/>
  </startup>
  <appSettings>
    <add key="PortableMode" value="Off"/>
    <add key="SomethingPath" value="Software\Cragin\FooApp\SomethingPath"/>
  </appSettings>
</configuration>

I think it's more about 250 lines of code than 2 but it does the trick (sorry, it's in C# but you probably know how to adapt it to C++).

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml.Linq;

namespace DesktopApp1 {

    static class Program {

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main() {
            Config myConfig = new Config();
            myConfig.Load();

            //Change settings during the livetime of the application
            myConfig.SomethingPath = @"C:\Temp\Foo\TestUser.dat";
            myConfig.PortableMode = false;

            //Write it when closing the application
            myConfig.Save();

        }

    }

    internal class Config {

        //Private Fields
        private AppOrUserConfig _Config;
        private Boolean _IsUserConfig;
        private String _AppConfigPath;
        private String _UserConfigPath;

        public void Load() {
            AppOrUserConfig myDefaultConfig = new AppOrUserConfig();
            AppOrUserConfig myAppConfig = new AppOrUserConfig(AppConfigPath, myDefaultConfig);
            if (myAppConfig.PortableMode) {
                _Config = myAppConfig;
                _IsUserConfig = false;
            } else {
                _Config = new AppOrUserConfig(UserConfigPath, myAppConfig);
                _IsUserConfig = true;
            }
        }

        public Boolean Save() {
            CheckLoad();
            String myFilePath = IsUserConfig ? UserConfigPath : AppConfigPath;
            try {
                String myContent = _Config.XmlContent;
                String myFolder = Path.GetDirectoryName(myFilePath);
                Directory.CreateDirectory(myFolder);
                File.Delete(myFilePath);
                File.WriteAllText(myFilePath, myContent, new UTF8Encoding(true));
                return true;
            } catch {
            }
            return false;
        }

        public Boolean PortableMode {
            get {
                CheckLoad();
                return _Config.PortableMode;
            }
            set {
                CheckLoad();
                if (PortableMode == value) return;
                if (value) {
                    _Config.PortableMode = true;
                    _IsUserConfig = false;
                    Save();
                } else {
                    String myPath = SomethingPath;
                    _Config.PortableMode = false;
                    Save();
                    Load();
                    SomethingPath = myPath;
                }
            }
        }

        public String SomethingPath {
            get {
                CheckLoad();
                return _Config.SomethingPath;
            }
            set {
                CheckLoad();
                _Config.SomethingPath = value;
            }
        }

        private String AppConfigPath {
            get {
                String myResult = _AppConfigPath;
                if (myResult == null) {
                    myResult = Assembly.GetEntryAssembly().EntryPoint.DeclaringType.Module.FullyQualifiedName + ".config";
                    _AppConfigPath = myResult;
                }
                return myResult;
            }
        }

        private String UserConfigPath {
            get {
                String myResult = _UserConfigPath;
                if (myResult == null) {
                    myResult = Path.Combine(Environment.ExpandEnvironmentVariables(@"%LOCALAPPDATA%\Cragin\FooApp"), Path.GetFileName(AppConfigPath));
                    _UserConfigPath = myResult;
                }
                return myResult;
            }
        }

        private Boolean IsUserConfig {
            get {
                return _IsUserConfig;
            }
        }

        private void CheckLoad() {
            if (_Config == null) throw new InvalidOperationException(@"Call method ""Load()"" first.");
        }

    }

    internal class AppOrUserConfig {

        //Private Fields
        private XDocument _Xml;

        //Constructors

        public AppOrUserConfig() {
            _Xml = XDocument.Parse(@"<?xml version=""1.0"" encoding=""utf-8""?>
                                        <configuration>
                                        <startup>
                                            <supportedRuntime version=""v2.0.50727""/>
                                        </startup>
                                        <appSettings>
                                            <add key=""PortableMode"" value=""Off""/>
                                            <add key=""SomethingPath"" value=""C:\ProgramData\Cragin\SomeLibrary""/>
                                        </appSettings>
                                        </configuration>");
        }

        public AppOrUserConfig(String filePath, AppOrUserConfig defaultValue) : this() {
            XDocument myXml = null;
            try {
                myXml = XDocument.Parse(File.ReadAllText(filePath));
            } catch {
                return;
            }
            AppOrUserConfig myDummy = new AppOrUserConfig(myXml, defaultValue);
            PortableMode = myDummy.PortableMode;
            SomethingPath = myDummy.SomethingPath;
        }

        public AppOrUserConfig(XDocument xml, AppOrUserConfig defaultValue) : this() {
            if (defaultValue == null) defaultValue = new AppOrUserConfig();
            if (xml == null) {
                PortableMode = defaultValue.PortableMode;
                SomethingPath = defaultValue.SomethingPath;
                return;
            }
            AppOrUserConfig myDummy = new AppOrUserConfig();
            myDummy._Xml = xml;
            PortableMode = myDummy.GetPortableMode(defaultValue.PortableMode);
            SomethingPath = myDummy.GetSomethingPath(defaultValue.SomethingPath);
        }

        public Boolean PortableMode {
            get {
                return GetPortableMode(false);
            }
            set {
                (from e in _Xml.Element("configuration").Element("appSettings").Elements("add") where (string)e.Attribute("key") == "PortableMode" select e).Last().Attribute("value").Value = value ? "on" : "off";
            }
        }

        public String SomethingPath {
            get {
                return GetSomethingPath(@"C:\ProgramData\Cragin\SomeLibrary");
            }
            set {
                (from e in _Xml.Element("configuration").Element("appSettings").Elements("add") where (string)e.Attribute("key") == "SomethingPath" select e).Last().Attribute("value").Value = value ?? "";
            }
        }

        public String XmlContent {
            get {
                return _Xml.ToString(SaveOptions.None);
            }
        }

        private Boolean GetPortableMode(Boolean defaultValue) {
            try {
                String myString = (from e in _Xml.Element("configuration").Element("appSettings").Elements("add") where (string)e.Attribute("key") == "PortableMode" select e).Last().Attribute("value").Value;
                return ToBoolean(myString);
            } catch {
                PortableMode = defaultValue;
                return defaultValue;
            }
        }

        private String GetSomethingPath(String defaultValue) {
            try {
                return (from e in _Xml.Element("configuration").Element("appSettings").Elements("add") where (string)e.Attribute("key") == "SomethingPath" select e).Last().Attribute("value").Value;
            } catch {
                SomethingPath = defaultValue;
                return defaultValue;
            }
        }

        private static Boolean ToBoolean(String value) {
            value = value.Trim();
            switch (value.Length) {
                case 1:
                    if (value[0] == '0') return false;
                    if (value[0] == '1') return true;
                    break;
                case 5:
                    if (value.ToLowerInvariant() == "false") return false;
                    break;
                case 4:
                    if (value.ToLowerInvariant() == "true") return true;
                    break;
                case 3:
                    if (value.ToLowerInvariant() == "off") return false;
                    break;
                case 2:
                    if (value.ToLowerInvariant() == "on") return true;
                    break;
            }
            throw new FormatException();
        }

    }

}

I hope it is useful for you or anybody else out there.

like image 67
Christoph Avatar answered Oct 01 '22 19:10

Christoph


I have solved it myself with a ConfigurationManager, inspired by this example: https://docs.microsoft.com/en-us/dotnet/api/system.configuration.appsettingssection?view=netframework-4.8

My implementation works, but I don't understand it.

like image 44
Yves Daoust Avatar answered Oct 01 '22 19:10

Yves Daoust