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"
}


[1] The code included in this article is a sketch of a solution and has not been tested. It does compile, though.