Inter Process Communication Between Applications and Vista Gadgets Using WCF (Part 2)
Once I got WCF working in a gadget, another scenario came up: wouldn't it be nice if the gadget and the application shared a data model? The gadget could remotely databind to the same data that the application was databound to. As such, there would be one data source, owned by the application, that the gadget could both manipulate as well as receive updates when things changed. To be clear, there are not two data sources that I am trying to keep in sync, but rather one data source owned by the application which the gadget reads and writes to. (It does have a "local copy" but from the code never touches the "local copy" and only manipulates the data source through its proxy class to the master copy.) I wanted to use as much of the WPF databinding as I could to try and get some of the databinding goodness for free. I based the code on a sample in the SDK under the WCF samples called Data Binding in a Windows Presentation Client. Also, I found this blog post helpful.
The architecture is different than my earlier example because there is a more pure client/server relationship between the application and the gadget. The application acts purely as the server and really could care less if it has any clients. The gadget acts as a client only, unlike my earlier sample.
Similar to my earlier sample, I removed the metadata exchange and share type between projects. Again, because no one else will be using the service, it turns out to be simpler to just share the code and not rely on the proxy code generation tool. In this case, I have have data contracts shared as well as contract. The contract itself is a bit trickier because I am using a Duplex channel, which is what allows the application to notify the gadget that data has changed. As such, I decorate the contract with the SessionMode=SessionMode.Required attribute. I then decorate the server implementation of the contract with [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,ConcurrencyMode = ConcurrencyMode.Multiple)] . Notice that I still have an InstanceContextMode of single, so that I only have one instance of the service object, but I allow a ConcurrencyMode of multiple, so that if there were multiple clients to the service, all would still be hunky dory. (Thanks to Craig McMurtry for helping understand the right choices to make here.)
In the implementation of the service, I used a trick I picked up from the fantastic samples up at the IDesign site. Basically, I use their technique of registering a list of clients through the use of a Connect method. Once a client is registered, it can be called back from the server. The convention of Disconnect allows the server to clean up any subscribers. Nice!
As I mentioned earlier, I wanted to try to preserve as much databinding goodness as I could. As such, I subclasses ObservableCollection and overrode the OnCollectionChanged event. It is within this event that I callback to any listeners to let them know that the data has changed. Unfortunately, I couldn't simply serialize the NotifyCollectionChangedEventArgs. As such, it would take quite a bit of custom code to truly serialize this event -- looking at Reflector is a good start if you are interested in doing this. For my purposes, I just notify the client that the data has changed so that they can rebind. It is worth calling out that this same technique could be used to serialize the INotifyPropertyChanged event as well, for more granular control over changes instead of the brute force approach I took here.
One thing I do in the client (which I also learned from the IDesign site) was to create my own asynchronous proxy class, rather than having the svcutil tool generate it for me. I then use a slightly different technique in instantiating the client than I did in part 1. This time, I create a DuplexChannelFactory which I use to new up clients to the service. Another thing I did was not use config files but rather I have things like the endpoint hardcoded into the app -- more secure.
I do something simliar as I did in part 1 as far as making the gadget reliable in the event that the application isn't running by wrapping the initial Connect operation in a do/while statement, nulling out a client object if it fails to connect.
The asynchronous databinding is entirely lifted from the SDK sample other than the moment where I get a callback. At first I tried to call the service as soon as I received the CollectionChanged notification. However, this created a race condition. So, I call the service on a background thread when I receive the callback, which worked out fine.
The WPF code itself is wrapped up in an ActiveX control, as discussed in this post. Nothing new here. Of course, installing the ActiveX control requires elevation.
So, to get the sample working, first run the install.cmd from a cmd prompt run as administrator to register the ActiveX control. Then, compile the databinding_serv.sln file in the sources directory. Again, if you don't have VS on your Vista box, you can use this trick from Tim Sneath to compile from a command line. You'll notice that the gadget binds to the application's data whenever the application comes online. If you update the data in the application, the gadget will get updated. And if you update the data in the gadget, the application's data is updated. Note that I never manipulate the data directly within the gadget, but always through the proxy. Also, note that I rebind to the gadget data every time, which isn't super efficient and could be a problem with larger data sets. Of course, it is communication on the same box, which is pretty darned fast. Go WCF!