DataBinding WPF to a WCF Service, and Leveraging IValueConverter for Encoded XML

I met with a customer the other day who had an interesting question:  how do I databind a WPF app to a WCF service?  I thought this was going to be a really quick answer, it turned into another really long blog post.

For the service, I am using a slimmed down version of the default service generated by Visual Studio 2008 (File / New / Web Site / WCF Service). 

 using System;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Xml.Linq;

[ServiceContract]
public interface IService
{
    [OperationContract]
    CompositeType GetDataUsingDataContract(CompositeType composite);
}


[DataContract]
public class CompositeType
{
    bool boolValue = true;
    string stringValue = "Hello ";

    [DataMember]
    public bool BoolValue
    {
        get { return boolValue; }
        set { boolValue = value; }
    }

    [DataMember]
    public string StringValue
    {
        get { return stringValue; }
        set { stringValue = value; }
    }
}

public class Service : IService
{
    public CompositeType GetDataUsingDataContract(CompositeType composite)
    {
        XDocument doc = new XDocument
            (
            new XElement("customer",
                new XElement("id","kirke"),
                new XElement("name","Kirk Allen Evans"))
            );
                
        return new CompositeType { 
 BoolValue = composite.BoolValue, 
 StringValue = doc.ToString() };
    }
}

One thing to notice is the service implementation... This is fairly close to the type of service that my customer is calling.  Notice that I am explicitly sending back an encoded XML string from the service.  The actual XML on the wire looks something like this:

 <s:Envelope xmlns:s="https://schemas.xmlsoap.org/soap/envelope/">
<s:Header></s:Header>
<s:Body>
<GetDataUsingDataContractResponse xmlns="https://tempuri.org/">
<GetDataUsingDataContractResult 
 xmlns:a="https://schemas.datacontract.org/2004/07/" xmlns:i="https://www.w3.org/2001/XMLSchema-instance">
<a:BoolValue>false</a:BoolValue>
<a:StringValue>&lt;customer&gt;
  &lt;id>kirke&lt;/id&gt;
  &lt;name&gt;Kirk Allen Evans&lt;/name&gt;
&lt;/customer&gt;</a:StringValue>
</GetDataUsingDataContractResult>
</GetDataUsingDataContractResponse>
</s:Body>
</s:Envelope>

I have written before that I think it is generally evil to return encoded XML strings from web services, however I now admit that sometimes encoded XML strings can be OK.  You should still avoid encoded XML in your web services at all costs because once the client consumes your service, you now force them to decode and parse your XML string into something meaningful. 

Calling the WCF service was dead simple, but I quickly realized that I had a problem if I wanted to use WPF's databinding.  I have an encoded XML string to deal with... I can simply splat the whole string into a text box, but what if I wanted to find values within that string?

The solution is to create a System.Windows.Data.IValueConverter implementation.  The code is fairly straightforward... get the original XML string into an XElement object, run through the descendant axis looking for a specific element name, and return its value.

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Xml.XPath;
using System.Xml.Linq;
using System.Xml;
using System.IO;

namespace WpfApplication2
{
    public class XmlValueConverter : IValueConverter
    {
        
        public object Convert(object originalValue, Type targetType,
            object parameter, System.Globalization.CultureInfo culture)
        {                        
            //Parse the XML string
            XElement element = XElement.Parse(originalValue.ToString());
            

            ////Get the first child node with the supplied parameter name
            element = element.Descendants(parameter.ToString()).First();
            
            //Return the string value of the element's contents
            return element.Value;
        }

        public object ConvertBack(object value, Type targetType,
            object parameter, System.Globalization.CultureInfo culture)
        {
            return null;
        }     
    }
}

OK, the easy part was figuring out how to write the converter.  The hard part was figuring out how to actually use it.  Once I figured that bit out, the rest is cake.

 <Window
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="WpfApplication2.Window1"
    Title="Binding To A WCF Service With WPF" 
    Height="300" 
    Width="300"
    xmlns:my="clr-namespace:WpfApplication2">
    <Window.Resources>
        <my:XmlValueConverter x:Key="xmlconvert"/>
    </Window.Resources>
    <StackPanel x:Name="panel" >
        <TextBox Text="{Binding Path=StringValue, 
 Converter={StaticResource xmlconvert}, ConverterParameter= id}" 
 TextWrapping="Wrap"/>
        <TextBox Text="{Binding Path=StringValue, 
 Converter={StaticResource xmlconvert}, ConverterParameter= name}" 
 TextWrapping="Wrap"/>
        <TextBox Text="{Binding Path=BoolValue}" />
        <Button Click="Button_Click">Click Me</Button>
    </StackPanel>
</Window>

The part to notice is that I specify my custom Converter, and you can also pass parameters to your converter.  This is what I used to pass in the name of the element in the encoded XML string that I want to bind to.  It kind of functions like the XmlDataProvider, but is different because we are binding to the StringValue property and can't simply use XPath over it.. we have to decode the string and then query it as an XML tree.

The code-behind for the XAML looks like this:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;
using System.ServiceModel;
using System.ComponentModel;
using WpfApplication2.ServiceReference1;

namespace WpfApplication2
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();            
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (!DesignerProperties.GetIsInDesignMode(this))
            {
                ServiceClient s = new ServiceClient();
                CompositeType ret = s.GetDataUsingDataContract(
 new CompositeType { StringValue = "hello", BoolValue = false });
                panel.DataContext = ret;
            }
        }
    }
}

You can see that setting the context is incredibly simple, we just set the StackPanel's context to an object, and specify the binding path in the XAML itself. 

I kept the BoolValue property in there to prove a point: you don't have to go through all of this if you just strongly type the data being returned. 

I am really impressed with how incredibly flexible WPF's databinding architecture is.  I was able to extend WPF's databinding capabilities into something it clearly wasn't meant to do out of the box in just 3 lines of code.