this.Blog.Find(entry => entry.IsHelpful);
 Wednesday, June 10, 2009
WCF and IDisposable

If you’re like me (and apparently many other people), when you start using (no pun intended) WCF by creating a client channel, it’s only a matter of time before you realize that you’re probably doing it wrong. 

Problem number one is that if you don’t explicitly close the connection, it’s only a matter of time before the connection starts faulting.  So you say to yourself, “Aha!  I need to close the damn connection.  And since I’m a good .Net programmer, I see the IChannelListener interface extends the IDisposable interface so I can just wrap my call in a ‘using’ statement which will explicitly clean up all my resources no matter what happens during the method execution.”

And then an exception happens.  Even though you wrap your call in the using statement, you see this weird exception about the channel being in the faulted state (or something like that).  Wait – I wrapped my call in the using statement.  This should work!  So you fire up Google Bing and quickly learn the WCF team made the decision to implement IDisposable wrong uniquely. 

In order to get a WCF client to work as expected, you need to workaround the decision made by the WCF team and wrap all your WCF calls in blocks similar to this one:

  1: var channelFactory = new ChannelFactory(binding, endpointAddress);
  2: 
  3: var proxy = channelFactory.CreateChannel<ICustomerService>();
  4: IClientChannel clientChannel = (IClientChannel)proxy;
  5: bool isSuccessful = false;
  6: try
  7: {
  8:   proxy.AddCustomer(customer);
  9:   clientChannel.Close();
 10:   isSuccessful = true;
 11: }
 12: finally
 13: {
 14:   if ( !isSuccessful )
 15:   {
 16:     clientChannel.Abort();
 17:   }
 18: }

As you can see here, you must explicitly call Close() on the client channel unless an Exception is thrown, in which case you need to call the Abort() method.

This code doesn’t just emit a code smell of violating the DRY principle, it absolutely reeks of it.  Steve Smith (and others) have come up with extension/utility methods aimed at simplifying the call and removing the repetitive boiler plate code from the above example.  Inspired by these solutions, I’d thought I’d throw out my own answer to this problem:

  1: public class WcfClient<TInterface>
  2: {
  3:   public WcfClient(Uri uri) : this(uri, new NetTcpBinding(SecurityMode.None))
  4:   {
  5:     Uri = uri;
  6:   }
  7:   
  8:   public WcfClient(Uri uri, Binding binding) 
  9:   {
 10:     this.Uri = uri;
 11:     this.Binding = binding;
 12:   }
 13:   
 14:   public Uri Uri
 15:   {
 16:     get;
 17:     private set;
 18:   }
 19:   
 20:   public Binding Binding 
 21:   {
 22:     get
 23:     private set;
 24:   }
 25: 
 26:   public void Using(Action<TInterface> serviceAction)
 27:   {
 28:     TInterface proxy = CreateChannelFactory().CreateChannel();
 29:     IClientChannel clientChannel = (IClientChannel)proxy;
 30:     bool isSuccessful = false;
 31:     try
 32:     {
 33:       serviceAction(proxy);
 34:       clientChannel.Close();
 35:       isSuccessful = true;
 36:     }
 37:     finally
 38:     {
 39:       if ( !isSuccessful )
 40:       {
 41:         clientChannel.Abort();
 42:       }
 43:     }
 44:   }
 45: 
 46:   public TResult Using<TResult>(Func<TInterface, TResult> serviceAction)
 47:   {
 48:     TResult result;
 49:     TInterface proxy = CreateChannelFactory().CreateChannel();
 50:     IClientChannel clientChannel = (IClientChannel)proxy;
 51:     bool isSuccessful = false;
 52:     try
 53:     {
 54:       result = serviceAction(proxy);
 55:       clientChannel.Close();
 56:       isSuccessful = true;
 57:     }
 58:     finally
 59:     {
 60:       if ( !isSuccessful )
 61:       {
 62:         clientChannel.Abort();
 63:       }
 64:     }
 65: 
 66:     return result;
 67:   }
 68:   
 69:   private ChannelFactory CreateChannelFactory()
 70:   {
 71:     var endpoint = new EndpointAddress(Uri);
 72:     return new ChannelFactory(Binding, endpoint);
 73:   }
 74: }

The two Using() methods offer an overloaded way to invoke a method on the service proxy.  In the first version, you can invoke an Action call which doesn’t expect a returned value.  In the second version, the TResult generic type determines the type of value expected from the invocation of the proxy object.  The return value will flow through from the proxy call execution to the caller.  Think of these two methods as being akin to the differences between a Sub or a Function in VB (my God, did I just admit to knowing VB?!?!?!).

The WcfClient code is responsible for creating the client proxy and returns the proxy back to the calling code.  As the caller, here is how you would execute the methods:

  1: string wcfUri = ConfigurationManager.AppSettings["wcfUri"];
  2: 
  3: var wcfClient = new WcfClient<ICustomerService>(wcfUri);
  4: 
  5: wcfClient.Using(proxy => proxy.AddCustomer(new Customer()));
  6: 
  7: // ...
  8: 
  9: Customer[] customers = wcfClient.Using(proxy => proxy.GetCustomers());

The nice thing about this solution is that we take care of the exception handling and correctly clean up our resources in one place, and that logic is completely shielded from the calling code.  Plus, we can account for situations where a return value is expected versus when one is not.


Kick it on DotNetKicks.com
Tuesday, June 09, 2009 11:02:10 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  .Net | C# | WCF

Comments are closed.