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, allows you to create a proxy
on the fly.
Example 1-21. The ChannelFactory<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 the endpoint name from the client config file, 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 Channel
Factory<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 Create
Instance()
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 very similar 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
{
static readonly string BaseAddress = "net.pipe://localhost/" + Guid.NewGuid();
static readonly Binding Binding;
static Dictionary<Type,Tuple<ServiceHost,EndpointAddress>> m_Hosts =
new Dictionary<Type,Tuple<ServiceHost,EndpointAddress>>();
static
InProcFactory()
{
NetNamedPipeBinding binding = new NetNamedPipeBinding();
binding.TransactionFlow = true;
Binding = binding;
AppDomain.CurrentDomain.ProcessExit += delegate
{
foreach(Tuple<ServiceHost,EndpointAddress>
record in m_Hosts.Values)
{
record.Item1.Close();
}
};
}
public static I CreateInstance<S,I>() where I : class
where S : I
{
EndpointAddress address = GetAddress<S,I>();
return ChannelFactory<I>.CreateChannel(Binding,address);
}
static EndpointAddress GetAddress<S,I>() where I : class
where S : class,I
{
Tuple<ServiceHost,EndpointAddress> record;
if(m_Hosts.ContainsKey(typeof(S)))
{
hostRecord = m_Hosts[typeof(S)];
}
else
{
ServiceHost host = new ServiceHost(typeof(S));
string address = BaseAddress + Guid.NewGuid();
record = new Tuple<ServiceHost,EndpointAddress>(
host,new EndpointAddress(address));
m_Hosts[typeof(S)] = record;
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 allows you to use
InProcFactory
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 and the endpoint address using a tuple.
When Create
Instance()
is called to create an
instance of a particular type, it looks in the dictionary using a
helper method called GetAddress()
. 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, GetAddress()
programmatically adds an
endpoint to that host, using a new GUID as the unique pipe name.
GetAddress()
stores the new host
and its address in the dictionary. CreateInstance()
then 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.
If you wish to completely approximate the C# programming model
you can wrap the in-proc factory (and thus, all of WCF) with my
helper base class WcfWrapper
, shown
in Example 1-23:
Example 1-23. The WcfWrapper class
public abstract class WcfWrapper<S,I> : IDisposable,ICommunicationObject where I : class where S : class,I { protected I Proxy {get;private set;} protected WcfWrapper() { Proxy = InProcFactory.CreateInstance<S,I>(); } public void Dispose() { Close(); } public void Close() { InProcFactory.CloseProxy(Proxy); } void ICommunicationObject.Close() { (Proxy as ICommunicationObject).Close(); } //Rest of ICommunicationObject }
Using WcfWrapper<S,I>
is simple—derive from it and the contract and implement the
operations on the contract by delegating to the Proxy
property. For example, for this
service definition:
[ServiceContract] interface IMyContract { [OperationContract] string MyMethod(); } class MyService : IMyContract { public string MyMethod() {...} }
This is the matching wrapper class:
class MyClass : WcfWrapper<MyService,IMyContract>,IMyContract
{
public string MyMethod()
{
return Proxy.MyMethod();
}
}
Using the wrapper class is now indistinguishable from regular C# code and yet all the calls are actually WCF calls:
MyClass obj = new MyClass();
string text = obj.MyMethod();
obj.Close();
Appendix A further discusses the profound implications of this programming model.
Get Programming WCF Services, 3rd 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.