Building a Better WCF Client
The WCF client that Visual Studio generates by default is, to put it bluntly, a problem. Besides not being a direct match with the server-side interface code, which causes a lot of code and data sharing problems, the generated code still has a glaring bug after all these years. Michael Taylor discusses these issue and his attempts to address them in a series titled “A Smarter WCF Service Client”.
One well known problem for WCF client proxies is that they cannot be generated unless the server is actually running. This makes updating the service reference at build time difficult, if not impossible, for many teams. Instead teams have to fall back on remembering to notify each other of changes and manually update the service reference as needed. Michael continues,
The URL used to generate the reference is stored in the generated code (.svcmap). If the reference is updated then the original URL is used. This can cause problems if the original URL is old or refers to a developer’s machine. Even worse is that changing the URL in order to regenerate the reference causes all the files to change even if there is no actual code changes involved.
A common mistake when using a service reference style WCF client is the use of a 'using' block. While common, this practice is inappropriate because the WCF client may throw an exception when Dispose is invoked. This is known as the “WCF Dispose Problem” and work-arounds are quite common.
Other problems cited by Michael include,
- While WCF uses interfaces to hide the implementation details, the generated code actually contains a different interface that happens to share the same name. The interface is defined in the service reference code. This makes using the original interface across projects more difficult.
- Even worse is that some of the method signatures may be changed. For example enumerables and collections get converted to arrays, by default. Parameters can even be moved around.
- In Visual Studio you can use Find All References to find all references to types and members. But if you are using service references then FAR won’t detect them because, again, the service reference generate a new interface.
- Any data contracts that are pulled over are modified as well. They include properties that are not on the original object. If any code uses these properties then they are now using infrastructure provided by WCF which makes unit testing harder.
- Don’t even think about sharing data that was generated by a service reference in one project with any other project (even if it is the same service). The types are different to the compiler irrelevant of their name.
- The whole reason WCF uses interfaces is for abstraction but because of how service references work there is no easy way to abstract the actual usage of the service.
Michaels first offering is a subclass of ClientBase<T> that correctly implements the IDisposable pattern. He writes,
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.
In part three of his ongoing series, Michael talks about how to reduce the boilerplate needed for making individual service calls into a single line. For example,
ServiceClientFactory.InvokeMethod<ServiceReference1.IEmployeeService>(c => c.Update(dlg.Employee));
This pattern does introduce some of its own limitations,
Perhaps the biggest issue with the above code is the reliance on a static class. Static classes cannot be easily mocked during testing so any code that uses the static class cannot be easily unit tested without the WCF services being available. You can work around this by creating a virtual method that then calls the static class but you will end up with lots of virtual methods (probably one for each service method) just for unit testing purposes.
Michael intends to correct these issues in a future article.