Store Dictionary<string,string> in application settings
Asked Answered
G

9

42

I have a dictionary of strings that i want the user to be able to add/remove info from then store it for them so it they can access it the next time the program restarts

I am unclear on how i can store a dictionary as a setting. I see that under system.collections.special there is a thing called a stringdictionary but ive read that SD are outdated and shouldn't be used.

also in the future i may have need to store a dictionary that is not strings only (int string)

how would you store a dictionary in the settings file for a .net application?

Groundwork answered 28/5, 2009 at 17:3 Comment(2)
Related: #2890771Subheading
these days, if you're using Json.Net just serialize the object into a string and store that. See https://mcmap.net/q/150820/-entity-framework-code-first-can-39-t-store-list-lt-string-gt for details.Brainstorming
C
21

The simplest answer would be to use a row & column delimiter to convert your dictionary to a single string. Then you just need to store 1 string in the settings file.

Cynic answered 28/5, 2009 at 17:8 Comment(6)
Good idea - use something like JSON serialization to make this process relatively painless.Endearment
could you elborate on that a little?Groundwork
JSON serialization is of the form {"object": { "key": "value", "key": "value" } }Rabush
How would i do that? Im thinking i could just do a foreach loop of each key value pair into a string then save the string or something but i would like to do this as correctly as possibleGroundwork
There are JSON serialization libraries out there in the wild - you don't have to do this from scratch; but if you wanted to, it shouldn't be too hard to write up a very simple one.Endearment
Similar but simpler is to use a StringCollection (which can be stored as as setting). Then, you only need to separate the key from the value (do not use '\0' - the .Net settings code is happy saving StringCollection setting entry with with a zero byte but will not load it later).Bugbee
G
47

You can use this class derived from StringDictionary. To be useful for application settings it implements IXmlSerializable. Or you can use similar approach to implement your own XmlSerializable class.

public class SerializableStringDictionary : System.Collections.Specialized.StringDictionary, System.Xml.Serialization.IXmlSerializable
{
    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        while (reader.Read() &&
            !(reader.NodeType == System.Xml.XmlNodeType.EndElement && reader.LocalName == this.GetType().Name))
        {
            var name = reader["Name"];
            if (name == null)
                throw new FormatException();

            var value = reader["Value"];
            this[name] = value;
        }
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        foreach (System.Collections.DictionaryEntry entry in this)
        {
            writer.WriteStartElement("Pair");
            writer.WriteAttributeString("Name", (string)entry.Key);
            writer.WriteAttributeString("Value", (string)entry.Value);
            writer.WriteEndElement();
        }
    }
}

Resulting XML fragment will look similar to:

...
<setting name="PluginSettings" serializeAs="Xml">
    <value>
        <SerializableStringDictionary>
            <Pair Name="property1" Value="True" />
            <Pair Name="property2" Value="05/01/2011 0:00:00" />
        </SerializableStringDictionary>
    </value>
</setting>
...
Grosberg answered 31/5, 2011 at 23:50 Comment(5)
I don't know why this answer is not accepted. Very useful, thanks!Palladic
Worked for me, but I added this.Clear(); statement to the top of ReadXml method, just to make sure there aren't any stale items in the dictionary.Maryammaryann
How would one go about extending this to a Generic dictionary?Shiau
Any designer support for this?Triolet
@Shiau it's been done, see weblogs.asp.net/pwelter34/444961.Fillister
C
21

The simplest answer would be to use a row & column delimiter to convert your dictionary to a single string. Then you just need to store 1 string in the settings file.

Cynic answered 28/5, 2009 at 17:8 Comment(6)
Good idea - use something like JSON serialization to make this process relatively painless.Endearment
could you elborate on that a little?Groundwork
JSON serialization is of the form {"object": { "key": "value", "key": "value" } }Rabush
How would i do that? Im thinking i could just do a foreach loop of each key value pair into a string then save the string or something but i would like to do this as correctly as possibleGroundwork
There are JSON serialization libraries out there in the wild - you don't have to do this from scratch; but if you wanted to, it shouldn't be too hard to write up a very simple one.Endearment
Similar but simpler is to use a StringCollection (which can be stored as as setting). Then, you only need to separate the key from the value (do not use '\0' - the .Net settings code is happy saving StringCollection setting entry with with a zero byte but will not load it later).Bugbee
B
4

If you don't need to use the settings designer or edit your settings with a text editor, you can create a simple class that derives from ApplicationSettingsBase:

namespace MyNamespace
{
    using System.Collections.Generic;
    using System.Configuration;

    /// <summary>
    /// Persistent store for my parameters.
    /// </summary>
    public class MySettings : ApplicationSettingsBase
    {
        /// <summary>
        /// The instance lock.
        /// </summary>
        private static readonly object InstanceLock = new object();

        /// <summary>
        /// The instance.
        /// </summary>
        private static MySettings instance;

        /// <summary>
        /// Prevents a default instance of the <see cref="MySettings"/> class 
        /// from being created.
        /// </summary>
        private MySettings()
        {
            // don't need to do anything
        }

        /// <summary>
        /// Gets the singleton.
        /// </summary>
        public static MySettings Instance
        {
            get
            {
                lock (InstanceLock)
                {
                    if (instance == null)
                    {
                        instance = new MySettings();
                    }
                }

                return instance;
            }
        }

