Archive

Posts Tagged ‘Xml’

Using ICustomTypeProvider in Silverlight 5 with XML

September 16, 2011 1 comment

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…

Posting source code on my blog

I had a request from a reader as to how they could post sample code in response to an article I had written. I thought this was an excellent question and it can be hard to find the right answer when all you want to do is post your question.

The following is a quick example of posting a code snippet for XAML which is actually XML. If you want to submit XAML in a post or comment then you must wrap it with the following tag:

XAML snippet

It will then look like this once rendered:

<TextBox Text="Hello World" />

There are many other language targets. I use these common ones:

• csharp
• vb
• sql
• xml

Refer to this link for other targets as well.

Hope that helps….

Categories: English Tags: , , , , , ,

Silverlight 4 and Xml namespaces

May 6, 2010 5 comments

Probably one of the most irritating issues when dealing with Xaml in Silverlight was the pain of creating all the Xmlns namespace entries for the namespaces and assemblies you wanted to use in your Xaml.  The following is an example of what you would have to do in Sivlerlight 3:

<UserControl
x:Class="Designer.FormView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:radGrid="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.GridView"
xmlns:radDock="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.Docking"
xmlns:radNav="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.Navigation"
xmlns:radDrag="clr-namespace:Telerik.Windows.Controls.DragDrop;assembly=Telerik.Windows.Controls"
xmlns:radPrimitives="clr-namespace:Telerik.Windows.Controls.Primitives;assembly=Telerik.Windows.Controls"
xmlns:dc="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"
xmlns:toolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
xmlns:ctls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:input="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input"
xmlns:navMap="clr-namespace:System.Windows.Navigation;assembly=System.Windows.Controls.Navigation"
xmlns:nav="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
>
...
</UserControl>

Now in Silverlight 4, you can now do the following:

<UserControl
x:Class="Designer.FormView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:rad="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
>
...
</UserControl>

So if you want the same features and power in your own library, all you need to do is provide the XmlnsDefinition attribute. This attribute lives in the System.Xaml assembly.

If you would like to do this to your own assemblies, you would something like to the following to your AssemblyInfo.cs file:

[assembly: XmlnsDefinition(Constants.XmlNamespace, "Core.UI.View")]
[assembly: XmlnsDefinition(Constants.XmlNamespace, "Core.UI.Controls")]
[assembly: XmlnsDefinition(Constants.XmlNamespace, "Core.UI.Interactivity")]


[assembly: XmlnsPrefix(Constants.XmlNamespace, "core")]

internal static class Constants
{
    internal const string XmlNamespace = "http://schemas.mywebsite.com/core";
}

Finally, you would access you new namespace definitions with the following:

...
xmlns:core="http://schemas.mywebsite.com/core"
...

With this firmly tucked in your toolbelt, you are ready to make your developer’s lives much easier and nicer to deal with. If you have multiple assemblies, just use the same XmlNamespace so that they will all show up correctly.

Hope this helps…

Categories: English Tags: , ,
Follow

Get every new post delivered to your Inbox.

Join 193 other followers