7

Easy Loading and Saving of Settings, Stored as a Simple Class

 2 years ago
source link: https://www.codeproject.com/Tips/5325914/Easy-Loading-and-Saving-of-Settings-Stored-as-a-Si
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Introduction

While working on an application, I got tired of adding lines for every setting I created, so I thought that there had to be better way of processing them all. This is the result.

This piece of code offers to use a class for settings. To add a new setting, one simply has to create a new property in the class. Settings are stored in a simple class, a setting is a property in that class. No extra code is needed to ensure that it gets saved or loaded. The saver will read all properties and save them all to the file. The loader will create the class, and thereby set default settings. Any value present in the file will be applied to the class. At the end, the class is returned for the software to use.

The Code is Simple

It starts with setting up the storage space and the file name for settings. One may change this as one finds necessary.

Next, for saving, all properties are read and saved to file, one by one.

For reading, the file is read, and if the name exists as a property in the settings class, then it is applied to the class. This also enables to have some validation within the set { } in the settings class.

The class used can be named anything, and it is possible to have multiple files and settings.
This example is using a default file name, settings.config, but one can use any file name to create more.

Example

Here is a simple example of a settings class:

Copy Code
public class Settings
{
    public int Setting1 { get; set; }
    public bool Setting2 { get; set; }
    public double Setting3 { get; set; } = 7.6;
    public DateTime Setting4 { get; set; }
    public string Setting5 { get; set; } = "test";
    public List<string> Setting6 { get; set; } = new List<string>()
                                       { "test1", "test2" };
}

And how it can be used:

Copy Code
var settings = (Settings)GetSettings("Settings"); // name of class, from above
settings.Setting1 = Settings.setting1 + 1;        // change some settings
settings.Setting4 = DateTime.Now;
SaveSettings(settings);                   // save all properties of the class to file		

Types of Settings

As mentioned before, this will read the properties of the class and apply them. As of now, this has implemented the following types: string, boolean, int, double, Datetime and list of strings (List<string>).
For other types, the code will give an exception, but the code is easily expandable.

Storage Location

The present code will use the local application folder, where it will create a folder with name of the executing (your) application.

Error Handling

This example has basic error handling. One can add additional error handling if needed.

Saving a Class

Shrink ▲   Copy Code
public void SaveSettings(object settings, string filename = "settings.config")
{
    // create storage folder and file name
    string storageFolder = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
        Path.GetFileNameWithoutExtension(Application.ExecutablePath));
    if (!Directory.Exists(storageFolder))
        Directory.CreateDirectory(storageFolder);
    if (!Directory.Exists(storageFolder))
        throw new Exception($"Could not create folder {storageFolder}");
    string settingsFileName = Path.Combine(storageFolder, filename);
    
    // create file and process class, save properties one by one
    XmlWriterSettings xmlWriterSettings = new XmlWriterSettings()
    {
        Indent = true,
    };
    XmlWriter writer = XmlWriter.Create(settingsFileName, xmlWriterSettings);
    using (writer)
    {
        writer.WriteStartElement("settings");
        var classType = settings.GetType();
        var props = classType.GetProperties();
        for (int i = 0; i < props.Length; i++)
        {
            string fileSetting = char.ToLower(props[i].Name[0]) + props[i].Name.Substring(1);
            
            if (props[i].PropertyType == typeof(string))
            {
                var sett = classType.GetProperty(props[i].Name).GetValue(settings);
                if (sett != null)
                {
                    string s = sett.ToString();
                    if (!string.IsNullOrEmpty(s))
                        writer.WriteElementString(fileSetting, s);
                }
            }
            else if (props[i].PropertyType == typeof(int))
            {
                writer.WriteElementString(fileSetting, 
                classType.GetProperty(props[i].Name).GetValue(settings).ToString());
            }
            else if (props[i].PropertyType == typeof(double))
            {
                writer.WriteElementString(fileSetting, 
                classType.GetProperty(props[i].Name).GetValue(settings).ToString());
            }
            else if (props[i].PropertyType == typeof(DateTime))
            {
                var dt = (DateTime)(classType.GetProperty(props[i].Name).GetValue(settings));
                writer.WriteElementString(fileSetting, dt.ToOADate().ToString());
            }
            else if (props[i].PropertyType == typeof(bool))
            {
                writer.WriteElementString(fileSetting,
                classType.GetProperty(props[i].Name).GetValue(settings).ToString());
            }
            else if (props[i].PropertyType == typeof(List<string>))
            {
                List<string> values =
                classType.GetProperty(props[i].Name).GetValue(settings) as List<string>;
                string val = string.Join(",", values.ToArray());
                writer.WriteElementString(fileSetting, val);
            }
            else
                throw new Exception($"Unknown setting type found: {props[0].PropertyType}");
        }
        
        writer.WriteEndElement();
        writer.Flush();
    }
}

