P3.NET

A Smarter WCF Service Client, Part 1

WCF is a great way to implement service-based APIs but the standard approach to consuming a service lacks a lot to be desired. In this series of articles I will discuss the approach that I’ve used in commercial applications to make consuming WCF services much cleaner and simpler. We will start with the standard approach and identify its flaws. From there we will work toward a solution that has minimal impact on consumers while providing maximal benefit. The solution itself is large but not overly complex. More importantly, once finished, you never need to worry about it again.

Before Getting Started

This series of articles has prerequisite knowledge. If any of the following items are not familiar to you then read up on them before continuing.

Sample WCF Service

For purposes of demonstration we need a simple WCF service to call. Since the focus is on the client our service will not do anything useful. We will start with a simple service interface and expand as we get into more complicated scenarios. The service interface (and any types needed by the interface) will be contained in a class library separate from the service implementation. This is a traditional approach to service development.

[ServiceContract]
public interface IEmployeeService
{
   [OperationContract]
   Employee Get ( int id );

   [OperationContract]
   Employee Update ( Employee employee );

   [OperationContract]
   IEnumerable<Employee> GetEmployees ();
}

Caveat: The purpose of this series of articles is to demonstrate how to write a better service client. The focus is not to demonstrate how to write proper service interfaces. Therefore the interface will be created in a manner that best demonstrates the techniques being shown and not necessarily how you should write a production ready service interface.

The service implementation is not relevant for consumption so we will ignore it.

Service References

The standard approach to consuming a WCF service is using a service reference. Visual Studio (VS) will generate the necessary service proxy to talk with the service along with any contract types. On the surface this sounds great and is a common approach but it has many, many flaws. Before we get there though here’s an example of how you might consume the service using the generated code.

private void LoadEmployees ( )
{
   using (var client = new ServiceReference1.EmployeeServiceClient())
   {
      dataGridView1.DataSource = client.GetEmployees();
   };
}

Problems with Service References

Caveat: I loathe service references. At my company we forbid the use of service references because they introduce more problems than they solve. We have a superior approach anyway.

There are many problems with service references in general.

  • In order to add the reference the service must be running somewhere. Often this is the developer’s machine during development. This also means the service needs to be runnable before the client can even be started making parallel development harder.
  • A service reference generates lots of files (including .datasource files in some cases) that you might or might not need. Even worse is that whenever the reference is updated all the files are refreshed as well.
  • 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.
  • Unless you check the appropriate options when setting up the service reference then all non-standard data types used by the service are recreated in each project. This makes it difficult to share types between a service reference and underlying libraries. Even though the types are the same the compiler sees them differently and will not allow you to convert from a reference-generated type to an assembly-provided type. Note that the default for newer versions of VS is to reuse types in referenced assemblies but the references have to already exist and developers have to know not to turn this option off.

The generated code has problems as well.

  • The service client needs to be disposed when it is not needed anymore. If you’re using interface-based programming then this starts to fall apart. If you are using an IoC this is less of a concern.
  • A using statement is not sufficient because the default client proxy will throw an exception when disposing itself under certain conditions. The above code should actually be written like this.

    private void LoadEmployees ( )
    {
       ServiceReference1.EmployeeServiceClient client = null;
    
       try
       {
          client = new ServiceReference1.EmployeeServiceClient();
          dataGridView1.DataSource = client.GetEmployees();
       } finally
       {
          try
          {
             if (client.State != CommunicationState.Closed)
                client.Close();
          } catch
          {
             client.Abort();
          };
       };
    }
    
  • 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.

Building a Smarter Client

Building a smarter client is not terribly hard but it does require quite a bit of work. However once we’re done we won’t have to write this code again. Furthermore we will have an easily extensible framework to add new features as needed. Here is the rough outline of how we’re going to get to a smarter client.

  • Modify the existing code to isolate the service reference code.
  • Move all service contracts and data contracts to an assembly accessible by clients (using a shared folder, NuGet, etc).
  • Create a more robust service client wrapper that can handle the lifetime of the channel.
  • Create an example client implementation using the wrapper.
  • Modify client code to use the service contract directly and rely on the example client where needed.
  • Convert the example client into a T4 template that can be used to generate clients for any contract.
  • Extend the solution to provide support for optional functionality such as asynchronous calls and optimized client lifetime management.

Download the Code

Comments

  1. Matt Shepley

    I have over a dozen web apps using the:
    using (var client = new ServiceReference1.EmployeeServiceClient())
    {
    dataGridView1.DataSource = client.GetEmployees();
    };

    format of calling what used to be .NET 2.0 web services and are now WCF services.

    Do you have any recommendations for placement of the try-catch statements so that I don’t have to update all my service calls in my web apps?

    Thanks for your time and help!

  2. MichaelTaylor

    I don’t believe switching to try-catch is the correct answer. It is simply more code. The correct answer is to eliminate the need for the cleanup altogether. That is part of the goal of this series of articles. Replacing all such calls (even with the solution I’m presenting) is still probably going to be done as part of normal refactoring over time. I would recommend waiting until we get farther into the series when I demonstrate how to eliminate the need for even the using statement before trying to replace your existing code unless you have an immediate need.
    If you do have an immediate need then you could rename the existing proxy class to something else and create a new class that mimics the proxy class but with the smarter clean up code. Then all your existing code would be fine. For a small number of services this isn’t too bad but it is certain a short term solution.