        /// <summary>
        /// Gets or sets the parameters.
        /// </summary>
        [UserScopedSetting]
        [SettingsSerializeAs(SettingsSerializeAs.Binary)]
        public Dictionary<string, string> Parameters
        {
            get
            {
                return (Dictionary<string, string>)this["Parameters"];
            }

            set
            {
                this["Parameters"] = value;
            }
        }
    }
}

The real trick is the [SettingsSerializeAs(SettingsSerializeAs.Binary)] attribute. Most (all?) classes can get serialized this way where SettingsSerializeAs.String or SettingsSerializeAs.Xml wont work for a Dictionary.

Use this in your code as you would normal settings:

// this code untested...
MySettings.Instance.Parameters["foo"] = "bar";
MySettings.Instance.Parameters.Save();
MySettings.Instance.Parameters.Reload();
string bar;
if (!MySettings.Instance.Parameters.TryGetValue("foo", out bar))
{
    throw new Exception("Foobar");
}

If you want the Dictionary to serialize into something user editable, you must derive from Dictionary and play with TypeConverter (see Using Custom Classes with Application Settings).

Bugbee answered 9/10, 2012 at 20:11 Comment(1)
I've got an NullReferenceException on MySettings.Instance.Parameters ... any idea? I have to admit, that i have no idea whats happening there.Mayfly
E
3

Other than doing something like David's suggests, I would look into alternate storage for the Dictionary. Ultimately the Settings object serializes to disk.

Endearment answered 28/5, 2009 at 17:12 Comment(0)
M
3

Have you considered using XML to store your dictionary? That would provide a certain amount of extensibility if in the future you decide you want to be able to store other types of dictionaries. You might do something like:

<dictionary>
   <entry key="myKey">
      [whatever data you like]
   </entry>
</dictionary>

Might be overkill, but you'd also be prepared in the case that you wanted to store more complex data, like custom objects.

Mortgagee answered 28/5, 2009 at 17:28 Comment(0)
J
2

You can also use a System.Collections.Specialized.StringCollection by putting key on even index and values on odd index.

/// <summary>
/// Emulate a Dictionary (Serialization pb)
/// </summary>
private static string getValue(System.Collections.Specialized.StringCollection list, string key)
{
    for (int i = 0; i * 2 < list.Count; i++)
    {
        if (list[i] == key)
        {
            return list[i + 1];
        }
    }
    return null;
}

/// <summary>
/// Emulate a Dictionary (Serialization pb)
/// </summary>      
private static void setValue(System.Collections.Specialized.StringCollection list, string key, string value)
{
    for (int i = 0; i * 2 < list.Count; i++)
    {
        if (list[i] == key)
        {
            list[i + 1] = value;
            return;
        }
    }
    list.Add(key);
    list.Add(value);
}
Journalistic answered 3/8, 2010 at 13:2 Comment(0)
A
2

Edit: This will return a Hashtable (for whatever reason, despite being a 'DictionarySectionHandler'). However, being that Hashtables and Dictionaries are so similar, it shouldn't be a large issue (though I realize Dictionaries are newer, parameterized, etc; I would have preferred dicitonaries myself, but this is what .NET gives us).


The best answer I just found for this is here. It returns a typesafe collection witout any muddling in code to transform it, and you create an obvious (and simple) collection in your .config file. I'm using this and it's quite straight forward for any future programmer (including yourself). It allows for stronger typing and more flexibility, without any overly-complicated and unnecessary parsing.

Allene answered 21/10, 2010 at 20:34 Comment(0)
E
1

You could create a custom class that exposes a Dictionary as a public property. Then you can specify this custom type as the type for your setting.

Edit:

I have just read that, for some reason, a generic dictionary cannot be XML-serialized, so my solution will probably not work (I haven't tested it though...). That's strange, because a generic list can be serialized without any problem.

You could still create a custom class that can be set as a user setting, but you will need to have a list exposed as a property instead of a dictionary.

Epigone answered 28/5, 2009 at 17:12 Comment(2)
I'm not sure i follow. create a class that contains a dictionary then save that class?Groundwork
Exactly, but because generic dictionary is not serializable (see edit) it would need to be a list instead.Epigone
S
1

You can store a StringCollection. It is similar to this solution.

I made 2 extension methods to convert between StringCollection and a Dictionary. This is the easiest way I could think of.

public static class Extender
{
    public static Dictionary<string, string> ToDictionary(this StringCollection sc)
    {
        if (sc.Count % 2 != 0) throw new InvalidDataException("Broken dictionary");

        var dic = new Dictionary<string, string>();
        for (var i = 0; i < sc.Count; i += 2)
        {
            dic.Add(sc[i], sc[i + 1]);
        }
        return dic;
    }

    public static StringCollection ToStringCollection(this Dictionary<string, string> dic)
    {
        var sc = new StringCollection();
        foreach (var d in dic)
        {
            sc.Add(d.Key);
            sc.Add(d.Value);
        }
        return sc;
    }
}

class Program
{
    static void Main(string[] args)
    {
        //var sc = new StringCollection();
        //sc.Add("Key01");
        //sc.Add("Val01");
        //sc.Add("Key02");
        //sc.Add("Val02");

        var sc = Settings.Default.SC;

        var dic = sc.ToDictionary();
        var sc2 = dic.ToStringCollection();

        Settings.Default.SC = sc2;
        Settings.Default.Save();
    }
}
Subheading answered 21/10, 2010 at 21:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.