views:

246

answers:

2

I have to store a complex type in the application settings. I thought that storing it as XML would work best.

The problem is I don't know how store XML. I prefer to store it as a managed XML rather than using just a string of raw XML having to parse it on each access. I managed to set the Type column of the setting to XDocument, but I was unable to set its value.

Is there a way to use XDocument or XML in application settings?

Update

I found a way, simply by editing the .settings file with the xml editor.

I changed it to a custom serializable dictionary, but I get the following error when I try to access the setting-property (I set it to a serialized representation of the default value).

The property 'Setting' could not be created from it's default value.
Error message: There is an error in XML document (1, 41).

Any ideas will be appreciated.

+1  A: 

What I did (even I don't like so much - but works) is:

I created simple serializable classes of my values:

<Xml.Serialization.XmlRoot("Rooms")> _
Public Class Rooms : Inherits List(Of Room)
End Class

<Serializable()> _
Public Class Room
    Private m_Room As String
    Public Property Room() As String
        Get
            Return m_Room
        End Get
        Set(ByVal value As String)
            m_Room = value
        End Set
    End Property

    Private m_Sections As New List(Of Section)
    Public ReadOnly Property Sections() As List(Of Section)
        Get
            Return m_Sections
        End Get
    End Property
End Class

<Serializable()> _
Public Class Section
    Private m_Section As String
    Public Property Section() As String
        Get
            Return m_Section
        End Get
        Set(ByVal value As String)
            m_Section = value
        End Set
    End Property
End Class

Then in the .settings file, I edited the type of the setting (the .setting file opened with the xml editor) to full name of Rooms (i.e. MyProject.Rooms). Then I made myself a sample deserialized sample and copied it to the value field in the .settings editor to have a default value. it works great. It is indeed not a dictionary, but I could still implement in the Rooms class and internal dictionary (that returns Sections by Room.Room.

Any good ideas are still welcommed, I am still looking for a way to be able to use IDictinoarys in the .settings file.

Besides, I opened a connection for it, please be kind and vote!

Shimmy
+1  A: 

What I ended up doing because this was the path of least resistance was use the built-in Configuration classes in the .NET Fx. Even though the below code is in C# you should be able to convert it to VB.NET with little difficulty (or edit and compile it into an assembly you can reference from your project).

You will note that the ConfigurationElementCollection class can be converted into a dictionary of key/value pairs with little difficulty (you may have to use reflection for the value pairs or the classes you want to store as value pairs could take settings classes that inherit from ConfigurationElement as a constructor argument).

// ConfigurationElement.cs
public class ConfigurationElement : System.Configuration.ConfigurationElement
{
    protected T GetValue<T>(string key, T defaultValue)
    {
        var value = default(T);
        if (base[key] != null)
        {
            var str = base[key].ToString();
            try
            {
                if (!String.IsNullOrEmpty(str))
                    value = (T)Convert.ChangeType(str, typeof(T));
            }
            catch // use the default
            {
            }
        }

        return value;
    }
}

// ConfigurationElementCollection.cs
public abstract class ConfigurationElementCollection<TElement,TKey> : 
    ConfigurationElementCollection, 
    IEnumerable<TElement> where TElement : System.Configuration.ConfigurationElement, new()
{
    public TElement this[int index]
    {
        get { return (TElement)BaseGet(index); }
    }

    public TElement this[TKey key]
    {
        get { return (TElement)BaseGet(key); }
    }

    protected override System.Configuration.ConfigurationElement CreateNewElement()
    {
        return new TElement();
    }

    protected override object GetElementKey(System.Configuration.ConfigurationElement element)
    {
        return GetElementKey((TElement)element);
    }
    protected abstract TKey GetElementKey(TElement element);

    public TKey[] GetAllKeys()
    {
        var keys = BaseGetAllKeys();
        var ret = new TKey[keys.Length];
        for (var i = 0; i < keys.Length; i++)
            ret[i] = (TKey)keys[i];

        // done
        return ret; 
    }
    public void Add(TElement element)
    {
        BaseAdd(element);
    }
    public void Remove(TElement element)
    {
        BaseRemove(element);
    }
    public void Clear()
    {
        BaseClear();
    }

    IEnumerator<TElement> IEnumerable<TElement>.GetEnumerator()
    {
        foreach (TElement element in this)
        {
            yield return element;
        }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        throw new System.NotImplementedException();
    }
}

And here's an example where I use the above base class code for our sharding strategy in our system (names altered to protect the innocent):

<!-- web.config -->
<!-- ... -->
<configuration>
    <configSections>
        <section name="sharding" type="Domain.ShardingSection, Domain.Configuration" />
    </configSections>
</configuration>
<!-- ... -->
<sharding>
    <configurationMappings>
        <add lastDigit="0" sqlMapFileName="Shard-0.SqlMap.config" />
        <add lastDigit="1" sqlMapFileName="Shard-1.SqlMap.config" />
        <add lastDigit="2" sqlMapFileName="Shard-2.SqlMap.config" />
        <add lastDigit="3" sqlMapFileName="Shard-3.SqlMap.config" />
        <add lastDigit="4" sqlMapFileName="Shard-4.SqlMap.config" />
        <add lastDigit="5" sqlMapFileName="Shard-5.SqlMap.config" />
        <add lastDigit="6" sqlMapFileName="Shard-6.SqlMap.config" />
        <add lastDigit="7" sqlMapFileName="Shard-7.SqlMap.config" />
        <add lastDigit="8" sqlMapFileName="Shard-8.SqlMap.config" />
        <add lastDigit="9" sqlMapFileName="Shard-9.SqlMap.config" />
    </configurationMappings>
</sharding>

And then the configuration classes represented by the XML instance above:

// ShardElement.cs
public class ShardElement : ConfigurationElement
{
    [ConfigurationProperty("lastDigit", IsKey=true, IsRequired=true)]
    public int LastDigit
    {
        get { return (int)this["lastDigit"]; }
    }

    [ConfigurationProperty("sqlMapFileName", IsRequired=true)]
    public string SqlMapFileName
    {
        get { return (string)this["sqlMapFileName"]; }
    }
}

// ShardElementCollection.cs
public class ShardElementCollection : ConfigurationElementCollection<ShardElement, int>
{
    protected override int GetElementKey(ShardElement element)
    {
        return element.LastDigit;
    }
}

 // ShardingSection.cs
 public class ShardingSection : ConfigurationSection
{
    public const string Name = "sharding";

    [ConfigurationProperty("configurationMappings")]
    public ShardingElementCollection ConfigurationMappings
    {
        get { return (ShardingElementCollection)base["configurationMappings"]; }
    }
}

While its not true IDictionary in a *.config file it can handle the job, and if your configuration file is updated at runtime you don't have to restart the application or recycle the AppPool to get the new values.

cfeduke