O'Reilly logo

Programming WCF Services by Juval Lowy

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Working with Channels

You can use channels directly to invoke operations on the service without ever resorting to using a proxy class. The ChannelFactory<T> class (and its supporting types), shown in Example 1-21, enables you to create a proxy on the fly.

Example 1-21. TheChannelFactory<T> class

public class ContractDescription
{
   public Type ContractType
   {get;set;}
   //More members
}

public class ServiceEndpoint
{
   public ServiceEndpoint(ContractDescription contract,Binding binding,
                                                          EndpointAddress address);
   public EndpointAddress Address
   {get;set;}
   public Binding Binding
   {get;set;}
   public ContractDescription Contract
   {get;}
   //More members
}

public abstract class ChannelFactory : ...
{
   public ServiceEndpoint Endpoint
   {get;}
   //More members
}
public class ChannelFactory<T> : ChannelFactory,...
{
   public ChannelFactory(ServiceEndpoint endpoint);
   public ChannelFactory(string configurationName);
   public ChannelFactory(Binding binding,EndpointAddress endpointAddress);
   public static T CreateChannel(Binding binding,EndpointAddress endpointAddress);
   public T CreateChannel( );
   //More Members
}

You need to provide the constructor of ChannelFactory<T> with the endpoint—either the endpoint name from the client config file, or the binding and address objects, or a ServiceEndpoint object. Next, use the CreateChannel( ) method to obtain a reference to the proxy and use its methods. Finally, close the proxy by either casting it to IDisposable and calling the Dispose( ) method or to ICommunicationObject and calling the Close( ) method:

ChannelFactory<IMyContract> factory = new ChannelFactory<IMyContract>( );

IMyContract proxy1 = factory.CreateChannel( );
using(proxy1 as IDisposable)
{
   proxy1.MyMethod( );
}

IMyContract proxy2 = factory.CreateChannel( );
proxy2.MyMethod( );
ICommunicationObject channel = proxy2 as ICommunicationObject;
Debug.Assert(channel != null);
channel.Close( );

You can also use the shorthand static CreateChannel( ) method to create a proxy given a binding and an address, without directly constructing an instance of ChannelFactory<T>:

Binding binding = new NetTcpBinding( );
EndpointAddress address = new EndpointAddress("net.tcp://localhost:8000");

IMyContract proxy = ChannelFactory<IMyContract>.CreateChannel(binding,address);
using(proxy as IDisposable)
{
   proxy1.MyMethod( );
}

The InProcFactory Class

To demonstrate the power of ChannelFactory<T>, consider my static helper class InProcFactory, defined as:

public static class InProcFactory
{
   public static I CreateInstance<S,I>( ) where I : class
                                          where S : I;
   public static void CloseProxy<I>(I instance) where I : class;
   //More members
}

InProcFactory is designed to streamline and automate in-proc hosting. The CreateInstance( ) method takes two generic type parameters: the type of the service S and the type of the supported contract I. CreateInstance( ) constrains S to derive from I. Using InProcFactory is straightforward:

IMyContract proxy = InProcFactory.CreateInstance<MyService,IMyContract>( );

proxy.MyMethod( );

InProcFactory.CloseProxy(proxy);

It literally takes a service class and hoists it up as a WCF service. It is as close as you can get in WCF to the old Win32 call of LoadLibrary( ).

Implementing InProcFactory<T>

All in-proc calls should use named pipes, and should also flow all transactions. You can use programmatic configuration to automate the configurations of both the client and the service, and use ChannelFactory<T> to avoid the need for a proxy. Example 1-22 shows the implementation of InProcFactory with some of the code removed for brevity.

Example 1-22. The InProcFactory class

public static class InProcFactory
{
   struct HostRecord
   {
      public HostRecord(ServiceHost host,string address)
      {
         Host = host;
         Address = address;
      }
      public readonly ServiceHost Host;
      public readonly string Address;
   }
   static readonly Uri BaseAddress = new Uri("net.pipe://localhost/");
   static readonly Binding NamedPipeBinding;
   static Dictionary<Type,HostRecord> m_Hosts = new Dictionary<Type,HostRecord>( );static InProcFactory( )
   {
      NetNamedPipeBinding binding = new NetNamedPipeBinding( );
      binding.TransactionFlow = true;
      NamedPipeBinding = binding;
      AppDomain.CurrentDomain.ProcessExit += delegate
                                             {
                             foreach(KeyValuePair<Type,HostRecord> pair in m_Hosts)
                                                 {
                                                   pair.Value.Host.Close( );
                                                 }
                                              };
   }
   public static I CreateInstance<S,I>( ) where I : class
                                          where S : I
   {
      HostRecord hostRecord = GetHostRecord<S,I>( );
      return ChannelFactory<I>.CreateChannel(NamedPipeBinding,
                                          new EndpointAddress(hostRecord.Address));
   }
   static HostRecord GetHostRecord<S,I>( ) where I : class
                                           where S : I
   {
      HostRecord hostRecord;
      if(m_Hosts.ContainsKey(typeof(S)))
      {
         hostRecord = m_Hosts[typeof(S)];
      }
      else
      {
         ServiceHost host = new ServiceHost(typeof(S), BaseAddress);
         string address = BaseAddress.ToString() + Guid.NewGuid().ToString( );
         hostRecord = new HostRecord(host,address);
         m_Hosts.Add(typeof(S),hostRecord);
         host.AddServiceEndpoint(typeof(I),NamedPipeBinding,address);
         host.Open( );
      }
      return hostRecord;
   }
   public static void CloseProxy<I>(I instance) where I : class
   {
      ICommunicationObject proxy = instance as ICommunicationObject;
      Debug.Assert(proxy != null);
      proxy.Close( );
   }
}

The main challenge facing InProcFactory is that CreateInstance( ) can be called to instantiate services of every type. For every service type, there should be a single matching host (an instance of ServiceHost). Allocating a host instance for each call is not a good idea. The problem is what should CreateInstance( ) do when it is asked to instantiate a second object of the same type:

IMyContract proxy1 = InProcFactory.CreateInstance<MyService,IMyContract>( );
IMyContract proxy2 = InProcFactory.CreateInstance<MyService,IMyContract>( );

The solution is to internally manage a dictionary that maps service types to a particular host instance. When CreateInstance( ) is called to create an instance of a particular type, it looks in the dictionary, using a helper method called GetHostRecord( ), which creates the host only if the dictionary does not already contain the service type. If it needs to create a host, GetHostRecord( ) programmatically adds to that host an endpoint, using a new GUID as a unique pipe name. CreateInstance( ) then grabs the address of the endpoint from the host record and uses ChannelFactory<T> to create the proxy. In its static constructor, which is called upon the first use of the class, InProcFactory subscribes to the process exit event, using an anonymous method to close all hosts when the process shuts down. Finally, to help the clients close the proxy, InProcFactory provides the CloseProxy( ) method, which queries the proxy to ICommunicationObject and closes it.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required