In order for Sitefinity to be able to save the property automatically the value of your property needs to be in the string format. Sitefinity will automatically convert
primitive types for you, such as integer or boolean, but what if you need to persist property that is an array or a list. Our sample Links List control needs to persist the list
of links (link titles and link urls), so we will need to find a way to make Sitefinity persist a property of type Dictionary<string, string>.
We will start by declaring the public ListLinks property of type Dictionary<string, string> where the key of the dictionary is a link title,
while the value is link url. We will categorize it under "Links settings" category, and finally we will add one more attribute: TypeConverter. TypeConverter
editor will define the class that will provide logic for converting the Dictionary<string,string> type to string, and vice versa. Basically, the idea is to serialize the
object.
Our property declaration will look like this:
| C# |
Copy Code |
|
/// <summary>
/// Gets or sets the list of items to be displayed by the bulleted list control
/// </summary> [Category("List settings")]
[TypeConverter("Telerik.Samples.DictionaryConverter, App_Code")] public Dictionary<string, string> ListLinks
{
get
{
// if listLinks dictionary is null, we'll create an empty dictionary
// to avoid dealing with null reference in code
if(listLinks == null)
listLinks = new Dictionary<string, string>();
return listLinks;
}
set
{
listLinks = value;
}
}
|
The next step we have to do is to implement the DictionaryConverter class that we have declared as the type of TypeConverter. To do so we will implement a new
class which will inherit from .NET framework’s TypeConverter and place that class into the App_Code folder. The code for that class looks like this:
| DictionaryConverter.cs |
Copy Code |
|
using System; using System.Globalization; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.Design.Serialization; using System.Text; namespace Telerik.Samples
{
/// <summary>
/// This class implements methods that convert string representation of Dictionary
/// to Dictionary object and vice versa - converter will convert only dictionaries
/// where both key and value are of type string
/// </summary>
public class DictionaryConverter : TypeConverter
{
/// <summary>
/// Returns whether this converter can convert an object of the given type to
/// the type of this converter, using the specified context.
/// </summary>
/// <param name="context">A System.ComponentModel.ITypeDescriptorContext that
provides a format context.</param>
/// <param name="sourceType">A System.Type that represents the type you want to
convert from.</param>
/// <returns>true if this converter can perform the conversion; otherwise,
false.</returns>
public override bool
CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return ((sourceType ==
typeof(string)) || base.CanConvertFrom(context, sourceType));
}
/// <summary>
/// Returns whether this converter can convert the object to the specified type,
/// using the specified context.
/// </summary>
/// <param name="context">A System.ComponentModel.ITypeDescriptorContext that
provides a format context.</param>
/// <param name="destinationType">A System.Type that represents the type you
want to convert to.</param>
/// <returns>true if this converter can perform the conversion; otherwise,
false.</returns>
public override bool
CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return ((destinationType ==
typeof(InstanceDescriptor)) || base.CanConvertTo(context, destinationType));
}
/// <summary>
/// Converts the given object to the type of this converter, using the specified
/// context and culture information.
/// </summary>
/// <param name="context">A System.ComponentModel.ITypeDescriptorContext that
provides a format context.</param>
/// <param name="culture">The System.Globalization.CultureInfo to use as the
current culture.</param>
/// <param name="value">The System.Object to convert.</param>
/// <returns>An System.Object that represents the converted
value.</returns>
/// <exception cref="System.NotSupportedException">The conversion cannot be
performed.</exception>
public override object
ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object
value)
{
// the logic in this method will convert string into a
Dictionary. The string has following
// format:
// key1=value1;key2=value2
// where semi-color (;) separates dictionary items, while
equal sign (=) separates
// item key from item value
if (value is string)
{
Dictionary<string, string> dictionary = new Dictionary<string, string>();
// split string by semi-colon (;) to
get array of dictionary items
string[]
dictionaryItems = ((string) value).Split(';');
foreach(string dictionaryItem in dictionaryItems)
{
// split item
by equal sign (=) to get array of key and value
string[] keyValuePair = dictionaryItem.Split('=');
// add new
item to the dictionary
if(keyValuePair.Length == 2)
dictionary.Add(keyValuePair[0].Trim(),
keyValuePair[1].Trim());
}
return dictionary;
}
return base.ConvertFrom(context, culture, value);
}
/// <summary>
/// Converts the given value object to the specified type, using the specified
/// context and culture information.
/// </summary>
/// <param name="context">A System.ComponentModel.ITypeDescriptorContext that
provides a format context.</param>
/// <param name="culture">A System.Globalization.CultureInfo. If null is passed,
the current culture is assumed.</param>
/// <param name="value">The System.Object to convert.</param>
/// <param name="destinationType">The System.Type to convert the value parameter
to.</param>
/// <returns>An System.Object that represents the converted
value.</returns>
/// <exception cref="System.NotSupportedException">The conversion cannot be
performed.</exception>
/// <exception cref="System.ArgumentNullException">The destinationType parameter
is null.</exception>
public override object
ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type
destinationType)
{
// the logic in this method will convert Dictionary into a
string (serialize the dictionary).
//The string has following format:
// key1=value1;key2=value2
// where semi-color (;) separates dictionary items, while
equal sign (=) separates
// item key from item value
if (destinationType ==
null)
throw new ArgumentNullException("destinationType");
if ((destinationType ==
typeof(string)) && (value is Dictionary<string, string>))
{
StringBuilder sb = new
StringBuilder();
foreach(KeyValuePair<string, string>
dictionaryItem in (Dictionary<string,string>)value)
{
sb.AppendFormat("{0}={1};", dictionaryItem.Key, dictionaryItem.Value);
}
return sb.ToString();
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
|
If you look at the code you will notice that we need to implement four methods. First, we need to implement methods which will determine can this converter convert
Dictionary<string, string> to string, and second one - determining can the converter convert string to Dictionary<string, string>. Once we have implemented those two
methods we are left with implementing methods which actually convert Dictionary<string,string> to a string and one that converts string to a Dictionary<string, string>
object.
The way of implementing the conversion logic is completely left to the developer. In this sample you will notice that string representation of the
Dictionary<string, string> object follows this logic:
Key1=Value1;Key2=Value2…
where the equal sign ("=") separates key from value, and the semi-colon sign (";") separates dictionary entries.
There is nothing stopping you from changing this logic.
After we have implemented this new property, let’s take a look at how our property grid looks like now:

Figure 1: using TypeConverter to expose complex non-string properties as a property of Links List control
Finally, let us examine the code that will populate our BulletedList with the items declared in the ListLinks property. Once again, we will be placing the code in the Page_Load
event handler:
| C# |
Copy Code |
|
protected void Page_Load(object sender, EventArgs e)
{
// set the text of title label to the value of ControlTitle property
controlTitleLabel.Text = ControlTitle;
// clear the bulleted list of any items and populate it
// with the values of ListLinks dictionary
linksBulletedList.Items.Clear();
foreach(KeyValuePair<string, string> listLink in ListLinks)
{
ListItem item = new ListItem(listLink.Key,
listLink.Value);
linksBulletedList.Items.Add(item);
}
}
|
The logic is pretty straightforward. We first clear the BulletedList items, so that the hard-coded items don’t appear. Then we loop through all KeyValuePair objects of our
ListLinks dictionary and for each pair we add a new ListItem. For the text property of ListItem we use the key of our KeyValuePair, while for the url we use value of the
KeyValuePair.
You can download the control from here: LinksList5.
While we have achieved our goal of exposing a complex, non-string property through our control, this is not a very user friendly way of exposing such properties. First of all, our
users would have to know what kind of logic we are applying to represent a dictionary as a string and second, even if they knew the logic, TextBox is not the best GUI element to
represent the value of this property. Therefore, in our next sample in topic Implementing
TypeEditors for Complex Properties, we are going to use TypeEditor class. TypeEditor class will allow us to provide our users with a user friendly way of
editing complex non-string properties.