Loading a Class

Here is the code to read a file and apply properties into the class and return it.
Note that some parts throw an exception, but can be changed to simply returning default settings.
Also note, that the class returns an object, as it is not possible to return the actual class. Therefore, it is needed to typecast the result the class used.

Example:

Copy Code
var mySettings = (MySettingsClass)GetSettings("MySettingsClass"); // here, the class
                                               // is used is named MySettingsClass
Shrink ▲   Copy Code
public object GetSettings(string settingsClassName, string filename = "settings.config")
{
// get class type and create default class
    var settingsType = (from assembly in AppDomain.CurrentDomain.GetAssemblies()
                        from t in assembly.GetTypes()
                        where t.Name == settingsClassName
                        select t).FirstOrDefault();
    object settings = Activator.CreateInstance(settingsType);
    
// create storage folder and file name
    string storageFolder = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
        Path.GetFileNameWithoutExtension(Application.ExecutablePath));
    if (!Directory.Exists(storageFolder))
        Directory.CreateDirectory(storageFolder);
    if (!Directory.Exists(storageFolder))
        throw new Exception($"Could not create folder {storageFolder}");
    string settingsFileName = Path.Combine(storageFolder, filename);
    
// recreate file if missing and return default settings
    if (!File.Exists(settingsFileName))
    {
        SaveSettings(settings);
        return settings;
    }
    
// read and process file
    XmlDocument settingsFile = new XmlDocument();
    try
    {
        settingsFile.Load(settingsFileName);
    }
    catch (Exception ex)
    {
        throw ex;
        // option to return default data
        SaveSettings(settings);
        return settings;
    }
    XmlNode settingsNode = null;
    int n = 0;
    while (n < settingsFile.ChildNodes.Count && settingsNode == null)
    {
        if (settingsFile.ChildNodes[n].Name.ToLower() == "settings")
            settingsNode = settingsFile.ChildNodes[n];
        n++;
    }
    if (settingsNode == null)
    {
        throw new Exception($"Settings section is not found 
                            in settings file {settingsFileName}");
        // option to return default data
        return settings;
    }
    var classType = settings.GetType();
    var props = classType.GetProperties();
    foreach (XmlNode setting in settingsNode.ChildNodes)
    {
        if (setting.ParentNode.Name.ToLower() != "settings")
            break;
        if (setting.NodeType != XmlNodeType.Element)
            continue;
            
        var settingName = props.Where
        (w => string.Compare(w.Name, setting.Name, true) == 0).ToList();
        if (settingName.Count == 0)
            continue;
        if (string.IsNullOrEmpty(settingName[0].Name))
            continue;
            
        // parse setting as type defines
        if (settingName[0].PropertyType == typeof(string))
            classType.GetProperty(settingName[0].Name).SetValue(settings, setting.InnerText);
        else if (settingName[0].PropertyType == typeof(int))
        {
            int val = 0;
            if (int.TryParse(setting.InnerText, out val))
                classType.GetProperty(settingName[0].Name).SetValue(settings, val);
        }
        else if (settingName[0].PropertyType == typeof(double))
        {
            double val = 0;
            if (double.TryParse(setting.InnerText, out val))
                classType.GetProperty(settingName[0].Name).SetValue(settings, val);
        }
        else if (settingName[0].PropertyType == typeof(DateTime))
        {
            double val = 0;
            if (double.TryParse(setting.InnerText, out val))                        
                classType.GetProperty(settingName[0].Name).SetValue
                (settings, DateTime.FromOADate(val));
        }
        else if (settingName[0].PropertyType == typeof(bool))
        {
            bool val = (string.Compare("true", 
            setting.InnerText, true) == 0) || setting.InnerText == "1";
            classType.GetProperty(settingName[0].Name).SetValue(settings, val);
        }
        else if (settingName[0].PropertyType == typeof(List<string>))
        {
            string val = setting.InnerText.Trim();
            if (string.IsNullOrEmpty(val))
            {
                classType.GetProperty(settingName[0].Name).SetValue
                (settings, new List<string>());
                continue;
            }
            List<string> values = val.Split(',').ToList();
            classType.GetProperty(settingName[0].Name).SetValue(settings, values);
        }
        else
            throw new Exception
            ($"Unknown setting type found: {settingName[0].PropertyType}");
    }
    return settings;
}

Conclusion

This is just a simple way of using a class as a parameter, and processing the class' properties. It has the most common types, but can easily be expanded. Also, the file name and location is easily changed.
Enjoy!

History

  • 23rd February, 2022: Initial version

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK