Johan Stenberg's blog

My setting values for collection types are lost when I save my settings!?

The client configuration runtime will use (in order) the following methods to serialize/deserialize setting values

1) The sheme indicated by the SettingsSerializeAs attribute

2) TypeConverter.To/FromString() for the type converter associated with the type of the setting if it exists and supports converting to/from strings

3) XML Serialization

If you want to use a type that doesn't have the appropriate type converter and doesn't support XML serialization well (which happens to be true for quite a few of the collection types) you can still take advantage of client config by providing your own type converter. Below is a sample that (hopefully) will show what can be done. The sample uses the base64 encoded representation of the binary serialized values as the string representation for the value - this is not exactly easily read by humans, so depending on your specific requirements, you may want to use a different string representation of your data...

Option Strict On

Module Module1

    Public Sub Main()
        ' Associate a type descriptor with the setting type. This could also be done by putting a TypeDescriptionProvider attribute
        ' on the type declaration itself.
        System.ComponentModel.TypeDescriptor.AddProvider( _
            New SerializableTypeDescriptionProvider(Of System.Collections.Generic.Dictionary(Of String, String)), _
            GetType(System.Collections.Generic.Dictionary(Of String, String)))

        ' Create an instance of my test class
        Dim s As New MyTestSettingsClass
        s.Foo = New System.Collections.Generic.Dictionary(Of String, String)
        s.Foo.Add("Bar", "Zoo")
        s.Foo.Add("Doo", "Goo")
        s.Save()

        ' Load the same settings again...
        Dim s2 As New MyTestSettingsClass
        Debug.WriteLine(s2.Foo.Count)
    End Sub

    ''' <summary>
    '''
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <remarks></remarks>
    Public Class SerializableTypeDescriptionProvider(Of T)
        Inherits System.ComponentModel.TypeDescriptionProvider

        Public Overrides Function GetTypeDescriptor(ByVal objectType As System.Type, ByVal instance As Object) As System.ComponentModel.ICustomTypeDescriptor
            If Not objectType Is GetType(T) Then
                Return MyBase.GetTypeDescriptor(objectType, instance)
            End If

            Dim parentTypeDescriptor As System.ComponentModel.ICustomTypeDescriptor = MyBase.GetTypeDescriptor(objectType, instance)
            If parentTypeDescriptor IsNot Nothing Then
                Dim existingTypeConverter As System.ComponentModel.TypeConverter = _
                    parentTypeDescriptor.GetConverter()
                If existingTypeConverter IsNot Nothing _
                    AndAlso existingTypeConverter.CanConvertFrom(GetType(String)) _
                    AndAlso existingTypeConverter.CanConvertTo(GetType(String)) _
                Then
                    ' If the parentdescriptor already supported to/from string, we might as well
                    ' use that... We don't really *have* to check this, but...
                    Return MyBase.GetTypeDescriptor(objectType, instance)
                End If
            End If

            Return New SerializableConverterProvider(Of T)(parentTypeDescriptor)
        End Function
    End Class

    ' Simple generic implementation that will provide a type converter
    ' that supports to/from string using base64 encoded string based on
    ' binary serialization
    Public Class SerializableConverterProvider(Of T)
        Inherits System.ComponentModel.CustomTypeDescriptor

        Sub New(ByVal parentTypeDescriptor As System.ComponentModel.ICustomTypeDescriptor)
            MyBase.New(parentTypeDescriptor)
        End Sub

        Public Overrides Function GetConverter() As System.ComponentModel.TypeConverter
            Return New SerializeAsStringTypeConverter()
        End Function

        Private Class SerializeAsStringTypeConverter
            Inherits System.ComponentModel.TypeConverter

            Public Overrides Function CanConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal sourceType As System.Type) As Boolean
                If sourceType Is GetType(String) Then
                    Return True
                Else
                    Return MyBase.CanConvertFrom(context, sourceType)
                End If
            End Function

            Public Overrides Function CanConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal destinationType As System.Type) As Boolean
                If destinationType Is GetType(String) Then
                    Return True
                Else
                    Return MyBase.CanConvertTo(context, destinationType)
                End If
            End Function

            Public Overrides Function ConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object, ByVal destinationType As System.Type) As Object
                If destinationType Is GetType(String) Then
                    Dim serializer As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter()
                    Dim stream As New System.IO.MemoryStream
                    serializer.Serialize(stream, value)
                    Return System.Convert.ToBase64String(stream.ToArray())
                Else
                    Return MyBase.ConvertTo(context, culture, value, destinationType)
                End If
            End Function

            Public Overrides Function ConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object) As Object
                If TypeOf value Is String Then
                    Dim serializer As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter()
                    Dim bytes() As Byte = System.Convert.FromBase64String(DirectCast(value, String))
                    Dim stream As New System.IO.MemoryStream(bytes)
                    Return serializer.Deserialize(stream)
                Else
                    Return MyBase.ConvertFrom(context, culture, value)
                End If
            End Function

        End Class
    End Class

    ' Since the settings designer isn't very good at handling generic types,
    ' I have created a (very) simplistic class that expose a setting of a type
    ' that ordinarily would have been problematic to persist...
    Public Class MyTestSettingsClass
        Inherits System.Configuration.ApplicationSettingsBase

        <System.Configuration.UserScopedSetting()> _
        Public Property Foo() As System.Collections.Generic.Dictionary(Of String, String)
            Get
                Return CType(Me("Foo"), System.Collections.Generic.Dictionary(Of String, String))
            End Get
            Set(ByVal value As System.Collections.Generic.Dictionary(Of String, String))
                Me("Foo") = value
            End Set
        End Property

    End Class
End Module

This posting is provided "AS IS" with no warranties, and confers no rights.

 

 

Published Thursday, November 17, 2005 3:01 PM by Johan (Stenis) Stenberg

Comments

No Comments
New Comments to this post are disabled

© 2009 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement
Microsoft
Page view tracker