I’ve been involved in a lengthy discussion recently at work involving the IDisposable implementation in WCF clients.
The topic is a complex one, potentially involving discussion of fault propagation strategies, general IDisposable patterns, proxy lifetime patterns, and the like. However, for the purposes of this post, I’m only interested in the implementation details of IDisposable in WCF clients (specifically, System.ServiceModel.ClientBase<T>).
Props to a very respected co-worker of mine, Chris Rolon, for the following question: What’s wrong with the following code snippet (where SandboxServiceClient is a WCF client)?
using (sandbox.SandboxServiceClient serviceClient =
new sandbox.SandboxServiceClient()) {
serviceClient.HelloWorld(name);
}
At first glance, one would think, “nothing”. And you could very well argue that many, many developers out there would be comfortable with it, oblivious to the potential pitfall contained within. The using statement is an implicit IDisposable pattern, and if you’re not intimately familiar with the using statement, simply know that the IDisposable interface Dispose method is called at the end of the using statement to ensure that resources are cleaned up.
If you use Reflector to examine the native behavior of the ClientBase<T> class (the base class of svcutil.exe-generated WCF client proxies), you see that the IDisposable implementation looks like:
void IDisposable.Dispose() {
this.Close();
}
“Fine”, you say. No problem. Ahhh, but let’s assume that the service throws an exception during the course of fulfilling your request, and let’s assume the service (a .Net-based service) throws a native .Net exception type. The .Net WCF infrastructure is smart enough to translate that exception into a fault. But here’s the kicker. In certain situations (default out of the box wsHttpBinding being one of them), receiving a fault exception puts the client into a faulted state.
Guess what happens when you call this.Close() on a faulted ClientBase<T>? It throws an exception.
Bad, bad, bad.
So, in the scenario above (the using statement), when the Dispose method is called at the end of the using statement, the Dispose method throws an exception. The net result is that the original exception (the fault exception) is lost.
Bad, bad, bad.
So, how do you remedy the problem? There are actually a couple different ways, but to solve the dilemma of the ‘broken’ IDisposable implementation, you need to check the state of the client to know whether you call .Close, or in the case of a faulted client, the .Abort method. You can do this by avoiding the using statement altogether, and instantiating a client, performing your call, and then checking the status via if/then to know whether to call .Abort or .Close.
The slightly more elegant solution (IMHO), and the one proposed by Chris Rolon, was to override the default IDisposable behavior of the scvutil.exe-generated class. Because this class is generated as partial, you can provide another partial implementation such as:
public partial class SandboxServiceClient : IDisposable {
void IDisposable.Dispose() {
if (this.State == CommunicationState.Faulted) {
this.Abort();
} else {
this.Close();
}
}
}
With the fix implemented, faults occurring in the using statement are correctly propagated up the call stack, and are not in danger of being overwritten by a faulty IDisposable implementation.
As I mentioned above, there are other factors that can influence this scenario. Implementing an exception shielding and propogation pattern on the service side via expected typed fault exceptions eliminates this issue, as the client behaves as expected when the interface declares (and the client receives) a typed fault. Incidentally, using basicHttpBinding (and possibly others) prevents this issue from happening. Some have also stated that a using statement is completely silly, as WCF clients are expensive to construct, and should be kept around for the lifetime of the application (and thus, the issue becomes more one of managing the state of the client, resetting it appropriately so that’s it ready for the next call).
Regardless, a widely known pattern such as the using statement — that’s often suggested as “best practice” for logically cleaning up resources once you’re done with them — turns out to be a pitfall in this case. But, knowing what’s going on under the hood, we can adapt to correct the faulty behavior, and continue coding the way we’re comfortable with..
posted on Thursday, November 22, 2007 8:58 PM Print