Wednesday, March 07, 2012

Proxy-Free WCF: Ditching The Proxy

Last time, we took a peek inside the auto-generated code that makes up a Visual Studio Service Reference. When you strip away all the tooling support and configuration data, what we're left with is a very thin wrapper around a dynamically-generated proxy class, courtesy of the ChannelFactory object.

Today, we'll see how to take advantage of that class directly, and eliminate the need for a Service Reference at all. Along the way, we'll also see how to use this new-found knowledge to help with unit testing and a few other interesting scenarios.

UPDATE: See this blog post for the sample code for this article.

The Channel Model

The key to understanding the WCF proxy, and how to effectively replace it, is an understanding of the WCF channel model. The term channel is tossed around a lot when taking about the .NET service model, as it's a central concept behind WCF. Essentially, a WCF channel is a representation of all of the pieces and elements that go into an end-to-end communication. A full understanding of service channels is beyond the scope of this post, but a good place to start is the Channel Model Overview on MSDN.

"Channels" in WCF parlance are logical elements of the service model that transmit messages from one point to another. WCF services operate over a layered stack of channels, each one responsible for moving data one step along the route from client to server and back. This stack always includes at least two channels: a transport channel (such as HTTP or named pipes) at the bottom, an encoding channel (such as MTOM or text) above it. Depending on the bindings configuration, there could be any number of additional channels (for session management, security, etc). At the client application level, we deal only with the channel at the top of the stack, which we obtain through its associated channel factory.

A channel factory, in general, is an object that is capable of constructing a channel or channel stack and returning it to a client. Custom channels must include a custom channel factory (and the associated server-side channel listener). For our purposes, however, we can use the built-in ChannelFacotry<> object which can produce a channel stack out of any IClientChannel-derived channel. To do this, we'll need three things: the channel type, the address of the service endpoint, and a binding for an appropriate channel stack.

Bindings are how WCF brings together all of the pieces of a channel stack into one coherent unit. They encapsulate everything the service model needs to know about a given communication format. WCF provides a number of standard bindings, such as the WsHttpBinding or NetNamedPipeBinding, that create well-defined channel stacks appropriate for a given remote service. These bindings include properties that identify which optional channels are added to the channel stack upon creation. For example, the WsHttpBinding has a MessageEncoding property that determines if a text or MTOM encoding channel is used, and a Security.Mode property that determines if a secure transport channel (HTTPS) or message security channel is included.

We can provide a binding to the ChannelFactory object in two ways: via configuration, or via code. When we add a service reference to our client application, one of the elements that is provided by the service metadata is a binding configuration for the client to use, which is added into our application's configuration file. The configuration applies to a given type of binding, and is essentially a serialized version of the binding itself, which looks something like this:
<wsHttpBinding>
  <binding name="WSHttpBinding_IProxyless" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" 
           bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="524288" 
           maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
    <reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false"/>
    <security mode="Message">
       <transport clientCredentialType="Windows" proxyCredentialType="None" realm=""/>
       <message clientCredentialType="Windows" negotiateServiceCredential="true" algorithmSuite="Default"/>
    </security>
  </binding>
</wsHttpBinding>
Later on in the configuration file, this specific binding configuration is associated with the endpoint for our service:
<endpoint address="http://localhost:8732/Design_Time_Addresses/ProxylessService/Proxyless/" 
         binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IProxyless" 
         contract="ProxylessService.IProxyless" name="WSHttpBinding_IProxyless" />
With this information in our configuration file, we can simply give the endpoint configuration name to the ChannelFactory constructor, and it will set everything up for us. Alternatively, we could provide both an endpoint address and a binding in code, and supply those to the constructor. We'll see examples of both in a minute, but first we have to take care of the third element: the channel type we want to create.

Shared Contracts And Channels

As you may remember from our last post, besides defining the client proxy class itself, the auto-generated code for the service also defines all the supporting types needed by our service. This includes the data and service contracts, as well as a channel interface that derives from both our service contract and from IClientChannel.

