Sunday, 6 December 2009

A Better WCF Client

Let's consider the following very simple WCF service interface:

public interface IAdd
int Add( int a, int b );

The usual methods of creating a client for this WCF service result in the creation of an AddClient class that derives from ClientBase<IAdd> and implements IAdd. This client has various problems:
  • Every time the IAdd interface changes, the client has to be regenerated. For services where you don't have access to the assembly that defines IAdd, this is not a problem because you'll have to regenerate the local definition IAdd anyway. However, if you have access to this assembly (usually in the case of a service that you yourself publish), then it is a bit of a pain to have to regenerate AddClient every time.
  • Disposing the AddClient is not as simple as one would think. There have been many posts on this (like here, here, and here), so I will not go into this in more detail.
  • I saved the worst for last: A good client should be transparent. That is, the fact that one is using a service reference to IAdd rather than a local implementation should be completely hidden except when the client is constructed (and disposed), allowing for dependency injection at the top level of the client program. One couldn't be faulted for thinking that this is indeed the case for the generated AddClient. However, it is not so. The underlying channel can become unusable for many reasons including, but not limited to, expiration of security tokens and a fault occuring during a call. The usual pattern to get around this is to construct and dispose the client within a using block every time you need to make a call or two. This is not only cumbersome but also breaks dependency injection because and AddClient would have to be constructed every where that it's needed. Of course, we could hide the construction behind an IClientProvider<IAdd> interface and pass that around instead of IAdd (this is like passing around the dependency injector instead of passing around the dependency itself). But this is still cumbersome and inelegant because we'd still have to have a using block every time we want to make a call. Besides, IAdd is not disposable (but AddClient is) and you'd have to jump through some hoops to make the using block work. Also, as much as possible, I like to keep code unaware of any dependency injection except at the top level (for example, in the Main() method).
  • This is closely related to the previous point: With secure channels, the process of creating a client is very expensive and given the periodic expiration of security tokens, client pooling becomes necessary. This has to be done manually and the common solutions break the transparency requirement in the previous point.

After getting sufficiently bothered by this state of affairs, I decided to fix it. The result is available here as a small (20KB) runnable sample. The client framework is in WcfClientLibrary\ClientFramework.cs. AddClient\Program.cs shows how it is used. The special sauce here is the use of a System.Runtime.Remoting.Proxies.RealProxy-derived class that provides a transparent proxy object and allows the interception of proxy calls. There is some reflection-based method invoking involved here.

Using the WCF client framework in this sample, here is all the code you would have to write to create a client to an IAdd service:

IClientProvider<IAdd> addClientProvider = new ClientProvider<IAdd>(
new ServiceChannelProvider<IAdd>(
new ChannelFactory<IAdd>( "AddServiceEndpoint" ) ) );
IAdd addClient = addClientProvider.GetClient();
// addClient can now be used as a plain and simple IAdd. It automatically
// handles refreshing the underlying channel when it becomes unusable.
// The addClient itself does not need to be disposed. But the addClientProvider
// should be disposed on program shutdown (and this is a well-behaved
// Dispose(), unlike that of ClientBase). Please look in the sample
// for more details (in AddClient\Program.cs)

There is no other hidden generated code. Of course, if you don't have access to AddServiceLibrary from your client app, you'll need to use SvcUtil.exe to generate the IAdd interface.

You can create a pooling client provider as follows:

IClientProvider<IAdd> addClientProvider = new ClientProvider<IAdd>(
new PoolingServiceChannelProvider<IAdd>(
new ChannelFactory<IAdd>( "AddServiceEndpoint" ) ) );

No comments:

Post a Comment