Introduction
You may not have written yet your own type converter. But any time you developed a web form or Windows form using the Visual Studio .NET designer or used the view state architecture of ASP.NET you have relied on the help of a type converter. As the name already states, type converters are used to convert from one type to another, for example an integer to a string. The .NET framework comes with a number of type converters which perform all that work for you for the most common .NET types. But when you create your own complex types then you need to create your own type converters. This article will explain how you can write your own type converter and then assign that to your own type.
Where are type converters used?
Type converters are mostly used to convert your type to a string or a string back to your type. Web controls and web forms have properties which you can view and edit through the property browser of the Visual Studio .NET designer. The property browser finds the type of the property and then the type converter associated with that type to convert the type to a string. Any changes the user makes to the property in the property browser is written back to the property, which again uses the type converter to convert from a string back to the type.
You can also design web forms through the Visual Studio .NET designer. This allows you to place new controls onto the web form and modify their properties. The designer creates the necessary form tags and properties. You can also switch to the source mode and then edit the tags itself. You can add new properties to a control or modify existing ones. This is called declarative persistence. The designer itself uses type converters to convert a property from your type to a string and again from a string back to your type.
The state view framework provided by web controls and web forms also relies on type converters. Type converters provide a better performance than reflection. So whenever possible avoid binary serialization which uses reflection. Provide your own type converter. You can not store complex types in state view unless it is marked serializable or has its own type converter associated.
How do type converters work?
Type converters allow you to convert from your type to another type and also form another type back to your type. All type converters are inherited from the TypeConverter
base class provided by .NET. You need to override four methods. The methods CanConvertTo
and ConvertTo
are used when converting from your type to another type, for example to a string
. The methods CanConvertFrom
and ConvertFrom
are used when converting from another type back to your type, for example from a string
.
CanConvertTo
- Passes along the required destination type, for example, type of string
. The method returns true
if it can convert to that type otherwise false
.
ConvertTo
- Gets passed along the type to convert, the destination type and the culture info to use during the type conversion. This method then returns the converted type.
CanConvertFrom
- Passes along the source type, for example, the type of string
. The method returns true
if it can convert from that type back to your type. Otherwise it returns false
.
ConvertFrom
- Gets passed along the type to convert from and the culture info to use during the type conversion. This method returns then the converted type.
Walk through a sample type converter
The attached sample application defines three complex types - GPSLocation
, Latitude
and Longitude
. As the names already indicate these types are used to describe a GPS position. Each type has its own type converter associated with. We will walk now through the LatitudeTypeConverter
.
The following code snippet shows the implementation of CanConvertFrom
. This type converter will be able to convert from a string
back to a Latitude
type. So if the source type is of the type string
then we return true
, otherwise we call the method of the base class - the TypeConverter
class.
public override bool CanConvertFrom(ITypeDescriptorContext context,
Type sourceType)
{
if (sourceType == typeof(string))
return true;
else
return base.CanConvertFrom(context, sourceType);
}
The next code snippet shows the implementation of the ConvertFrom
method. First we check if the value (type) passed along is null
. In that case we create a new instance of the Latitude
class and return it. This means no value is provided so we create an empty instance of our type. Next we check if the value passed along is of the type string
. In that case we will process the request otherwise we again call the method of the base class. So we perform a conversion from a string
but any other conversion we delegate to the base class.
If the value is a string
then we check the length. If the length is zero or less then we return again a new instance of the Latitude
class. This means that no value is provided so we create an empty instance of our type. Now we can start with the actual conversion. The Latitude
type consists of a degree, latitude direction (North or South) and the minutes and seconds, for example, 48N15b0b for Vienna (Austria). First we search for the North direction and if not found then we search for the South direction. Finally we search for the minute character and second character. Next we check whether we have found a latitude direction, minute character and second character. We also check that we have a degree value by making sure that the latitude direction starts at a position greater then zero. If any of them is missing then we throw an appropriate exception informing the caller that the format is not correct. Next we get an instance of the integer type converter by calling TypeDescriptor.GetConverter(typeof(int))
. This returns the type converter which can be used to convert to or from an integer. Finally we cut the value string apart to obtain the degrees, minutes and seconds and use the integer converter to convert each from a string
to an integer. Last thing we again create a new instance of the Latitude
class and pass along the four values - latitude direction, degrees, minutes and seconds. This is the converted type we return.
public override object ConvertFrom(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value)
{
if (value == null)
return new Latitude();
if (value is string)
{
string StringValue = value as string;
if (StringValue.Length <= 0)
return new Latitude();
int DirPos =
StringValue.IndexOf(LatitudeDiretcion.North.ToString().Substring(0,1));
LatitudeDiretcion Direction = LatitudeDiretcion.North;
if (DirPos == -1)
{
DirPos =
StringValue.IndexOf(LatitudeDiretcion.South.ToString().Substring(0, 1));
Direction = LatitudeDiretcion.South;
}
int MinutesPos = StringValue.IndexOf(MinutesUnit);
int SecondsPos = StringValue.IndexOf(SecondsUnit);
if (MinutesPos == -1)
throw new Exception(NoMinutes);
if (SecondsPos == -1)
throw new Exception(NoSeconds);
if (DirPos == -1)
throw new Exception(NoDirection);
if (DirPos == 0)
throw new Exception(NoDegrees);
TypeConverter IntConverter =
TypeDescriptor.GetConverter(typeof(int));
int Degrees = (int)IntConverter.ConvertFromString(context, culture,
StringValue.Substring(0, DirPos));
int Minutes = (int)IntConverter.ConvertFromString(context, culture,
StringValue.Substring(DirPos + 1, MinutesPos - DirPos - 1));
int Seconds = (int)IntConverter.ConvertFromString(context, culture,
StringValue.Substring(MinutesPos + 1, SecondsPos - MinutesPos - 1));
return new Latitude(Degrees, Minutes, Seconds, Direction);
}
else
return base.ConvertFrom(context, culture, value);
}
This is all we need to convert from a string
back to our type. The next code snippet shows the implementation of CanConvertTo
. This type converter will be able to convert to a string
and an instance descriptor. The instance descriptor is needed when the framework needs to create a new instance of your type. It tells the framework the constructor to use, the argument types and the actual arguments to pass along. You only need to implement the instance descriptor when the framework needs to be able to create a new instance of your type. If for example your control exposes a read-only property of your type, then there is never a need for the framework to create an instance of your type. Therefore no instance descriptor is needed. Quite naturally if your control exposes complex types then this is the recommendation. Make the property read only and in your property implementation make sure there is always an instance present. For all other requested destination types we call again the method of the base class.
public override bool CanConvertTo(ITypeDescriptorContext context,
Type destinationType)
{
if ((destinationType == typeof(string)) |
(destinationType == typeof(InstanceDescriptor)))
return true;
else
return base.CanConvertTo(context, destinationType);
}
The final code snippet shows the implementation of the ConvertTo
method. First we check if the source value passed along is of the type Latitude
or null
, otherwise we throw an exception telling the caller that an unsupported type has been passed along. Next we check if the destination type is string
, which we process. If the source value is null
then we return an empty string. Next we get the type converters for integer and enumerators by calling again TypeDescriptor.GetConverter
and passing along the type we want to have a type converter for. We then use the type converters to convert the degrees, latitude direction, minutes and seconds to strings. For the latitude direction we just use the first character - N for North and S for South. We also append the minutes and seconds character. The end result is a string like 48N15b0b for Vienna (Austria). This string is then returned.
If the destination type is an instance descriptor then we perform the following processing. If the value is null
we return null
, which means no instance descriptor is returned. First we declare a MemberInfo
variable which we use to store a reference to the constructor for the Latitude
type and an array of objects which we use to create the array of arguments to pass along to the constructor. We then call GetConstructor()
on the type of Latitude
, which returns a reference to the constructor. A type can have multiple constructors, so we pass along an array of types, which tells GetConstructor
to look for the constructor with this signature. In our case we look for the constructor which takes three integers followed with the LatitudeDirection
enumerator. Then we create an array of object
s, which will be the arguments passed along when the constructor gets called. The array mirrors the constructor signature. So it contains the degrees, minutes and seconds of the latitude value we process as well as the latitude direction. If we were able to get a reference to the constructor then we create an InstanceDescriptor
type, pass along the constructor and arguments reference and return the instance of the InstanceDescriptor
. This instance descriptor is then used by the framework if it needs to create a new instance of our type. It tells the framework the constructor to call and the arguments to pass along. All this relies on the .NET reflection API. If we were unable to obtain a reference to the constructor then we return null
, which means no instance descriptor returned.
public override object ConvertTo(ITypeDescriptorContext context,
CultureInfo culture, object value, Type destinationType)
{
if (value != null)
if (!(value is Latitude))
throw new Exception(WrongType);
if (destinationType == typeof(string))
{
if (value == null)
return String.Empty;
Latitude LValue = value as Latitude;
TypeConverter IntConverter =
TypeDescriptor.GetConverter(typeof(Int32));
TypeConverter EnumConverter =
TypeDescriptor.GetConverter(typeof(LatitudeDiretcion));
return IntConverter.ConvertToString(context, culture, LValue.Degrees) +
EnumConverter.ConvertToString(context,culture,
LValue.Direction).Substring(0,1) +
IntConverter.ConvertToString(context, culture,
LValue.Minutes) + MinutesUnit +
IntConverter.ConvertToString(context, culture,
LValue.Seconds) + SecondsUnit;
}
if (destinationType == typeof(InstanceDescriptor))
{
if (value == null)
return null;
Latitude LatitudeValue = value as Latitude;
MemberInfo Member = null;
object[] Arguments = null;
Member =
typeof(Latitude).GetConstructor(new Type[] { typeof(int),
typeof(int), typeof(int), typeof(LatitudeDiretcion) });
Arguments = new object[] {
LatitudeValue.Degrees, LatitudeValue.Minutes,
LatitudeValue.Seconds, LatitudeValue.Direction };
if (Member != null)
return new InstanceDescriptor(Member, Arguments);
else
return null;
}
return base.ConvertTo(context, culture, value, destinationType);
}
That is all you need to do for your custom type converter. You can assign your type converter to your type by applying the TypeConverter
attribute to the custom type. You pass along the type of the type converter to assign. Here is the code snippet for our Latitude
type:
[TypeConverter(typeof(LatitudeTypeConverter))]
public class Latitude
{
public override string ToString()
{
return ToString(CultureInfo.InvariantCulture);
}
public string ToString(CultureInfo Culture)
{
return TypeDescriptor.GetConverter(GetType()).ConvertToString(null,
Culture, this);
}
}
We also implement the ToString()
method on our custom type so it becomes easy for callers to convert our type to a string
. We expose one implementation with no arguments, which just calls ToString()
with the invariant culture, meaning it performs a string conversion which is culture independent. The second ToString()
implementation allows to pass along the culture info to use during the string conversion. This implementation calls again GetConverter()
on our custom type, which returns the type converter associated with that type. It then calls ConvertToString()
to perform the string conversion. Whenever you want to convert the custom type to a string
it is a simple call of the ToString()
method provided on the custom type.
Please see the attached sample application which shows the implementation of the Latitude
, Longitude
and GPSLocation
type converter. The example shows how you then use the property browser to set these values and how you display it on the web form itself. Notice that in the Render()
method of the SimpleLocationControl
control we simply call ToString()
on our custom type and output that.
The standard .NET type converters
As mentioned the .NET framework comes with a number of standard type converters you can leverage (MSDN help topic ). You for example find a BooleanConverter
, EnumConverter
, FontConverter
, ColorConverter
, DateTimeConverter
and many more. The BaseNumberConverter
has a number of derived classes which provide all the number converters, for example the Int32Converter
, ByteConverter
, etc.. All these types have the TypeConverter
attribute applied which tells the framework which type converter to use for these types. This is the reason why you are able to use these types in the Visual Studio .NET designer and property browser without any additional coding effort.
You can also use classes with properties in the designer and property browser by simply applying the ExpandableObjectConverter
type converter. This type converter is able to read all the properties of a class and display each separately in the property browser as well as persist each property separately to the web form in the designer. This of course requires that the types used by the properties have again available type converters. The GPSLocation
is a good example. It has two properties, the GPSLatitude
which is of the type Latitude
and the GPSLongitude
which is of the type Longitude
. The Latitude
and Longitude
types have their own type converters. The GPSLocation
type has the GPSLocationTypeConverter
which we implemented. That type converter is inherited from the ExpandableObjectConverter
and by default inherits all its logic and therefore allows to expand that type in the property browser and view and edit each property separately. For this to work properly, you need to apply the NotifyParent
attribute to each property in the class. This attribute notifies the parent when the property changes. This way when you edit a property it calls the type converter of that property as well of the parent class. Here is a code snippet for this:
[TypeConverter(typeof(GPSLocationTypeConverter))]
public class GPSLocation
{
true)>
[NotifyParentProperty(true)]
public Longitude GPSLongitude
{
get
{
return _Longitude;
}
set
{
_Longitude = value;
}
}
}
The GPSLocationTypeConverter
converts the type to and from a string
. It itself uses the longitude type converter and latitude type converter. When the property is collapsed in the property browser the ExpandableObjectConverter
normally just shows the type name. By implementing our own to string type conversion we are able to show the GPS location value which is nothing other than a combination of the latitude and longitude value. When you create a control using complex types you can also apply attributes to tell the designer how to persist it. The two attributes to use are DesignerSerialization
and PersistenceMode
. The DesignerSerialization
should be set to Content
which tells the designer to also persist each property of the type. The PersistenceMode
attribute tells how to persist it. The default value is Attribute
which means it will add all the properties as attributes using the property name hyphen sub-property name syntax. Here is an example:
<ui:LocationControl ID="LocationControl1" runat="server"
Location-GPSLatitude="12N1'2"" Location-GPSLongitude="24W3'4"">
</ui:LocationControl>
By using the InnerProperty
value you tell it to make it a sub-tag when persisting it. Here is an example of that syntax:
<ui:LocationControl ID="LocationControl1" runat="server">
<Location GPSLatitude="12N1'3"" GPSLongitude="24W3'4"" />
</ui:LocationControl>
There are many more control attributes you can apply. But that goes beyond this article. Here is the code snippet for the DesignerSerialization
and PersistenceMode
attributes:
[DefaultProperty("GPSLocation")]
[ParseChildren(true)]
[ToolboxData("<{0}:LocationControl runat="server">")]
public class LocationControl : WebControl
{
true)>
[Category("Appearance")]
[DefaultValue(typeof(GPSLocation), "")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[PersistenceMode(PersistenceMode.InnerProperty)]
public GPSLocation Location
{
get
{
if (_Location == null)
_Location = new GPSLocation();
return _Location;
}
}
}
Please see the attached sample for a complete working control. It allows you to set the value of the GPS location property in the property browser and then outputs it when rendering the control.
Summary
Type converters are an important part of the Visual Studio .NET designer and property browser as well as the view state management. As long as you use standard types there is no need to write your own type converter. But when you create your own complex types you need to implement your own type converters. This article explains how to implement your own type converters and how to associate those type converters with your complex types. The .NET framework makes it very easy to implement your own type converters by inheriting from the TypeConverter
class and implementing the CanConvertTo
, ConvertTo
, CanConvertFrom
and ConvertFrom
methods. If you have comments on this article or this topic, please contact me @ klaus_salchner@hotmail.com. I want to hear if you have learned something new. Contact me if you have questions about this topic or article.