If we want to eliminate the client proxy, we'll need some other way to get these definitions in our client. Fortunately, this is easy to do. We just need to refactor our service project into two parts: a shared service library, and the actual service implementation. To do this, we'll pull out our DataContract classes and ServiceContract interfaces into their own project, then reference it in our service (and, ultimately, client) projects:


In addition, we need to add our channel interface, since ChannelFactory can only create proxy classes for interfaces derived from IChannel. This is as straighforward as duplicating the definition from the service reference in our own library:
using System;
using System.ServiceModel;

namespace KutuluWare.ProxylessServiceLibrary
{
    interface IProxylessChannel : IProxyless, IClientChannel
    {
    }
}

Injecting Channel Factories

Now we're ready to create an actual channel factory, and from there, a service channel. But remember, one of our goals was to make the code that uses these services more unit-test-friendly. When unit testing our client application, we'd really like the ability to mock up a channel to use in place of actual service calls. The Factory pattern actually makes this very easy, when used in conjunction with a Dependency Injection pattern. Instead of creating an actual ChannelFactory in our client code, we'll use a dependency injection technique to supply one.

There are a number of ways to handle DI, but for simplicity I'll just roll my own quick and dirty DI container. (In practice I'd probably use Unity for this.) Our container will only know how to create channel factory instances. For our first pass, we'll assume that our client configuration file still contains our binding configurations, named after the service contract:
public static T CreateChannel<T>()
    where T : IClientChannel
{
    // For a channel named IFooChannel, we expect an endpoint named WSHttpBinding_IFoo
    var configName = string.Format("WSHttpBinding_{0}", typeof(T).Name.Substring(0, typeof(T).Name.Length - 7));

    if (ServiceFactory.factories.ContainsKey(typeof(T)))
    {
        // Factory instances must supply an endpoint, so read that from the configuration.
        var factory = ServiceFactory.factories[typeof(T)] as ChannelFactory<T>;
        return factory.CreateChannel();
    }
    else
    {
        var factory = new ChannelFactory<T>(configName);
        ServiceFactory.factories.Add(typeof(T), factory);
        return factory.CreateChannel();
    }
}
As you can see, getting a proxy class is a two-step process. First, we create a properly configured factory tied to our channel object. Here we're passing in the name of an endpoint configuration from our client's configuration file. The ChannelFactory object will use this to find both the endpoint address and the correct binding information. Once the factory is created, we can call the CreateChannel method as often as needed, and it will generate an instance of the proxy object we need. This object, since it derives from our custom channel interface, will have all of the methods of our service contract, and through its IClientChannel base, is also Disposable.

Simplified Configuration

One of the benefits introduced into version 4 of WCF was a simplified configuration system. For most scenarios, you don't need to include any information in your client configuration. If you want to override the default settings, you can also do so at a per-binding level, which is convenient if you have multiple services with similar configuration. To see how this works, lets take out all of the configuration data for WCF and create our factories entirely in code.

For this version, we'll assume that all of our services are using the wsHttp binding with a secure (https) transport, that requires client credentials. We will also obtain the URL for each channel as it's requested. This is to demonstrate another useful feature of the channel factory: the actual endpoint associated with the channel is independent of its binding information. For example, if you had multiple services implementing the same contract, you could use a single, common factory for all of them, as long as you can supply the URL when needed.
public static T CreateChannel<T>(string endpoint)
    where T : IClientChannel
{
    if (ServiceFactory.factories.ContainsKey(typeof(T)))
    {
        // Factory instances must supply an endpoint, so read that from the configuration.
        var factory = ServiceFactory.factories[typeof(T)] as ChannelFactory<T>;
        return factory.CreateChannel(new EndpointAddress(endpoint));
    }
    else
    {
        // Set up 
        var binding = new WSHttpBinding(SecurityMode.TransportWithMessageCredential);
        binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
        binding.Security.Message.NegotiateServiceCredential = true;
                  
        var factory = new ChannelFactory<T>(binding);
        factory.Credentials.UserName.UserName = GlobalSession.UserName;
        factory.Credentials.UserName.Password = GlobalSession.Password;

        ServiceFactory.factories.Add(typeof(T), factory);
        return factory.CreateChannel(new EndpointAddress(endpoint));
    }
}
After we created a normal WSHttpBinding, we only set the properties that are not defaults. This process works well if you are in control over both server and client. In general, you only need to set properties on the client if you specifically changed them in your server-side configuration. If you aren't sure which settings you need to override, you are probably better off maintaining a configuration block: generate a service reference to obtain the configuration, then delete the reference but keep the configuration settings.

One thing to be aware of here: many of the properties of a ChannelFactory<> become immutable once you have used the factory to create a channel. For example, if you want to connect to the same remote service using difference client credentials, you will need a separate factory instance for each. In our example, trying to set factory.Credentials.UserName.UserName after having called CreateChannel for the first time would throw an exception, at run-time, that the field is read-only.

Custom Binding Information

There is a middle ground between having a per-service configuration on the client, and having none at all. If we include a binding element for the binding type, without a name, it will automatically apply to all bindings of that type. Alternately, we can pass a configuration name to the binding's constructor to read a named binding configuration, independent of any endpoint that may or may not be configured. This way, we can strike a balance between code and configuration that makes sense for our application. For example, the previous code to construct our secured channel might instead look like this:

public static T CreateChannel<T>(string endpoint)
    where T : IClientChannel
{
    if (ServiceFactory.factories.ContainsKey(typeof(T)))
    {
        // Factory instances must supply an endpoint, so read that from the configuration.
        var factory = ServiceFactory.factories[typeof(T)] as ChannelFactory<T>;
        return factory.CreateChannel(new EndpointAddress(endpoint));
    }
    else
    {                
        var factory = new ChannelFactory<T>(new WSHttpBinding("SecuredBinding"););
        factory.Credentials.UserName.UserName = GlobalSession.UserName;
        factory.Credentials.UserName.Password = GlobalSession.Password;

        ServiceFactory.factories.Add(typeof(T), factory);
        return factory.CreateChannel(new EndpointAddress(endpoint));
    }
}
<system.serviceModel>
  <bindings>
    <wsHttpBinding>
      <binding name="SecuredBinding">
        <security mode="TransportWithMessageCredential">
          <transport clientCredentialType="None" proxyCredentialType="None" realm="" />
          <message clientCredentialType="UserName" negotiateServiceCredential="true" algorithmSuite="Default" />
        </security>
      </binding>
    </wsHttpBinding>
  </bindings>
</system.serviceModel>

Using The Factory

With our factory in place, the code on the client side changes very little. Instead of creating a ProxylessClient object, we call ServiceFactory.CreateChannel. The resulting object implements the exact same interfaces, so the rest of our code should work exactly as-is. (Note here that we're eschewing the typical C# using statement because of a seriously idiotic bug in the client channel proxy: calling Dispose() on a client channel that has throw an exception can throw another exception, thus masking whatever real exception caused us to Dispose() in the first place. Why Microsoft hasn't fixed this yet is mind boggling, but until then, they recommend you not use using with your WCF clients).
var client = ServiceFactory.CreateChannel<IProxylessChannel>(url);
try
{
    client.IsVersionOk("1.1");
    MessageBox.Show(client.GetConfigurationElement("test"));
}
catch (Exception ex)
{
    MessageBox.Show(ex.Message);
}
finally
{
    if (client != null)
    {
        if (client.State == CommunicationState.Faulted)
        {
            client.Abort();
        }
        else
        {
            client.Close();
        }
    }
}

Next time, we'll up the ante a bit by extended this example to use an Entity Framework model. We'll see how the EF models are serialized normally via WCF, and how we can expose the model classes in our service library while keeping the entity context safely hidden behind our service.

2 comments:

Gary Woodfine said...

It would be handy if you could post code samples along with your post. Unfortunately as is the normal case of code blogs, the sample code only tells part of the story, and there is usually some other details omitted.

All in all this appears to be one of the better posts regarding this topic but the lack of sample code lets it down.

Michael Edenfield said...

Gary,

Now that I've moved the blog to a hosted domain I'm planning to publish the source code samples as well, I just haven't gotten things quite sorted out yet. Stay tuned :)