I've learned two new things in the last week about WCF or, to be more accurate, I have had two of my foolish assumptions corrected. The first assumption was that MSMQ provides support for disconnected interoperable services, while the second was that I would never need to self-host a WCF service in a Windows Forms/WPF application. Happily I now have a test application that does both of these things, thus giving me a pattern for what used to be called a "smart client".
Both of these discoveries came about as part of a project to deliver an occasionally-connected application for tablet devices. Basically, the client application would collect a load of data, package it up into a DTO, then persist it via a WCF service. Given the never-ending CTP state of the Sync Framework and my preference for SOA over the client/server database approach, I thought MSMQ would facilitate a single programming model regardless of the connectivity status of the client - simply designate the appropriate WCF service methods as one-way calls and expose over an MSMQ endpoint.
The problem with this is that MSMQ doesn't do what I thought it did. I assumed that I could expose a service endpoint somewhere "out there", then the MSMQ local queue on the client would monitor the availability of a network connection and post messages in the local queue off to the service when connectivity was achieved. This works very nicely in public queues, but they require an Active Directory installation and can't be exposed outside of the Intranet. A private queue (in a workgroup installation) can only post to/monitor an MSMQ service on the same machine. I did consider a bespoke solution using Isolated Storage, serialization, and a timer control - but it didn't have the transactional reliability of MSMQ (and had all the hallmarks of a badly reinvented wheel).
The answer to my problem came from the oft-quoted bible of WCF: Juval Lowy's "Programming WCF Services", where he addresses the issue of MSMQ over the Internet by means of the HTTP bridge pattern (p.518) and also the bit where he discusses responsiveness when hosting WCF services in a UI application (p.416). My solution is to post all messages into a local private queue, then trap the network connectivity events in the System.Net.NetworkChange class and launch/shutdown (as appropriate) a local MSMQ service host in the UI application (which must have the UseSynchronizationContext value on the ServiceBehavior attribute set to true so as not to lock up the UI thread). When the local MSMQ service starts up it sends all messages to the distant (HTTP) service using the bridge pattern. If the send fails (or any other unhandled exception occurs) then the local MSMQ service gets faulted and shuts down. It will start up again either the next time the network status active event is raised or when the application is opened again (the only problem with this approach being that if the call to the distant service fails because of a problem with the service itself then the call could well fail again the next time the local MSMQ service gets launched - but this doesn't lead to lost messages so I can live with it).
The sequence of activity is something like:
- Form startup
- Create queue (if it doesn't exist) on a background thread
- When background worker returns set up event handler for NetworkAvailabilityChanged
- If a network connection is available then launch the MSMQ service
- If the network connection becomes unavailable then shut down the service
- If the service faults then shut it down
- If the network connection becomes available then launch the service
Getting an exception to fault the service host is done in the binding configuration:
<bindings> <netMsmqBinding> <binding receiveErrorHandling="Fault" receiveRetryCount="0"> <security mode="None"/> </binding> </netMsmqBinding> </bindings>
This will stop any retries after the fault occurs. Subscribing to the Faulted event on the service host will provide a hook for taking action after a fault occurs.
The sample project can be downloaded here. It requires MSMQ workgroup installation, which can be reached by the following route:
- On Windows XP this can be enabled by going into Control Panel, Add Remove/Programs, Add/Remove Windows Components.
- In Vista/Windows 7 this can be enabled by going into Control Panel, Programs, Turn Windows features on or off.
Run the sample, disable your network adapter, make a call, then enable your network adapter – it should send the messages queued locally. It’ll need to be run in debug mode as the distant service simply writes out to the Debug/Output window, but it should be sufficient implementation for illustration.