Introduction
This article deals with replacing the auto generated proxy class in WCF with a generic class such that if the interface of the service contract is known, there is no need of ever generating a proxy class, which ends up improving developer productivity.
Audience
It is assumed that the audience is aware of WCF, operation contracts, data contracts, WCF Channel, Lambda Expressions, and Generics to understand the code. If however you just want to use the code, basic knowledge of C# is required.
Background
It is only in a dream that we can have all our interfaces closed before development phase. In practice, interfaces change because the method signature must change, or a new requirement comes in, or some other unanticipated task pops up.
The proxy is usually generated after defining the interface, and it is required that all associated code such as implementers of the interface compile. There are multiple instances where some interface changes need to be commented to get the proxy and then regenerate the proxy at least three times. This problem is compounded as the number of implementers of the interface increases.
Using the proxy also generates equivalent classes of the data contract for which adapters need to be written in some extreme programming requirements.
Solution
These problems can be eliminated by generation of a proxy. To achieve this, two technologies are used:
- Generics - This helps us to specify a type at runtime
- Lambda Expressions - This helps us to specify a code block (name of the operation contract method) to be passed to a function (to execute the code)
Code
This section jumps straight into the code. The first section describes the code of the service and how it will be used. This is followed by code of the proxy helper and how to use it. In this article, the code has been written in Visual studio 2010 using the .NET 3.5 framework. In case the client can only be created in .NET 2.0, the client code may be written using anonymous delegates.
Code Organization
WCFInterface
- This class contains the interface of the service contract along with the data contracts. In a regular WCF project, these are part of the service implementation, but in this scenario, they have been separated. This project will be referenced by the client.
- WCFService1 - This is the implementation of the service contract by a class.
- WCFProxy - This is the proxy helper class whose job is to replace the proxy.
- WCFClient - This is the WCF client which uses the proxy.
It should be noted that channel definition on both the server and the client remains unchanged and will be picked from the .config file.
The Service
The service in this code as visible is the default WCF service generated by the template. The only change done to the code is separation of the service implementation from the service contract in a separate project. The WCFInterface project is referred to by both the client and WCFService1.
Calling Sync
This section describes the code to call the WCF service in a synchronous call.
The proxy helper
This is a delegate which will be used by the client to specify the method name on the interface. This delegate says tell us the name of the code of type T
.
public delegate void UseServiceDelegate<T>(T proxy);
This is a simple method. It creates a channel using the channel factory for the specified WCF end point and opens the channel. It then calls the code block - which is the method on type T
as told by UseServiceDelegate
.
public void Use(UseServiceDelegate<T> codeBlock, string WCFEndPoint)
{
try
{
this.proxy = GetChannelFactory(WCFEndPoint).CreateChannel() as IClientChannel;
if (this.proxy != null)
{
this.proxy.Open();
codeBlock((T)this.proxy);
this.proxy.Close();
}
}
catch (CommunicationException communicationException)
....
The client
This is the client to the above code. We tell the LamdaProxyHelper
that we want a proxy for type IService1
and then call the Use
method. In the Lambda Expression, we call the method GetData
and return the value in the variable value
(Lambda Expressions allow us to access the local variables).
new LamdaProxyHelper<IService1>().Use(serviceProxy =>
{
value = serviceProxy.GetData(7);
}, "WCFEndPoint");
I would suggest that at this point of time, you should try this code and understand this completely before moving to the next piece which is calling async.
Calling Async
Proxy helper
This code follows a similar pattern; here, the delegate tells that it will be sending an additional parameter while executing. This is the object obj
. This is sent because it helps during the async execution to pass additional parameters such as the ID of the request so as to tie up the async response or the object on which the async method has to be executed or anything else.
public delegate void UseServiceDelegateWithAsyncReturn<T>(T proxy, object obj);
The proxy in this case is quite similar, except that now the execution is done on a new thread to provide the async pattern.
public void UseAsyncWithReturnValue(UseServiceDelegateWithAsyncReturn<T> codeBlock,
string WCFEndPoint, object obj)
{
try
{
this.proxy = GetChannelFactory(WCFEndPoint).CreateChannel() as IClientChannel;
if (this.proxy != null)
{
this.codeBlockWithAsyncReturn = codeBlock;
new Thread(() =>
{
codeBlock((T)this.proxy,obj);
this.proxy.Close();
}).Start();
}
}
catch (CommunicationException communicationException)
{...
The client
The client is called on a separate thread, but this time, the client code just doesn't tell the method to be called, it also calls up an async method to pass results: CallBackForReturnValueOfGetData
.
We should note that the same object (Guid
in this case) is passed back. In certain scenarios, we may choose to send the method as the object or any other code. It should be noted that type safety is maintained.
new LamdaProxyHelper<IService1>().UseAsyncWithReturnValue((proxy, obj) =>
{
value = proxy.GetData(9);
CallBackForReturnValueOfGetData(value, (Guid)obj);
}, "WCFEndPoint",Guid.NewGuid());
static void CallBackForReturnValueOfGetData(string returnValue,Guid id)
{
Console.WriteLine("The return value is =" +
returnValue+" which was called on the Guid = " +id);
}
Calling async with no return value
This is a similar proxy helper code in which we exploit the BeginInvoke
method on a delegate. It allows completion of the code on a separate thread and then calls the method implementing the AsyncCallback
delegate.
The proxy helper
In this code, a delegate of type AsyncCallback
via AsyncResults
called by the method closes the proxy and then calls the callBack
method.
public void UseAsync(UseServiceDelegate<T> codeBlock, string WCFEndPoint,
AsyncCallback callBack,object obj)
{
try
{
this.proxy = GetChannelFactory(WCFEndPoint).CreateChannel() as IClientChannel;
if (this.proxy != null)
{
this.proxy.Open();
this.callBack = callBack;
this.codeBlock = codeBlock;
IAsyncResult result = codeBlock.BeginInvoke((T)this.proxy, AsyncResult, obj);
}
The client
The client in this case is rather self explanatory, and is doing similar tasks as before.
new LamdaProxyHelper<IService1>().UseAsync(serviceProxy =>
{
serviceProxy.GetDataUsingDataContract(compositeType);
}, "WCFEndPoint", AsyncResultCallBack, Guid.NewGuid());
GetChannelFactory
This class is a simple method whose job is to provide a channel based on the channel name. It creates a singleton for each channel and keeps them. This gives performance benefits as creation of a channel is expensive.
private ChannelFactory<T> GetChannelFactory(string WCFEndPoint)
{
ChannelFactory<T> channelFactory = null;
if (! channelPool.TryGetValue(WCFEndPoint,out channelFactory))
{
channelFactory = new ChannelFactory<T>(WCFEndPoint);
channelPool.Add(WCFEndPoint, channelFactory);
}
return channelFactory;
}