5

What is the best way for storing a list of values for using in my program, and enable the user to change them? XML file or INI file and why?

If you have a better suggestion, you are welcome.

EDIT: For getting the most appropriate answer, here are some details. However, the answers can help other users as the custom details are written as comments only.

I have a collection of elements, each is marked with "true" or "false" value.
In my program I would like to use only the elements which are signed with "true" value in the list.

I can use an Enum with the "True" elements only, but I would like the user to choose his desired elements, by changing false to true and vice versa.

However, I do not want him to select the desired elements in every execution. Thats why I think about using a file that could be handy changed according to user preferance.

user3165438
  • 2,631
  • 7
  • 34
  • 54

6 Answers6

4

I suggest to use the .NET default way and this is for sure XML.

With XML you have some advantage over INI format:

  • Hierarchical data structure
  • Complex data types are easy to store (serialization)
  • You can simply validate with a xsd
  • XML is standardized and future proof
CodeTherapist
  • 2,776
  • 14
  • 24
4

In my opinion there are a lot of options, but when not very experienced you should keep it as easy as possible.

One way is to use the Settings you can generate with your project itself.

Just add a settings file to your project by clicking on Add > New Item in the Solution Explorer, and choose Settings File.

This is a useful article on adding lists to your settings file.


In your case you could also opt for a string setting. Just save the integer values to that string using:

Properties.Settings.Default.SettingX = string.Join(",", array);

Read them back using this:

string[] list = Properties.Settings.Default.SettingX.Split(',');
Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • Hofman, Can the user change the settings values? how? – user3165438 Feb 03 '14 at 10:23
  • They can change it through your user interface. If you want the user to change the 'text file' by hand, they can edit the user.config file automatically generated by your program. – Patrick Hofman Feb 03 '14 at 10:26
  • If you really want the user to change it by hand, a settings file is probably not the best option. Update your question to tell WHY you want this and HOW the user will edit it, so we can provide a better answer. – Patrick Hofman Feb 03 '14 at 10:29
  • Hofman, Thanks. I thought my question could help me in the hesitation, however it got me more hesitating.. May have you a link where I can learn more about your suggestion? – user3165438 Feb 03 '14 at 16:02
  • Sure, see MSDN: http://msdn.microsoft.com/en-us/library/aa730869(v=vs.80).aspx – Patrick Hofman Feb 03 '14 at 16:05
  • @Patrik Hofman Thanks. Is this a convenient way for the user to change the values? how exactly? – user3165438 Feb 03 '14 at 16:11
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/46691/discussion-between-patrick-hofman-and-user3165438) – Patrick Hofman Feb 03 '14 at 16:15
3

Answer a question for best could be a huge deal. I think there are several good solutions here is mine, hope it helps.

Actually i work on a lot of apps which all of them need to be configured by end users. After a while spent using 'xml' i changed myself to simple ini file made only of "propertyName"="value".

I wrote a simple class to handle such those kind of files. The usage it's simple

Settings.ConfigProperties cfProps = new Settings.ConfigProperties("c:\\test.ini");
//if the file exists it will be read

//to update or insert new values
cfProps.UpdateValue("myNewProperty", "myValue");

//to get a stored value
String readValue = cfProps.ToString("myNewProperty");

//to permanently write changes to the ini file
cfProps.Save();

