You can use channels directly to invoke operations on a 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. This can be 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 casting it 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) { proxy.MyMethod( ); }
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. This is as close
as you can get in WCF to the C# new
operator, as these
two lines are equivalent in their coupling to the service type:
IMyContract proxy = InProcFactory.CreateInstance<MyService,IMyContract>( ); IMyContract obj = new MyService( );
In the case of C#, the compiler verifies that the type supports the requested
interface and then, in effect, casts the interface into the variable. In the absence of
compiler support, InProcFactory
requires the interface
type so it will know which interface type to return.
All in-proc calls should use named pipes and should 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 = new EndpointAddress(address);
}
public readonly ServiceHost Host;
public readonly EndpointAddress Address;
}
static readonly Uri BaseAddress = new Uri("net.pipe://localhost/" +
Guid.NewGuid( ).ToString( ));
static readonly Binding Binding;
static Dictionary<Type,HostRecord> m_Hosts = new Dictionary<Type,HostRecord>( );
static
InProcFactory( )
{
NetNamedPipeBinding binding = new NetNamedPipeBinding( );
binding.TransactionFlow = true;
Binding = binding;
AppDomain.CurrentDomain.ProcessExit += delegate
{
foreach(HostRecord hostRecord in
m_Hosts.Values)
{
hostRecord.Host.Close( );
}
};
}
public static I CreateInstance<S,I>( ) where I : class
where S : I
{
HostRecord hostRecord = GetHostRecord<S,I>( );
return ChannelFactory<I>.CreateChannel(Binding,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),Binding,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( );
}
}
InProcFactory
's static constructor is called once
per app domain, allocating in each case a new unique base address using a GUID. This
enables InProcFactory
to be used multiple times on
the same machine, across app domains and processes.
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 CreateInstance( )
should do when it is asked to instantiate a second object
of the same type, like so:
IMyContract proxy1 = InProcFactory.CreateInstance<MyService,IMyContract>( ); IMyContract proxy2 = InProcFactory.CreateInstance<MyService,IMyContract>( );
The solution is for InProcFactory
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( )
. If the dictionary does not already contain
the service type, this helper method creates a host instance for it. If it needs to
create a host, GetHostRecord( )
programmatically adds
an endpoint to that host, using a new GUID as the 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.
Get Programming WCF Services, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.