Dealing with improper disposal in WCF clients
Published by marco on
There’s an old problem in generated WCF clients in which the Dispose()
method calls Close()
on the client irrespective of whether there was a fault. If there was a fault, then the method should call Abort()
instead. Failure to do so causes another exception, which masks the original exception. Client code will see the subsequent fault rather than the original one. A developer running the code in debug mode will have be misled as to what really happened.
You can see WCF Clients and the “Broken” IDisposable Implementation by David Barrett for a more in-depth analysis, but that’s the gist of it.
This issue is still present in the ClientBase
implementation in .NET 4.5.1. The linked article shows how you can add your own implementation of the Dispose()
method in each generated client. An alternative is to use a generic adaptor if you don’t feel like adding a custom dispose to every client you create.[1]
public class SafeClient<T> : IDisposable
where T : ICommunicationObject, IDisposable
{
public SafeClient(T client)
{
if (client == null) { throw new ArgumentNullException("client"); }
Client = client;
}
public T Client { get; private set; }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (Client != null)
{
if (Client.State == CommunicationState.Faulted)
{
Client.Abort();
}
else
{
Client.Close();
}
Client = default(T);
}
}
}
}
To use your WCF client safely, you wrap it in the class defined above, as shown below.
using (var safeClient = new SafeClient<SystemLoginServiceClient>(new SystemLoginServiceClient(…)))
{
var client = safeClient.Client;
// Work with "client"
}
If you can figure out how to initialize your clients without passing parameters to the constructor, you could slim it down by adding a “new” generic constraint to the parameter T in SafeClient
and then using the SafeClient
as follows:
using (var safeClient = new SafeClient<SystemLoginServiceClient>())
{
var client = safeClient.Client;
// Work with "client"
}