here is the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace Settings
{
    public class ConfigProperties
    {
        public class PropertyList<T> : List<ConfigProperty>
        {
            public ConfigProperty this[String propertyName]
            {
                get
                {
                    ConfigProperty ret = null;
                    foreach (ConfigProperty cp in this)
                    {
                        if (cp.Name == propertyName)
                        {
                            ret = cp;
                            break;
                        }
                    }
                    return ret;
                }
                set
                {
                    ConfigProperty ret = null;
                    foreach (ConfigProperty cp in this)
                    {
                        if (cp.Name == propertyName)
                        {
                            ret = cp;
                            break;
                        }
                    }
                    value = ret;
                }
            }
            public PropertyList()
                : base()
            {

            }

            public void Add(String Name, String Value)
            {
                ConfigProperty cp = new ConfigProperty();
                cp.Name = Name;
                cp.Value = Value;
                this.Add(cp);
            }
        }
        public class ConfigProperty
        {
            public String Name { get; set; }
            public String Value { get; set; }
            public ConfigProperty()
            {
                Name = "newPropertyName_" + DateTime.Now.Ticks.ToString();
                Value = "";
            }
            public ConfigProperty(String name, String value)
            {
                Name = name;
                Value = value;
            }
        }

        public String FileName { get; set; }
        public PropertyList<ConfigProperty> CFCollection { get; private set; }

        public ConfigProperties()
            : this(AppDomain.CurrentDomain.BaseDirectory + "config.ini")
        {
        }

        public ConfigProperties(String fileName)
        {
            CFCollection = new PropertyList<ConfigProperty>();
            FileName = fileName;
            if (fileName != null && File.Exists(fileName))
            {
                ReadFile();
            }
        }

        private void ReadFile()
        {
            if (File.Exists(FileName))
            {
                CFCollection = new PropertyList<ConfigProperty>();
                using (StreamReader sr = new StreamReader(FileName, Encoding.Default))
                {
                    while (!sr.EndOfStream)
                    {
                        String line = sr.ReadLine();
                        if (!line.StartsWith("#"))
                        {
                            ConfigProperty cf = new ConfigProperty();
                            String tmp = "";
                            Char splitter = '=';
                            for (int i = 0; i < line.Length; i++)
                            {
                                if (line[i] != splitter)
                                {
                                    tmp += ((Char)line[i]).ToString();
                                }
                                else
                                {
                                    cf.Name = tmp;
                                    tmp = "";
                                    splitter = '\n';
                                }
                            }
                            cf.Value = tmp;
                            if (cf.Name.Length > 0)
                            {
                                CFCollection.Add(cf);
                            }
                        }
                    }

                    sr.Close();
                }
            }
        }

        private void SaveConfigProperty(ConfigProperty prop)
        {
            List<String> output = new List<String>();
            if (File.Exists(FileName))
            {
                using (StreamReader sr = new StreamReader(FileName, Encoding.Default))
                {
                    while (!sr.EndOfStream)
                    {
                        String line = sr.ReadLine();
                        if (line.StartsWith(prop.Name + "="))
                        {
                            output.Add(prop.Name + "=" + prop.Value);
                        }
                        else
                        {
                            output.Add(line);
                        }
                    }
                    sr.Close();
                }
            }

            if (!output.Contains(prop.Name + "=" + prop.Value))
            {
                output.Add(prop.Name + "=" + prop.Value);
            }

            StreamWriter sw = new StreamWriter(FileName, false, Encoding.Default);

            foreach (String s in output)
            {
                sw.WriteLine(s);
            }
            sw.Close();
            sw.Dispose();
            GC.SuppressFinalize(sw);
            sw = null;

            output.Clear();
            output = null;

        }

        public void Save()
        {
            foreach (ConfigProperty cp in CFCollection)
            {
                SaveConfigProperty(cp);
            }
        }

        public void UpdateValue(String propertyName, String propertyValue)
        {
            try
            {
                IEnumerable<ConfigProperty> myProps = CFCollection.Where(cp => cp.Name.Equals(propertyName));
                if (myProps.Count() == 1)
                {
                    myProps.ElementAt(0).Value = propertyValue;
                }
                else if (myProps.Count() == 0)
                {
                    CFCollection.Add(new ConfigProperty(propertyName, propertyValue));
                }
                else
                {
                    throw new Exception("Can't find/set value for: " + propertyName);
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public String ToString(String propertyName)
        {
            try
            {
                return CFCollection.Where(cp => cp.Name.Equals(propertyName)).First().Value;
            }
            catch (Exception ex)
            {
                throw new Exception("Can't find/read value for: " +
                    propertyName, ex);
            }
        }

    }
}
Francesco Milani
  • 415
  • 4
  • 11
2

You should probably use the app.config configuration files.

It uses XML to store the settings, but the Framework takes care of most of the dirty work for you.

If you need to be able to change the setting from the application at runtime, have a look at Settings files.

Community
  • 1
  • 1
Rik
  • 28,507
  • 14
  • 48
  • 67
1

You can use a simple plain file.

Saving a List<int> list to a file.txt:

System.IO.File.WriteAllLines("file.txt", list.Select(v => v.ToString()).ToArray());

Loading:

List<int> loaded = System.IO.File.ReadAllLines("file.txt").Select(l => int.Parse(l)).ToList();

Use extension methods to design a nicer syntax and proper error handling

UPD: generic extension method I can propose (no exception handling):

public static class MyExtensions
{
    public static void SaveToFile<T>(this List<T> list, string filename)
    {
        System.IO.File.WriteAllLines(filename, list.Select(v => v.ToString()).ToArray());
    }

    public static void FillFromFile<T>(this List<T> list, string filename, Func<string, T> parser)
    {
        foreach (var line in System.IO.File.ReadAllLines(filename))
        {
            T item = parser(line);

            list.Add(item);
        }
    }
}

Use it like that:

List<int> list = new List<int>() { 0, 1, 2 };

list.SaveToFile("numbers.txt");

List<int> loaded = new List<int>();

loaded.FillFromFile("numbers.txt", (l) => int.Parse(l));
astef
  • 8,575
  • 4
  • 56
  • 95
  • Can you please guide me a little more about "Use extension methods to design a nicer syntax and proper error handling", Thanks. – user3165438 Feb 03 '14 at 10:03
  • What if you have a list of `string` objects? Reading all lines can cause the list to become invalid when an item contains a line break. – Patrick Hofman Feb 03 '14 at 10:05
  • @PatrickHofman You're right, this solution is not suitable for storing strings with line breaks. Although, it can be improved if neccessary. Note that any human-readable solutions has such problems which are often solved by "special" characters – astef Feb 03 '14 at 10:19
  • True, that's why you should revert to standards if possible. – Patrick Hofman Feb 03 '14 at 10:30
1

I would like to suggest JSON format

  • It can be read/edited by human
  • It supports hierarchy
  • It is not such complex like XML
  • It can be used like a charm (without ugly ini parsing)

formatted file looks like

{"DevCommentVisibility":27,"CommentVisibility":1,"IssueStatusAfterCheckin":4,"Log":{"Debug":false,"Info":true,"Warn":true,"FileName":"d:\temp\2.log"},"Language":"en"}

public static string Serialize(T pSettings)
{
    return (new JavaScriptSerializer()).Serialize(pSettings);
}

public static T Load(string fileName)
{
    T t = new T();
    if (File.Exists(fileName))
        try
        {
            t = (new JavaScriptSerializer()).Deserialize<T>(File.ReadAllText(fileName));
            var s = t as SettingsFoo;
            if (s != null)
                s.FileName = fileName;
        }
        catch (Exception ex)
        {
            Trace.WriteLine(string.Format("failed to parse settings file {0}", fileName));
            Trace.WriteLine(ex.Message);
        }
    else
    {
        Trace.WriteLine(string.Format("failed load settings '{0}' absolute '{1}'", fileName, Path.GetFullPath(fileName)));
        Save(t);
    }
    return t;
}
oleksa
  • 3,688
  • 1
  • 29
  • 54
  • Thanks for reply. Couldn't an XML file changed by the user? – user3165438 Feb 03 '14 at 10:20
  • Yes, a XML is also very human friendly.. – CodeTherapist Feb 03 '14 at 10:46
  • @user3165438 actually I prefer JSON because it doesn't require end tags (comparing to XML). I use JSON usually in small tools. I have some experience using app.config with custom section handlers or by deriving from ConfigurationSection and can say that only one advantage for windows application is that you can protect any section using ProtectSection method. However passwords in JSON can be processed with ProtectedData.Protect/Unprotect methods. – oleksa Feb 03 '14 at 11:57