Home > English > Using ICustomTypeProvider in Silverlight 5 with XML

Using ICustomTypeProvider in Silverlight 5 with XML

Back in June I did a post on ICustomTypeProvider with JSON and ASP.NET MVC 3. Today, I want to present to you the ability to use XML as your source data for the ICustomTypeProvider and being able to bind to the data. If you download the code from my previous post and start looking around, you will see a class with the following structure:

    public class JsonHelper<T> where T : ICustomTypeProvider
    {
        private readonly IEnumerable<string> _keys = Enumerable.Empty<string>();
        private readonly Dictionary<string, Func<object, object>> _converters = 
            new Dictionary<string, Func<object, object>>();

        public JsonHelper(IDictionary<string, JsonValue> template)
        {
            CustomTypeHelper<T>.ClearCustomProperties();

            _keys = (from k in template.Keys select k).ToArray();

            foreach (var key in template.Keys)
            {
                int integerTest;
                double doubleTest;
                DateTime datetimeTest;
                var value = template[key].ToString();
                if (DateTime.TryParse(value.Replace(@"\", "").Replace("\"", ""), out datetimeTest))
                {
                    CustomTypeHelper<T>.AddProperty(key, typeof(DateTime));
                    _converters.Add(key, obj => DateTime.Parse(obj.ToString().Replace(@"\", "").Replace("\"", "")));
                }
                else if (int.TryParse(value, out integerTest))
                {
                    CustomTypeHelper<T>.AddProperty(key, typeof(int));
                    _converters.Add(key, obj => int.Parse(obj.ToString()));
                }
                else if (double.TryParse(value, out doubleTest))
                {
                    CustomTypeHelper<T>.AddProperty(key, typeof(double));
                    _converters.Add(key, obj => double.Parse(obj.ToString()));
                }
                else
                {
                    CustomTypeHelper<T>.AddProperty(key, typeof(string));
                    _converters.Add(key, obj =>
                    {
                        // strip quotes
                        var str = obj.ToString().Substring(1);
                        return str.Substring(0, str.Length - 1);
                    });
                }
            }
        }

        public void MapJsonObject(Action<string, object> setValue, JsonValue item)
        {
            foreach (var key in _keys)
            {
                setValue(key, _converters[key](item[key]));
            }
        }
    };

Again, thanks goes out to all the other posts that I read and used in my solution.

This class is actually does all the magic for allowing us to map our JSON data to an object that implements ICustomTypeProvider. It gets a lot of help from a CustomTypeHelper class that you can look at in the source code from the previous post. But what is nice about this class is that we have a simple implementation that allows us to map our JSON object to a typed CustomType object. As you can see, we are basically trying to parse the data into the underlying data types we care about.

It happens that I had a requirement to be able to take XML in as an input and create a CustomType that I could bind to a DataGrid in XAML. Well this turned out to be extremely easy. Here is the new class that provides the same functionality as the JsonHelper class called, XamlHelper:

    public class XmlHelper<T> where T : ICustomTypeProvider
    {
        private readonly IEnumerable<string> _keys = Enumerable.Empty<string>();
        private readonly Dictionary<string, Func<object, object>> _converters =
            new Dictionary<string, Func<object, object>>();


        public XmlHelper(XElement input)
        {
            CustomTypeHelper<T>.ClearCustomProperties();

            var template = new Dictionary<string, object>();

            foreach (var xe in input.Descendants())
            {
                template.Add(xe.Name.LocalName, xe.Value);
            }
            _keys = (from k in template.Keys select k).ToArray();

            foreach (var key in template.Keys)
            {
                int integerTest;
                double doubleTest;
                DateTime datetimeTest;
                var value = template[key].ToString();
                if (DateTime.TryParse(value, out datetimeTest))
                {
                    CustomTypeHelper<T>.AddProperty(key, typeof(DateTime));
                    _converters.Add(key, obj => DateTime.Parse(obj.ToString()));
                }
                else if (int.TryParse(value, out integerTest))
                {
                    CustomTypeHelper<T>.AddProperty(key, typeof(int));
                    _converters.Add(key, obj => int.Parse(obj.ToString()));
                }
                else if (double.TryParse(value, out doubleTest))
                {
                    CustomTypeHelper<T>.AddProperty(key, typeof(double));
                    _converters.Add(key, obj => double.Parse(obj.ToString()));
                }
                else
                {
                    CustomTypeHelper<T>.AddProperty(key, typeof(string));
                    _converters.Add(key, obj => obj.ToString());
                }
            }
        }

        public void MapXmlObject(Action<string, object> setValue, XElement item)
        {
            foreach (var key in _keys)
            {
                setValue(key, _converters[key](item.Element(key).Value));
            }
        }
    };

Here is a snippet of code that uses this new helper:

StringReader sr = new StringReader(item);
XDocument xDoc = XDocument.Load(sr);
if (xDoc.Element("data").Descendants("Table").Count() > 0)
{
    // Pull the first record as a template.
    XElement template = xDoc.Element("data").Descendants("Table").ToList()[0];
    // Use the template to get type information.
    var xmlHelper = new XmlHelper<CustomType>(template);
    // Iterate over the results and add to the underlying collection.
    foreach (var xe in xDoc.Element("data").Descendants("Table").ToList())
    {
        var customType = new CustomType();
        xmlHelper.MapXmlObject(customType.SetPropertyValue, xe);
        ResultsPaneItems.Add(customType);
    }
}

In my XAML, I have a DataGrid that has its ItemsSource property bound to the ResultsPaneItems property on my ViewModel.

From the server, I am using a ADO.NET DataSet and DataAdapter to write the DataSet out as XML. Then on the client side, I just receive the XML and use the ICustomTypeProvider implementation to allow me to bind to this shape. The cool thing about this is that the shape of my query can be anything as long as it is tabular. I would have to do some extra work if I want to support hierarchical data.

With the helper classes in place, you can provide mappers for just about any shape you need and still have the ability to bind to it in Silverlight. Pretty cool, right?

If you want to use this class, download the source code from my previous post on this subject and just copy and paste this class.

Hope this helps…

Advertisements
  1. No comments yet.
  1. September 17, 2011 at 8:56 am

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: