Thursday, March 01, 2012

Proxy-Free WCF: What's In A Proxy

Writing services-based applications using Visual Studio has always been fairly easy, dating all the way back to the web services support in Visual Studio 2003. These days, services means WCF services, but the IDE support has remained largely the same. You just need to supply Visual Studio with the URL for your web service, close your eyes, tap your heels together, and poof! You get a fully-functioning WCF client proxy class all ready to go.

This process works very well for simple applications, or applications using third-party web services. But when you start writing applications against your own services, and factor in things like deployment, unit testing, etc., the default client proxy generate can really start to work against you. Fortunately, you do not need to use these client proxy classes: WCF includes everything you need to generate them on demand, at runtime, with only a tiny bit of effort.

The Use For Proxy Classes

In general, the kinds of services we use in a .NET application fall into two broad groups: third-party external services, and custom internal ones.

Web services in the first category include things like Google, Amazon, or Bing's public service APIs, but also commercial web services and third-party service-based APIs. (Our ImageQuest product, for example, includes a WCF-based API for basic document management.) These types of services have a few common features:
  • They sit at well-known, relatively static URLs
  • The service contracts are stable over the short and medium term, and changes are well-coordinated
  • Everything the client knows about the service comes from standard metadata channels, like WSDL or MEX.
In these cases, using Visual Studio's tooling support for services (which is basically the same as running svcutil.exe) makes sense. But lets take a look at the other group: internal services. These are services meant solely to support your own client applications, taking the place of DCOM or MTS or Remoting or whatever previous RPC mechanisms we used to have forced upon us. These services are usually quite different from the public variety:
  • They are published in multiple places (dev, test, and prod machines) during the development cycle.
  • The contract changes rapidly as the product's features evolve.
  • The contract is defined outside of the service, in the C# source definition of the interface .
  • The service calls must often participate in unit testing
For these reasons, using the Visual Studio generated proxy classes often adds layers of overhead (trying to mock them up, refreshing them every time the service changes, updating the URLs) without really adding any value. To understand why, lets quickly take a look at what really gets generated when we create a service reference.

Dissecting The Client Proxy

Lets start with a very simple service that implements the following contract:
[ServiceContract(Namespace = "http://namespaces.kutulu.org/wcf/proxyless")]
public interface IProxyless
{
    [OperationContract]
    bool IsVersionOk(string version);

    [OperationContract]
    string GetConfigurationElement(string key);

    [OperationContract]
    List<result> ProcessData();
}

[DataContract(Namespace = "http://namespaces.kutulu.org/wcf/proxyless/data")]
public class Result
{
    [DataMember]
    public string Operation;

    [DataMember]
    public int Value;

    [DataMember]
    public bool Success;
}
If we allow Visual Studio to generate a Service Reference to this service, we get a bunch of files added to our project (all thoughtfully hidden behind a single "service reference"), as in the following image:


Now, all but one of those files are just for reference; the *.xsd and *.wsdl files are just copies of what was produced by the service, and include the contract definition and configuration parameters that Visual Studio used to produce its proxy class. In most cases, we have one WSDL file with the overall definition, and one XSD file per OperationContract or DataContract your service exposes. The *.svcinfo files include copies of the original configuration data that was conveniently inserted into your application's local configuration file. The *.svcmap file just holds a list of all the other files in a single place. The basic point of all of this is to support the "Update Service Reference" operation as seamlessly as possible. Since we aren't planning to have a service reference, all of those files are extraneous.

We also, helpfully, get a *.datasource file that causes our data contract(s) to show up as project data sources if we ever need one. This is handy when dealing with the proxy definitions because the actual type definition is buried pretty deep otherwise, but when we eliminate the proxy, this won't be nearly as useful.

The real interesting bit is the Reference.cs file, which is the actual C# code generated for our proxy. This file contains a couple of things, which we'll look at one at a time.

Data Contract Definitions

For each data contract defined by our service, the proxy generator recreates a type that has the same serialization format. It also automatically implements the INotifyPropertyChanged interface on the proxy data objects, even if our source object didn't implement them. In fact, there is no direct relationship between our source data object and our proxy one except for the list of public fields it exposes. For example, our sample Reference.cs includes a Result class (abridged here for space):
[System.Runtime.Serialization.DataContractAttribute(Name="Result", Namespace="http://namespaces.kutulu.org/wcf/proxyless/data")]
[System.SerializableAttribute()]
public partial class Result : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {
    [System.Runtime.Serialization.OptionalFieldAttribute()]
    private int ValueField;

    // Other Fields 

    [System.Runtime.Serialization.DataMemberAttribute()]
    public int Value {
        get {
            return this.ValueField;
        }
        set {
            if ((this.ValueField.Equals(value) != true)) {
                this.ValueField = value;
                this.RaisePropertyChanged("Value");
            }
        }
    }

    // Other Properties
        
    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
        
    protected void RaisePropertyChanged(string propertyName) {
        System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
        if ((propertyChanged != null)) {
            propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
        }
    }
}

Service Contract Definitions

For each service contract (usually one per service) there is a C# interface generated that implements the interface, along with the metadata to associate it back to the service calls. Our example looks like this:
[System.ServiceModel.ServiceContractAttribute(Namespace="http://namespaces.kutulu.org/wcf/proxyless", ConfigurationName="ProxylessService.IProxyless")]
public interface IProxyless {
        
    [System.ServiceModel.OperationContractAttribute(Action="http://namespaces.kutulu.org/wcf/proxyless/IProxyless/IsVersionOk", ReplyAction="http://namespaces.kutulu.org/wcf/proxyless/IProxyless/IsVersionOkResponse")]
    bool IsVersionOk(string version);
        
    [System.ServiceModel.OperationContractAttribute(Action="http://namespaces.kutulu.org/wcf/proxyless/IProxyless/GetConfigurationElement", ReplyAction="http://namespaces.kutulu.org/wcf/proxyless/IProxyless/GetConfigurationElementResp" +
        "onse")]
    string GetConfigurationElement(string key);
        
    [System.ServiceModel.OperationContractAttribute(Action="http://namespaces.kutulu.org/wcf/proxyless/IProxyless/ProcessData", ReplyAction="http://namespaces.kutulu.org/wcf/proxyless/IProxyless/ProcessDataResponse")]
    proxylessclient.ProxylessService.Result[] ProcessData();
}
We also get one other interface, for each service contract, that looks somewhat pointless. In fact, this interface is the key to the entire proxyless client process, and we'll get back to it a whole lot later on. For now, here's its definition:
public interface IProxylessChannel : proxylessclient.ProxylessService.IProxyless, System.ServiceModel.IClientChannel {
}
Client Proxy Class

The final element of our Reference.cs is, of course, the proxy class itself. If you look at it, though, there's almost nothing to it. The class inherits from ClientBase<TChannel> and implements our service contract. It provides a few useful constructors that let us override the behavior and endpoints, which helps eliminate one of our initial concerns: the fluid nature of the endpoint addresses in an internal service. ClientBase's generic TChannel argument is also our service contract, and causes the base class to expose a Channel property of type TChannel. (All this talk of channels is key here, but I'll save the explanation for the next post.) From there, the proxy just forwards the interface calls through to the Channel property:
public partial class ProxylessClient : System.ServiceModel.ClientBase<proxylessclient.ProxylessService.IProxyless>, proxylessclient.ProxylessService.IProxyless {       
    // Constructors omitted for brevity.

    public bool IsVersionOk(string version) {
        return base.Channel.IsVersionOk(version);
    }
        
    public string GetConfigurationElement(string key) {
        return base.Channel.GetConfigurationElement(key);
    }
        
    public proxylessclient.ProxylessService.Result[] ProcessData() {
        return base.Channel.ProcessData();
    }
}

What Is ClientBase

The next obvious question, therefore, is what's going on in that ClientBase object to make ths actually work?  If you're curious about all the morbid details, the Reference Source for WCF is a good place to start, but here's the basics. The Channel property exposed by the ClientBase type is an implementation of a special interface called IClientChannel. Implementations of these classes are created for us by the classes in the System.ServiceModel namespace, with the public-facing entry point being the ChannelFactory class. If you follow the chain of factory classes to its ultimate end, what you get is a transparent proxy object that uses reflection to generate method bodies for each method on our interface. All of these calls do the same thing:
  • Extract whatever parameters were passed and serialize them to the appropriate format
  • Look up the method name that was called and serialize a message by that name.
  • Invoke an HTTP or TCP or whatever client object to send the message.
  • Deserialize the resulting information into return values.
The actually process is infinitely more complex and detailed than this, of course. The proxy class handles the configuration, the various types of transports, security, exceptions and faults, and a host of other things, all so we don't have to worry about it.

The key point here, though, is that the ChannelFactory class that ClientBase uses to get this proxy is a well-documented, publicly available class. There is nothing stopping us from skipping the middle-man, so to speak, and going right to ChannelFactory ourselves. But I'm getting ahead of myself here; that's for our next post.

List Type Mangling

One interesting thing to notice in our previous code is that the service contract exposes a method that returns a List<Result>. However, our proxy class generates code which expects a Result[]! The reason this happens is because our proxy is generated from metadata in WSDL/XSD format, which allows us to be used by any language or environment that supports SOAP. But there is no SOAP mechanism to represent a .NET List<> or other sequence type; instead, the WSDL just includes the following XSD snippet, which is the standard way to implement an array when serializing to XML:
<xs:complexType name="ArrayOfResult">
    <xs:sequence>
      <xs:element minOccurs="0" maxOccurs="unbounded" name="Result" nillable="true" type="tns:Result" />
    </xs:sequence>
  </xs:complexType>
Since Visual Studio no longer knows what kind of sequence object we really used in our contract, it defaults to the lowest common denominator: an array. Fortunately, there's a quick way to fix this, through the Advanced properties of the Service Reference dialog:
This way, for example, we can generate ObservableCollection properties for WPF, where they are ridiculously useful, or List properties in other cases.

Now that we've seen what goes in to the client proxy that Visual Studio produces for us, we can start to pick it apart and see what bits and pieces we actually need to call our services. As it turns out (and as we already mentioned), the answer is "none of it". Next time, we'll see how to convert our WCF client application into a true proxy-free client.

0 comments: