分享
 
 
 

WCF技术剖析之三十:一个很有用的WCF调用编程技巧

王朝学院·作者佚名  2010-01-07
窄屏简体版  字體: |||超大  

在进行基于会话信道的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: return function(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: }

三、对ServiceInvoker的改进

实际上,为了对服务调用实现细节进行进一步的封装,一般地我们可以将其定义在一个独立的层中,比如服务代理层(这里的层不一定像数据访问层、业务逻辑层一样需要一个明显的界限,这里可能就是一个单独的类型而已)。在这种情况下,我们可以上面的ServiceInvoker方法进行一定的改造,使之更加符合这种分层的场景。上面我们调用静态方法的形式进行服务的调用,现在我们需要的是:实例化服务代理对象,并调用相应的方法。为此,我创建了一个泛型的ServiceInvoker<TChannel>类型,该类型继承自上述的ServiceInvoker,泛型类型表示服务契约类型。ServiceInvoker<TChannel>定义如下:

1: using System; 2: namespace Artech.Lib 3: { 4: public class ServiceInvoker<TChannel>:ServiceInvoker 5: { 6: public string EndpointConfigurationName 7: {get; private set;} 8: 9: public ServiceInvoker(string endpointConfigurationName) 10: { 11: Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName"); 12: this.EndpointConfigurationName = endpointConfigurationName; 13: } 14: 15: public void Invoke(Action<TChannel> action) 16: { 17: Invoke<TChannel>(action, this.EndpointConfigurationName); 18: } 19: 20: public TResult Invoke<TResult>(Func<TChannel, TResult> function) 21: { 22: return Invoke<TChannel, TResult>(function, this.EndpointConfigurationName); 23: } 24: } 25: }

通过传入终结点配置名称创建ServiceInvoker<TChannel>对象,直接通过调用基类的静态方法实现了两个Invoke方法。

在分层设计中,为每一个层定义的组件创建基类是一个很常见的设计方式。在这里,假设所有的服务代理类型均继承自基类:ServiceProxyBase<TChannel>,泛型类型为服务契约类型。同样通过传入终结点配置名称创建服务代理,并借助于通过Invoker属性表示的ServiceInvoker<TChannel>对象进行服务的调用。ServiceProxyBase<TChannel>定义如下:

1: namespace Artech.Lib 2: { 3: public class ServiceProxyBase<TChannel> 4: { 5: public virtual ServiceInvoker<TChannel> Invoker 6: { get; private set; } 7: 8: public ServiceProxyBase(string endpointConfigurationName) 9: { 10: Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName"); 11: this.Invoker = new ServiceInvoker<TChannel>(endpointConfigurationName); 12: } 13: } 14: }

那么,具体的服务代理类型就可以通过如下的方式定义了:

1: using Artech.Lib; 2: using Artech.WcfServices.Contracts; 3: namespace Artech.WcfServices.Clients 4: { 5: public class CalculatorProxy : ServiceProxyBase<ICalculator>, ICalculator 6: { 7: public CalculatorProxy():base(Constants.EndpointConfigurationNames.CalculatorService) 8: { } 9: 10: public int Add(int x, int y) 11: { 12: return this.Invoker.Invoke<int>(calculator => calculator.Add(x, y)); 13: } 14: } 15: 16: public class Constants 17: { 18: public class EndpointConfigurationNames 19: { 20: public const string CalculatorService = "calculatorservice"; 21: } 22: } 23: }

那么现在服务代理的消费者(一般是Presenter层对象),就可以直接实例化服务代理对象,并调用相应的方法(这里的方法与服务契约方法一致)即可,所有关于服务调用的细节均被封装在服务代理中。

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: CalculatorProxy calculatorProxy = new CalculatorProxy(); 11: int result = calculatorProxy.Add(1, 2); 12: Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, result); 13: Console.Read(); 14: } 15: } 16: }

四、局限

这个解决方案有一个很大的局限:服务方式不能包含ref和out参数,因为这两种类型的参数不能作为匿名方法的参数。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有