Type Converters, Your Friendly Helpers!

Introduction

You may not have written your own type converter yet. But, any time you develop a Web form or Windows form using the Visual Studio .NET designer or use the view state architecture of ASP.NET, you rely 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, from an integer to a string. The .NET framework comes with a number of type converters that perform all that work for you for with the most common .NET types. But, when you create your own complex types, 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 that 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 also can 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 also can 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 again 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 that uses reflection. Provide your own type converter. You cannot store complex types in state view unless they are marked serializable or have their own type converter associated.

How Do Type Converters Work?

Type converters allow you to convert from your type to another type and also from another type back to your type. All type converters are inherited from the TypeConverter base class provided by .NET. You need to overwrite 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, the type of the 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 the 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 then method returns 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 it. Now, you will walk 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 string type, you return true; otherwise, you 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, check whether the value (type) passed along is null. In that case, create a new instance of the Latitude class and return it. This means no value was provided, so you create an empty instance of your type. Next, check whether the value passed along is of the string type. In that case, you will process the request; otherwise, again call the method of the base class. So, you perform a conversion from a string but any other conversion is delegated to the base class.

If the value is a string, you check the length. If the length is zero or less, you again return a new instance of the Latitude class. This again means that no value provided, so you create an empty instance of your type. Now, you 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, 48N15'0" for Vienna (Austria). First, you search for the North direction and, if not found, you search for the South direction. Finally, you search for the minute character and second character. Next, you check that you have found a latitude direction, minute character, and second character. You also check that you have a degree value by making sure that the latitude direction starts at a position greater then zero. If any of them is missing, you throw an appropriate exception informing the caller that the format is not correct. Next, you get an instance of the integer type converter by calling TypeDescriptor.GetConverter(typeof(int)). This returns the type converter that can be used to convert to or from an integer. Finally, you 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, you 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 you return.

public override object ConvertFrom(ITypeDescriptorContext context,
                                   System.Globalization.CultureInfo
                                   culture, object value)
{
   // if no value passed along, return a new Latitute instance
   if (value == null)
      return new Latitude();

   // if the source is a string, convert to your type
   if (value is string)
   {
      // get strongly typed value
      string StringValue = value as string;

      // if empty string, again return a new instance of Latitude
      if (StringValue.Length <= 0)
         return new Latitude();

      // search of the North latitude key
      int DirPos =
         StringValue.IndexOf(LatitudeDiretcion.North.ToString().
         Substring(0,1));
      LatitudeDiretcion Direction = LatitudeDiretcion.North;

      // if not found, search for the South latitude key
      if (DirPos == -1)
      {
         DirPos = StringValue.IndexOf(LatitudeDiretcion.South.
                  ToString().Substring(0, 1));
         Direction = LatitudeDiretcion.South;
      }

      // get the position of the seconds and minutes unit character
      int MinutesPos = StringValue.IndexOf(MinutesUnit);
      int SecondsPos = StringValue.IndexOf(SecondsUnit);

      // the minutes are missing
      if (MinutesPos == -1)
         throw new Exception(NoMinutes);

      // the seconds are missing
      if (SecondsPos == -1)
         throw new Exception(NoSeconds);

      // the Latitude direction is missing
      if (DirPos == -1)
         throw new Exception(NoDirection);

      // the degrees are missing
      if (DirPos == 0)
         throw new Exception(NoDegrees);

      // get the type converters you need
      TypeConverter IntConverter =
         TypeDescriptor.GetConverter(typeof(int));

      // get the degrees, minutes, and seconds values
      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));

      // create a new Latitude instance with these values and return it
      return new Latitude(Degrees, Minutes, Seconds, Direction);
   }

   // otherwise, call the base converter
   else
      return base.ConvertFrom(context, culture, value);
}

Type Converters, Your Friendly Helpers!

This is all you need to convert from a string back to your type. The next code snippet shows the implementation of CanConvertTo. This type converter can 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 create a new instance of your type. If, for example, your control exposes a read-only property of your type, there is never a need for the framework to create an instance of your type. Therefore, no instance descriptor is needed. Quite actually, if your control exposes complex types, 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, you again call 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, you check whether the source value passed along is of the Latitude type or null; otherwise, you throw an exception telling the caller that an unsupported type has been passed along. Next, you check whether the destination type is string, which you process. If the source value is null, you return an empty string. Next, you get the type converters for integer and enumerators by calling TypeDescriptor.GetConverter again and passing along the type for which you want to have a type converter. You then use the type converters to convert the degrees, latitude direction, minutes, and seconds to strings. For the latitude direction, you just use the first character—N for North and S for South. You also append the minutes and seconds character. The end result is a string like 48N15b'0" for Vienna (Austria). This string is then returned.

If the destination type is an instance descriptor, you perform the following processing. If the value is null, you return null; this means no instance descriptor is returned. First, you declare a MemberInfo variable that you use to store a reference to the constructor for the Latitude type and an array of objects that you use to create the array of arguments to pass along to the constructor. You then call GetConstructor() on the type of Latitude, which returns a reference to the constructor. A type can have multiple constructors, so you pass along an array of types, which tells GetConstructor to look for the constructor with this signature. In your case, you look for the constructor that takes three integers followed with the LatitudeDirection enumerator. Then, you create an array of objects, 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 you were able to get a reference to the constructor, you create an InstanceDescriptor type, pass along the constructor and arguments reference, and return the instance of the InstanceDescriptor. This instance descriptor then is used by the framework if it needs to create a new instance of your type. It tells the framework the constructor to call and the arguments to pass along. All this relies on the .NET reflection API. If you were unable to obtain a reference to the constructor, you return null, which means no instance descriptor returned.

