ApplicationConfiguration supports complex subtypes in your configruation object in two ways:

  • Via Serialization in non-.Config file formats
  • Via TypeConverters in .Config files

If you've designed your configuration type to be serializable (marking the class with the [Serializable()] attribute) using external non-config xml files, text or database out will automatically persist any subtypes. Since XML Serialization is used for any storage mechanism other than the .NET config files persistance into these types will be automatic.

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 { /// <summary> /// Allow only string conversions /// </summary> /// <param name="context"></param> /// <param name="destinationType"></param> /// <returns></returns> public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { if (destinationType == typeof(string) ) return true; return base.CanConvertTo (context, destinationType); } /// <summary> /// Allow only string conversions /// </summary> /// <param name="context"></param> /// <param name="sourceType"></param> /// <returns></returns> public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(string) ) return true; return base.CanConvertFrom (context, sourceType); } /// <summary> /// Convert into a string resource with a comma delimited list /// </summary> /// <param name="context"></param> /// <param name="culture"></param> /// <param name="value"></param> /// <param name="destinationType"></param> /// <returns></returns> 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); } /// <summary> /// Convert back into properties from a comma delimited list. Note the list is position specific. /// This code doesn't demonstrate proper error handling for manually invalidated values. /// </summary> /// <param name="context"></param> /// <param name="culture"></param> /// <param name="value"></param> /// <returns></returns> 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