调用 WCF 服务:比生成代理更好的方法






4.22/5 (13投票s)
如何使用泛型和 Lambda 表达式创建通用的 WCF 代理。
引言
本文旨在用一个泛型类替换 WCF 中自动生成的代理类,这样如果知道了服务契约的接口,就无需生成代理类,从而提高了开发人员的生产力。
目标读者
假设读者熟悉 WCF、操作契约、数据契约、WCF Channel、Lambda 表达式和泛型,以便理解代码。但是,如果您只想使用代码,则需要具备 C# 的基本知识。
背景
只有在梦想中,我们才能在开发阶段之前就完成所有接口。在实践中,接口会发生变化,因为方法签名必须改变,或者出现新的需求,或者出现其他不可预见的任务。
代理通常在定义接口后生成,并且需要所有相关的代码(如接口的实现者)进行编译。在某些情况下,为了获取代理,需要注释掉一些接口更改,然后至少三次重新生成代理。随着接口实现者数量的增加,这个问题会变得更加复杂。
使用代理还会生成等效的数据契约类,在某些极端编程需求下需要为这些类编写适配器。
解决方案
这些问题可以通过生成代理来消除。为了实现这一点,使用了两种技术:
- 泛型 - 这有助于我们在运行时指定一个类型。
- Lambda 表达式 - 这有助于我们将一个代码块(操作契约方法的名称)传递给一个函数(以执行代码)。
代码
本节直接进入代码。第一节描述了服务的代码以及如何使用它。接下来是代理辅助类的代码以及如何使用它。在本文中,代码使用 .NET 3.5 框架在 Visual Studio 2010 中编写。如果客户端只能在 .NET 2.0 中创建,则客户端代码可以使用匿名委托编写。
代码组织
WCFInterface
- 此类包含服务契约的接口以及数据契约。在常规的 WCF 项目中,这些是服务实现的一部分,但在本场景中,它们已被分离。客户端将引用此项目。
- WCFService1 - 这是通过一个类实现的服务的契约。
- WCFProxy - 这是代理辅助类,其工作是替换代理。
- WCFClient - 这是使用代理的 WCF 客户端。
应该注意的是,服务器和客户端上的通道定义保持不变,将从.config文件中读取。
服务
代码中可见的服务是模板生成的默认 WCF 服务。对代码唯一进行的更改是将服务实现与服务契约分离到单独的项目中。WCFInterface 项目被客户端和 WCFService1 引用。
调用同步
本节介绍以同步方式调用 WCF 服务的代码。
代理辅助
这是一个委托,将由客户端用来指定接口上的方法名称。此委托表示“告诉我们类型为 T
的代码的名称”。
/// <summary>
/// This delegate describes the method on the interface to be called.
/// </summary>
/// <typeparam name="T">This is the type of the interface</typeparam>
/// <param name="proxy">This is the method.</param>
public delegate void UseServiceDelegate<T>(T proxy);
这是一个简单的方法。它使用通道工厂为指定 WCF 端点创建一个通道并打开它。然后,它调用代码块——即由 UseServiceDelegate
指定的类型 T
上的方法。
/// <summary>
/// Invokes the method on the WCF interface with the given end point to
/// create a channel
/// Usage
/// new ProxyHelper<InterfaceName>().Use(serviceProxy =>
/// {
/// value = serviceProxy.MethodName(params....);
/// }, "WCFEndPoint");
/// </summary>
/// <param name="codeBlock">The WCF interface method of interface of type T
/// </param>
/// <param name="WCFEndPoint">The end point.</param>
public void Use(UseServiceDelegate<T> codeBlock, string WCFEndPoint)
{
try
{
//Create an instance of proxy
this.proxy = GetChannelFactory(WCFEndPoint).CreateChannel() as IClientChannel;
if (this.proxy != null)
{
//open the proxy
this.proxy.Open();
//Call the method
codeBlock((T)this.proxy);
this.proxy.Close();
}
}
catch (CommunicationException communicationException)
....
客户端
这是上述代码的客户端。我们告诉 LamdaProxyHelper
我们想要 IService1
类型的代理,然后调用 Use
方法。在 Lambda 表达式中,我们调用 GetData
方法并将值存储在变量 value
中(Lambda 表达式允许我们访问局部变量)。
//using a proxy for the interface IService1 call the method GetData
//call a sync method
new LamdaProxyHelper<IService1>().Use(serviceProxy =>
{
//save the return value in value
value = serviceProxy.GetData(7);
}, "WCFEndPoint");//use the end point name WCFEndPoint for this proxy
我建议您此时尝试此代码并完全理解它,然后再继续下一部分,即异步调用。
调用异步
代理辅助
此代码遵循类似模式,在此处,委托表示它将在执行时发送一个额外的参数。这是对象 obj
。发送此对象是因为它在异步执行期间有助于传递额外参数,例如请求的 ID,以便将异步响应或异步方法必须执行的对象或任何其他内容关联起来。
/// <summary>
/// This delegate describes the method on the interface to be called.
/// </summary>
/// <typeparam name="T">This is the type of the interface</typeparam>
/// <param name="proxy">This is the method.</param>
/// <param name="obj">This is any object which may be used to identify
/// execution instance.</param>
public delegate void UseServiceDelegateWithAsyncReturn<T>(T proxy, object obj);
在这种情况下,代理非常相似,只是现在执行是在新线程上进行的,以提供异步模式。
/// <summary>
/// This method calls the WCF Service in a new thread. The calling of other method
/// for result is the
/// responcibility of the client code
/// </summary>
/// <param name="codeBlock">The method on the WCF service to be called</param>
/// <param name="WCFEndPoint">This is the WCF end point</param>
/// <param name="obj">This is any object which may help in exeution of the async
/// parameters</param>
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(() =>
{
//Create a new thread and on the new thread call the methos
codeBlock((T)this.proxy,obj);
this.proxy.Close();
}).Start();
}
}
catch (CommunicationException communicationException)
{...
客户端
客户端在一个单独的线程上调用,但这次,客户端代码不仅告诉要调用的方法,还调用一个异步方法来传递结果:CallBackForReturnValueOfGetData
。
我们应该注意到,返回的对象是相同的(在本例中为 Guid
)。在某些情况下,我们可以选择将方法作为对象或任何其他代码发送。应该注意的是,类型安全得到了维护。
new LamdaProxyHelper<IService1>().UseAsyncWithReturnValue((proxy, obj) =>
{
//save the return value in value
value = proxy.GetData(9);
CallBackForReturnValueOfGetData(value, (Guid)obj);
}, "WCFEndPoint",Guid.NewGuid());
//use the end point name WCFEndPoint for this proxy
/// <summary>
/// This is a type safe return method for getting the return value on a
/// seperate thread. This is called when the method is actully invoked.
/// </summary>
/// <param name="returnValue">This is the return value</param>
static void CallBackForReturnValueOfGetData(string returnValue,Guid id)
{
Console.WriteLine("The return value is =" +
returnValue+" which was called on the Guid = " +id);
}
调用没有返回值的异步
这是类似的代理辅助代码,其中我们利用了委托上的 BeginInvoke
方法。它允许代码在新线程上完成,然后调用实现 AsyncCallback
委托的方法。
代理辅助
在此代码中,通过 AsyncResults
调用类型为 AsyncCallback
的委托的方法会关闭代理,然后调用 callBack
方法。
/// <summary>
/// Invokes the method on the WCF interface with the given end point to
/// create a channel
/// Usage
/// new ProxyHelper<InterfaceName>().Use(serviceProxy =>
/// {
/// value = serviceProxy.MethodName(params....);
/// }, "WCFEndPoint",callBackMethodName,id);
/// </summary>
/// <param name="codeBlock">The WCF interface method of interface of type T
/// </param>
/// <param name="WCFEndPoint">The end point.</param>
/// <param name="obj">The object instance used to identify in callback</param>
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);
}
客户端
在这种情况下,客户端具有自解释性,并且正在执行与之前类似的任务。
new LamdaProxyHelper<IService1>().UseAsync(serviceProxy =>
{
serviceProxy.GetDataUsingDataContract(compositeType);
}, "WCFEndPoint", AsyncResultCallBack, Guid.NewGuid());
GetChannelFactory
此类是一个简单的方法,其工作是根据通道名称提供通道。它为每个通道创建一个单例并保留它们。这可以带来性能优势,因为创建通道代价高昂。
/// </summary>
/// <param name="WCFEndPoint">This is the end point</param>
/// <returns>Return List of all the invoked proxies</returns>
private ChannelFactory<T> GetChannelFactory(string WCFEndPoint)
{
ChannelFactory<T> channelFactory = null;
//Check if the channel factory exists
//Create and return an instance of the channel
if (! channelPool.TryGetValue(WCFEndPoint,out channelFactory))
{
channelFactory = new ChannelFactory<T>(WCFEndPoint);
channelPool.Add(WCFEndPoint, channelFactory);
}
return channelFactory;
}