Adding Complex Types in .config Files

All serialization formats but .config files support true object serialization - meaning objects are serialized based on general serialization rules of the XML or JSON parsers in .NET and that you can simply serialize most objects regardless of hierarchy. .NET .config files however are flat and only offer key value pairs for configuration settings. Most of the time flat configuration values are perfectly appropriate. You can create multiple configuration objects to separate out options if necessary as well.

However, occasionally it's useful to include small child objects or even object lists as part of a configuration. The ApplicationConfiguration class allows serialization of some simple objects into string formats:

  • Via ToString()/FromString()
  • Via TypeConverters
  • IList conversion

ToString()/FromString()

You can implement objects that override ToString() and implement a static FromString() method to perform two-way serialization.

This is useful if you need to store some values that are semi-complex (typically for single level objects) that can be represented as delimited strings. For example, imagine you'd want to store license information in a .config file.

You can do this in the following manner:

public class LicenseInformation
{
    public string Name { get; set; }
    public string Company { get; set; }
    public int LicenseKey { get; set; }

    public static LicenseInformation FromString(string data)
    {
        return StringSerializer.Deserialize<LicenseInformation>(data,",");
    }

    public override string ToString()
    {
        // Westwind.Utilities.StringSerializer serializes flat objects to
        // a delimited string value - easy way to 'serialize'
        // but you can use string.Split/Join to do this manually
        // produces: "Rick,West Wind,10"
        return StringSerializer.SerializeObject(this, ",");
    }
}

If you you now have a configuration class that has a configuration property that includes this class as a property:

public class CustomConfigFileConfiguration : Westwind.Utilities.Configuration.AppConfiguration
{
        public string ApplicationName { get; set; }      
        public LicenseInformation ComplexType { get; set; }

        public CustomConfigFileConfiguration()
        {
            ApplicationName = "Configuration Tests";
            ComplexType = new LicenseInformation()
            {
                Name = "Rick", 
                Company = "West Wind",
                LicenseKey = 10
            };
        }
}

This will serialize into the .config file as:

<add key="ComplexType" value="Rick,West Wind,10" />

Note that you don't have to use StringSerializer class used in this example - it doesn't matter how you create the string as long as the string can be deserialized by FromString() in some way back into the object.

When the value is read this value is serialized back into the original ComplexType with the values from the config applied. This works as long as the type has a parameterless constructor.

It's a little hacky, but it's a great way to get flat complex values into into a single string in your config file.

TypeConverters for .Config persistence of complex objects

For .Config file however a different mechanism is required because values are simply stored in attributes of the Config file and no nesting is allowed in these config file sections. This means output must be generated into a plain string format that can then be stored in the config file attribute for the high level object property or field.

In order for this to work you have to either use types that support a TypeConverter natively (many .NET framework objects do this) or you can implement a custom TypeConverter that provides string serialization.

Here's an example of a custom type with a type converter implementation:

[TypeConverter(typeof(CustomCustomerTypeConverter)), Serializable()]
public class CustomCustomer
{
	public string Name = "Rick Strahl";
	public string Company = "West Wind";
	public decimal OrderTotal = 100.90M;
}

public class CustomCustomerTypeConverter : System.ComponentModel.ExpandableObjectConverter
{
	public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
	{
		if (destinationType == typeof(string) )
			return true;

		return base.CanConvertTo (context, destinationType);
	}

	public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
	{
		if (sourceType == typeof(string) )
			return true;

		return base.CanConvertFrom (context, sourceType);
	}


	public override object ConvertTo(ITypeDescriptorContext context, 
									System.Globalization.CultureInfo culture, 
									object value, 
									Type destinationType)
	{
		
		CustomCustomer Cust = (CustomCustomer) value;
		if ( destinationType == typeof(string) ) 
			return Cust.Name + "," + Cust.Company + "," + string.Format( culture.NumberFormat,"{0}",Cust.OrderTotal);

		return base.ConvertTo(context,culture,value,destinationType);
	} 

	public override object ConvertFrom(ITypeDescriptorContext context, 
			                            System.Globalization.CultureInfo culture, object value)
	{			
		if (! (value is string) )
			return base.ConvertFrom (context, culture, value );

		string Persisted = (string) value;

		CustomCustomer Cust = new CustomCustomer();

		if (Persisted != null || Persisted != "") 
		{
			string[] Fields = Persisted.Split(new char[1] {','});

			Cust.Company = Fields[1];
			Cust.Name = Fields[0];
			Cust.OrderTotal = (decimal)wwUtils.StringToTypedValue(Fields[2],Cust.OrderTotal.GetType());
		}

		return Cust;
	}
}

Note that the ConvertFrom() method has no error checking. You probably will need to check and make sure you get the right number of returned fields and double check type values etc.

For more detail on TypeConverters please visit:

http://west-wind.com/weblog/posts/980.aspx

IList Conversions

You can also serialize lists that implement IList. IList objects are iterated and embedded into the config file as ListItems1,ListItems2 where ListItems is the name of the List property. If the contained object in the List supports either ToString()/FromString() or a TypeConverter, then you can get the list serialized into a .config file. Again this works well only with single level objects - hierarchies are not supported by ToString()/FromString() and really not well workable with TypeConverters.

public class CustomConfigFileConfiguration : Westwind.Utilities.Configuration.AppConfiguration
{
        public string ApplicationName { get; set; }
        public List<string> ServerList { get; set;  }

        public CustomConfigFileConfiguration()
        {
            ApplicationName = "Configuration Tests";
            ServerList = new List<string>()
            {
                "DevServer",
                "Maximus",
                "Tempest"
            };
        }
}

This will serialize as:

<CustomConfigFileConfiguration>
   <add key="ApplicationName" value="Configuration Tests" />   
   <add key="ServerList1" value="DevServer" />
   <add key="ServerList2" value="Maximus" />
   <add key="ServerList3" value="Tempest" />
</CustomConfigFileConfiguration>

Notice the List type generates keys with the name of the list property followed by a one based index. Values are read and written to this file.

If the list represents objects the behavior of those child objects is determined by the complex type parsing of the parser, with each item rendering the string serialization that object provided.

Complex Types - Using Alternate Formats

As mentioned at the beginning all formats but standard .NET .config files support complex objects natively as they simply serialize. So if you use JSON or XML as your serialization format you can make your objects as complex as the serializers understand and automatically handle the nesting.

For desktop/console/service applications this is a good choice as the .config file is only loaded on startup once anyway. In those cases an Configuration.json or Configuration.xml is often a better choice.

In Web applications though, .config files are nice because they allwoi


© West Wind Technologies, 2019 • Updated: 12/19/15
Comment or report problem with topic