In the last article we made a case for why the standard WCF client generated by a service reference is not a great idea. Among the issues listed were testability and proper cleanup. To fix these issues we will need to ultimately replace service references altogether but this is time consuming for large code bases. When we started down this path at my company we did it using an iterative approach. While the intermediate stages may appear to be (and, in fact, may be) a step back we are ultimately working toward a solution that I believe is far better.
Wrapping the Client
ClientBase<T> is the base class for service clients. As mentioned previously this type does not properly handle error states when cleaning up the client. The type implements IDisposable which in turn calls Close. The implementation of Close
does not look at the state of the channel other than to confirm it is not null. In some cases an exception during a service call will set the entire channel to a faulted state. When this occurs calling Close
will throw an exception. Instead it is necessary to call Abort. We can handle this in our code by using a try-catch statement with the appropriate checks but that code is boilerplate and easy to forget. A better solution is to fix the implementation. Here is a simple class that does this wrapping.
public class ServiceClientWrapper<TChannel> : ClientBase<TChannel>, IDisposable where TChannel: class { public ServiceClientWrapper () { } public ServiceClientWrapper ( string endpointConfigurationName ) : base(endpointConfigurationName) { } public TChannel Client { get { return Channel; } } public new void Close () { ((IDisposable)this).Dispose(); } protected virtual void Dispose ( bool disposing ) { try { if (State != CommunicationState.Closed) base.Close(); } catch (CommunicationException) { base.Abort(); } catch (TimeoutException) { base.Abort(); } catch { base.Abort(); throw; }; } void IDisposable.Dispose () { Dispose(true); GC.SuppressFinalize(this); } }
The above code just shows the core methods. For consistency with ClientBase<T>
the actual implementation includes additional constructors to allow for creating clients. The wrapper is designed to replace existing code that uses ClientBase<T>
. Also note that it is actually not designed for use directly in code. It is actually a building block for a better solution.
The WCF interface is exposed via the Client
property. For now we will use this property whenever we need to call the WCF services. By the end of this series this restriction will be gone so we will just accept it for now.
Another interesting point about the wrapper is that it implements IDisposable
. ClientBase<T>
already implements this interface so why are we reimplementing it? Because the base type doesn’t follow the correct approaching to implementing the interface. When implementing this interface a class is supposed to provide an overridable method that derived classes can implement but this one does not. In order to fix the cleanup code we have to reimplement the interface and the actual method. But we will implement it properly, both in terms of derived types and in handling faulted channels.
Using the Wrapper
Now that we have the wrapper we can replace all instances of ClientBase<T>
with it. But the service reference code should not be modify because it is auto generated. We are not yet ready to fully replace the service reference code but we can, at least, replace any places where we create the service client with our new type. Unfortunately we will lose some functionality for now. Here is the original and updated versions of the application code.
//Original using (var client = new ServiceReference1.EmployeeServiceClient()) { client.Update(dlg.Employee); }; //New using (var client = new ServiceClientWrapper<ServiceReference1.IEmployeeService>()) { client.Client.Update(dlg.Employee); };
It is not much cleaner than before but it is only the first step in a larger solution. Even with the new client there is still too much code in my opinion. The best code is the code you don’t have to write and using statements are nothing more than infrastructure pushing its way into code. Next time we will eliminate the need for even this much code.