public override object ConvertTo(ITypeDescriptorContext context,
                                 CultureInfo culture, object value,
                                 Type destinationType)
{
   // check that the value passed along is of your type
   if (value != null)
      if (!(value is Latitude))
         throw new Exception(WrongType);

   // convert to a string
   if (destinationType == typeof(string))
   {
      // no Latitude instance so you return an empty string
      if (value == null)
         return String.Empty;

      // get strongly type value
      Latitude LValue = value as Latitude;

      // get the type converters you need
      TypeConverter IntConverter =
         TypeDescriptor.GetConverter(typeof(Int32));
      TypeConverter EnumConverter =
         TypeDescriptor.GetConverter(typeof(LatitudeDiretcion));

      // return string representation of Latitude
      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;
   }

   // convert to an instance descriptor
   if (destinationType == typeof(InstanceDescriptor))
   {
      // no Latitude instance
      if (value == null)
         return null;

      // get strongly type value
      Latitude LatitudeValue = value as Latitude;

      // used to describe the constructor
      MemberInfo Member  = null;
      object[] Arguments = null;

      // get the constructor of your Latitude type
      Member = typeof(Latitude).GetConstructor(new Type[]
               { typeof(int), typeof(int), typeof(int),
                 typeof(LatitudeDiretcion) });

      // the arguments to pass along to the Latitude constructor
      Arguments = new object[] { LatitudeValue.Degrees,
                                 LatitudeValue.Minutes,
                                 LatitudeValue.Seconds,
                                 LatitudeValue.Direction };

      // return instance descriptor or null if you could not find
      // a constructor
      if (Member != null)
         return new InstanceDescriptor(Member, Arguments);
      else
         return null;
   }

   // call base converter to convert
   return base.ConvertTo(context, culture, value, destinationType);
}

Type Converters, Your Friendly Helpers!

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 your 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);
   }
}

You also implement the ToString() method on your custom type so it becomes easy for callers to convert your type to a string. You expose one implementation with no arguments, which just calls ToString() with the invariant culture, meaning it performs a string conversion that is culture independent. The second ToString() implementation allows you to pass along the culture info to use during the string conversion. This implementation again calls GetConverter() on your 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 attached sample application, which shows the implementation of the Latitude, Longitude, and GPSLocation type converter. The example shows how you 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, you simply call ToString() on your custom type and output that.

The Standard .NET Type Converters

As mentioned earlier, the .NET framework comes with a number of standard type converters you can leverage (MSDN help topic). You, for example, can find a BooleanConverter, EnumConverter, FontConverter, ColorConverter, DateTimeConverter, and many more. The BaseNumberConverter has a number of derived classes that provides all the number converters; for example, the Int32Converter, ByteConverter, and so forth. All these types have the TypeConverter attribute applied; this 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 simply by 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 again have type converters available. The GPSLocation is a good example. It has two properties: the GPSLatitude, which is of the Latitude type, and the GPSLongitude, which is of the Longitude type. The Latitude and Longitude types have their own type converters. The GPSLocation type has the GPSLocationTypeConverter that you implemented. That type converter inherits from the ExpandableObjectConverter and by default inherits all its logic and therefore allows you 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
{
   [Browsable(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 your own to string type conversion, you are able to show the GPS location value, which is nothing more then a combination of the latitude and longitude values. When you create a control using complex types, you also can 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 also to persist each property of the type. The PersistenceMode attribute tells how to persist it. The default value is Attribute; this 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, this goes beyond this article. Here is the code snippet for the DesignerSerialization and PersistenceMode attributes:

[DefaultProperty("GPSLocation")]
[ParseChildren(true)]
[ToolboxData("<{0}:LocationControl runat=server>
              </{0}:LocationControl>")]
public class LocationControl : WebControl
{
   [Bindable(true)]
   [Category("Appearance")]
   [DefaultValue(typeof(GPSLocation), "")]
   [DesignerSerializationVisibility
      (DesignerSerializationVisibility.Content)]
   [PersistenceMode(PersistenceMode.InnerProperty)]
   public GPSLocation Location
   {
      get
      {
         // if no instance created yet, do so
         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 learned something new. Contact me if you have questions about this topic or article.

About the Author

Klaus Salchner has worked for 14 years in the industry, nine years in Europe and another five years in North America. As a Senior Enterprise Architect with solid experience in enterprise software development, Klaus spends considerable time on performance, scalability, availability, maintainability, globalization/localization, and security. The projects he has been involved in are used by more than a million users in 50 countries on three continents.

Klaus calls Vancouver, British Columbia his home at the moment. His next big goal is running the New York marathon in 2005. Klaus is interested in guest speaking opportunities or as an author for .NET magazines or Web sites. He can be contacted at klaus_salchner@hotmail.com or http://www.enterprise-minds.com.

Enterprise application architecture and design consulting services are available. If you want to hear more about it, contact me! Involve me in your projects and I will make a difference for you. Contact me if you have an idea for an article or research project. Also contact me if you want to co-author an article or join future research projects!



Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Wednesday, September 24, 2014 8:00 AM - 9:00 AM PDT According to a recent Forrester Research report, many companies are choosing low-code platforms over traditional programming platforms, due to the speed with which low-code apps can be assembled and tested. With customer-facing applications on the rise, traditional programming platforms simply can't keep up with the "short schedules and rapid change cycles" required to develop these applications. Check out this upcoming webinar and join Clay Richardson from …

  • Live Event Date: September 16, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you starting an on-premise-to-cloud data migration project? Have you thought about how much space you might need for your online platform or how to handle data that might be related to users who no longer exist? If these questions or any other concerns have been plaguing you about your migration project, check out this eSeminar. Join our speakers Betsy Bilhorn, VP, Product Management at Scribe, Mike Virnig, PowerSucess Manager and Michele …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds