Welcome

首页 / 软件开发 / WCF / WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇]

WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇]2012-12-07 cnblogs 蒋金楠在进行基于会话信道的WCF服务调用中,由于受到并发信道数量的限制,我们需要及时的关闭信道;当遇到某些异常,我们需要强行中止(Abort)信道,相关的原理,可以参考我的文章《服务代理不能得到及时关闭会有什么后果?》。在真正的企业级开发中,正如我们一般不会让开发人员手工控制数据库连接的开启和关闭一样,我们一般也不会让开发人员手工去创建、开启、中止和关闭信道,这些工作是框架应该完成的操作。这篇文章,我们就来介绍如果通过一些编程技巧,让开发者能够无视“信道”的存在,像调用一个普通对象一样进行服务调用。

一、正常的服务调用方式

如果通过ChannelFactory<TChannel>创建用于服务调用的代理,下面的代码片段描述了客户端典型的服务调用形式:将服务调用在基于代理对象的using块中,并通过try/catch进一步对服务调用操作进行异常处理。当TimeoutException或者CommunicationException被捕获后,调用Abort方法将信道中止。当程序执行到using的末尾,Dispose方法会进一步调用Close方法对信道进行关闭。

class Program
{
static void Main(string[] args)
{
using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorservice"))
{
ICalculator calculator = channelFactory.CreateChannel();
using (calculator as IDisposable)
{
try
{
Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2));
}
catch (TimeoutException)
{
(calculator as ICommunicationObject).Abort();
throw;
}
catch (CommunicationException)
{
(calculator as ICommunicationObject).Abort();
throw;
}
}
}
Console.Read();
}
}
二、借助通过Delegate实现异常处理和服务代理的关闭

虽然上面的编程方式是正确的服务调用方式,但是在真正的应用中,如果在每处进行服务调用的地方都采用上面的方式,在我看来是不能容忍的。这不但会让你的程序显得臃肿不堪,而且带来非常多重复的代码,此外频繁创建ChannelFactory<TChannel>对性能也会有影响。我们可以通过一些公共个方法实现对重复代码(ChannelFactory<TChannel>的创建,服务调用的创建、中止和关闭,以及异常处理)。为此我创建了如下一个ServiceInvoker类型,通过两个重载的Invoke方法实现对目标服务的调用。

 1: using System;
2: using System.Collections.Generic;
3: using System.ServiceModel;
4: namespace Artech.Lib
5: {
6: public class ServiceInvoker
7: {
8: private static Dictionary<string, ChannelFactory> channelFactories = new Dictionary<string, ChannelFactory>();
9: private static object syncHelper = new object();
10:
11: private static ChannelFactory<TChannel> GetChannelFactory<TChannel>(string endpointConfigurationName)
12: {
13: ChannelFactory<TChannel> channelFactory = null;
14: if (channelFactories.ContainsKey(endpointConfigurationName))
15: {
16: channelFactory = channelFactories[endpointConfigurationName] as ChannelFactory<TChannel>;
17: }
18:
19: if (null == channelFactory)
20: {
21: channelFactory = new ChannelFactory<TChannel>(endpointConfigurationName);
22: lock (syncHelper)
23: {
24: channelFactories[endpointConfigurationName] = channelFactory;
25: }
26: }
27: return channelFactory;
28: }
29:
30: public static void Invoke<TChannel>(Action<TChannel> action, TChannel proxy)
31: {
32: ICommunicationObject channel = proxy as ICommunicationObject;
33: if (null == channel)
34: {
35: throw new ArgumentException("The proxy is not a valid channel implementing the ICommunicationObject interface", "proxy");
36: }
37: try
38: {
39: action(proxy);
40: }
41: catch (TimeoutException)
42: {
43: channel.Abort();
44: throw;
45: }
46: catch (CommunicationException)
47: {
48: channel.Abort();
49: throw;
50: }
51: finally
52: {
53: channel.Close();
54: }
55: }
56:
57: public static TResult Invoke<TChannel, TResult>(Func<TChannel, TResult> function, TChannel proxy)
58: {
59: ICommunicationObject channel = proxy as ICommunicationObject;
60: if (null == channel)
61: {
62: throw new ArgumentException("The proxy is not a valid channel implementing the ICommunicationObject interface", "proxy");
63: }
64: try
65: {
66: returnfunction(proxy);
67: }
68: catch (TimeoutException)
69: {
70: channel.Abort();
71: throw;
72: }
73: catch (CommunicationException)
74: {
75: channel.Abort();
76: throw;
77: }
78: finally
79: {
80: channel.Close();
81: }
82: }
83:
84: public static void Invoke<TChannel>(Action<TChannel> action, string endpointConfigurationName)
85: {
86: Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName");
87: Invoke<TChannel>(action, GetChannelFactory<TChannel>(endpointConfigurationName).CreateChannel());
88: }
89:
90: public static TResult Invoke<TChannel, TResult>(Func<TChannel, TResult> function, string endpointConfigurationName)
91: {
92: Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName");
93: return Invoke<TChannel, TResult>(function, GetChannelFactory<TChannel>(endpointConfigurationName).CreateChannel());
94: }
95: }
96: }
处于对性能的考虑,避免对ChannelFactory<TChannel>的频繁创建,通过一个字典对象将创建出来的ChannelFactory<TChannel>缓存起来;两个Invoke方法中,服务的调用通过两个Delegate对象(Action<TChannel>和Func<TChannel, TResult>)表示,另一个参数表示终结点的配置名称。那么这时的服务调用就会变得相当简单:

 1: using System;
2: using Artech.Lib;
3: using Artech.WcfServices.Contracts;
4: namespace Artech.WcfServices.Clients
5: {
6: class Program
7: {
8: static void Main(string[] args)
9: {
10: int result = ServiceInvoker.Invoke<ICalculator, int>(calculator => calculator.Add(1, 2), "calculatorservice");
11: Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, result);
12: Console.Read();
13: }
14: }
15: }