WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇]
2015-07-10 17:10
411 查看
原文:WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇]在进行基于会话信道的WCF服务调用中,由于受到并发信道数量的限制,我们需要及时的关闭信道;当遇到某些异常,我们需要强行中止(Abort)信道,相关的原理,可以参考我的文章《服务代理不能得到及时关闭会有什么后果?》。在真正的企业级开发中,正如我们一般不会让开发人员手工控制数据库连接的开启和关闭一样,我们一般也不会让开发人员手工去创建、开启、中止和关闭信道,这些工作是框架应该完成的操作。这篇文章,我们就来介绍如果通过一些编程技巧,让开发者能够无视“信道”的存在,像调用一个普通对象一样进行服务调用。
TimeoutException或者CommunicationException被捕获后,调用Abort方法将信道中止。当程序执行到using的末尾,Dispose方法会进一步调用Close方法对信道进行关闭。
[/code]
[/code]
处于对性能的考虑,避免对ChannelFactory<TChannel>的频繁创建,通过一个字典对象将创建出来的ChannelFactory<TChannel>缓存起来;两个Invoke方法中,服务的调用通过两个Delegate对象(Action<TChannel>和Func<TChannel,TResult>)表示,另一个参数表示终结点的配置名称。那么这时的服务调用就会变得相当简单:
[/code]
[/code]
通过传入终结点配置名称创建ServiceInvoker<TChannel>对象,直接通过调用基类的静态方法实现了两个Invoke方法。
在分层设计中,为每一个层定义的组件创建基类是一个很常见的设计方式。在这里,假设所有的服务代理类型均继承自基类:ServiceProxyBase<TChannel>,泛型类型为服务契约类型。同样通过传入终结点配置名称创建服务代理,并借助于通过Invoker属性表示的ServiceInvoker<TChannel>对象进行服务的调用。ServiceProxyBase<TChannel>定义如下:
[/code]
那么,具体的服务代理类型就可以通过如下的方式定义了:
[/code]
那么现在服务代理的消费者(一般是Presenter层对象),就可以直接实例化服务代理对象,并调用相应的方法(这里的方法与服务契约方法一致)即可,所有关于服务调用的细节均被封装在服务代理中。
[/code]
四、局限
这个解决方案有一个很大的局限:服务方式不能包含ref和out参数,因为这两种类型的参数不能作为匿名方法的参数。
一、正常的服务调用方式
如果通过ChannelFactory<TChannel>创建用于服务调用的代理,下面的代码片段描述了客户端典型的服务调用形式:将服务调用在基于代理对象的using块中,并通过try/catch进一步对服务调用操作进行异常处理。当[code]classProgram
{
staticvoidMain(string[]args)
{
using(ChannelFactory<ICalculator>channelFactory=newChannelFactory<ICalculator>("calculatorservice"))
{
ICalculatorcalculator=channelFactory.CreateChannel();
using(calculatorasIDisposable)
{
try
{
Console.WriteLine("x+y={2}whenx={0}andy={1}",1,2,calculator.Add(1,2));
}
catch(TimeoutException)
{
(calculatorasICommunicationObject).Abort();
throw;
}
catch(CommunicationException)
{
(calculatorasICommunicationObject).Abort();
throw;
}
}
}
Console.Read();
}
}
[/code]
二、借助通过Delegate实现异常处理和服务代理的关闭
虽然上面的编程方式是正确的服务调用方式,但是在真正的应用中,如果在每处进行服务调用的地方都采用上面的方式,在我看来是不能容忍的。这不但会让你的程序显得臃肿不堪,而且带来非常多重复的代码,此外频繁创建ChannelFactory<TChannel>对性能也会有影响。我们可以通过一些公共个方法实现对重复代码(ChannelFactory<TChannel>的创建,服务调用的创建、中止和关闭,以及异常处理)。为此我创建了如下一个ServiceInvoker类型,通过两个重载的Invoke方法实现对目标服务的调用。
[code]1:usingSystem;
2:usingSystem.Collections.Generic;
3:usingSystem.ServiceModel;
4:namespaceArtech.Lib
5:{
6:publicclassServiceInvoker
7:{
8:privatestaticDictionary<string,ChannelFactory>channelFactories=newDictionary<string,ChannelFactory>();
9:privatestaticobjectsyncHelper=newobject();
10:
11:privatestaticChannelFactory<TChannel>GetChannelFactory<TChannel>(stringendpointConfigurationName)
12:{
13:ChannelFactory<TChannel>channelFactory=null;
14:if(channelFactories.ContainsKey(endpointConfigurationName))
15:{
16:channelFactory=channelFactories[endpointConfigurationName]asChannelFactory<TChannel>;
17:}
18:
19:if(null==channelFactory)
20:{
21:channelFactory=newChannelFactory<TChannel>(endpointConfigurationName);
22:lock(syncHelper)
23:{
24:channelFactories[endpointConfigurationName]=channelFactory;
25:}
26:}
27:returnchannelFactory;
28:}
29:
30:publicstaticvoidInvoke<TChannel>(Action<TChannel>action,TChannelproxy)
31:{
32:ICommunicationObjectchannel=proxyasICommunicationObject;
33:if(null==channel)
34:{
35:thrownewArgumentException("TheproxyisnotavalidchannelimplementingtheICommunicationObjectinterface","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:publicstaticTResultInvoke<TChannel,TResult>(Func<TChannel,TResult>function,TChannelproxy)
58:{
59:ICommunicationObjectchannel=proxyasICommunicationObject;
60:if(null==channel)
61:{
62:thrownewArgumentException("TheproxyisnotavalidchannelimplementingtheICommunicationObjectinterface","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:publicstaticvoidInvoke<TChannel>(Action<TChannel>action,stringendpointConfigurationName)
85:{
86:Guard.ArgumentNotNullOrEmpty(endpointConfigurationName,"endpointConfigurationName");
87:Invoke<TChannel>(action,GetChannelFactory<TChannel>(endpointConfigurationName).CreateChannel());
88:}
89:
90:publicstaticTResultInvoke<TChannel,TResult>(Func<TChannel,TResult>function,stringendpointConfigurationName)
91:{
92:Guard.ArgumentNotNullOrEmpty(endpointConfigurationName,"endpointConfigurationName");
93:returnInvoke<TChannel,TResult>(function,GetChannelFactory<TChannel>(endpointConfigurationName).CreateChannel());
94:}
95:}
96:}
[/code]
处于对性能的考虑,避免对ChannelFactory<TChannel>的频繁创建,通过一个字典对象将创建出来的ChannelFactory<TChannel>缓存起来;两个Invoke方法中,服务的调用通过两个Delegate对象(Action<TChannel>和Func<TChannel,TResult>)表示,另一个参数表示终结点的配置名称。那么这时的服务调用就会变得相当简单:
[code]1:usingSystem;
2:usingArtech.Lib;
3:usingArtech.WcfServices.Contracts;
4:namespaceArtech.WcfServices.Clients
5:{
6:classProgram
7:{
8:staticvoidMain(string[]args)
9:{
10:intresult=ServiceInvoker.Invoke<ICalculator,int>(calculator=>calculator.Add(1,2),"calculatorservice");
11:Console.WriteLine("x+y={2}whenx={0}andy={1}",1,2,result);
12:Console.Read();
13:}
14:}
15:}
[/code]
三、对ServiceInvoker的改进
实际上,为了对服务调用实现细节进行进一步的封装,一般地我们可以将其定义在一个独立的层中,比如服务代理层(这里的层不一定像数据访问层、业务逻辑层一样需要一个明显的界限,这里可能就是一个单独的类型而已)。在这种情况下,我们可以上面的ServiceInvoker方法进行一定的改造,使之更加符合这种分层的场景。上面我们调用静态方法的形式进行服务的调用,现在我们需要的是:实例化服务代理对象,并调用相应的方法。为此,我创建了一个泛型的ServiceInvoker<TChannel>类型,该类型继承自上述的ServiceInvoker,泛型类型表示服务契约类型。ServiceInvoker<TChannel>定义如下:
[code]1:usingSystem;
2:namespaceArtech.Lib
3:{
4:publicclassServiceInvoker<TChannel>:ServiceInvoker
5:{
6:publicstringEndpointConfigurationName
7:{get;privateset;}
8:
9:publicServiceInvoker(stringendpointConfigurationName)
10:{
11:Guard.ArgumentNotNullOrEmpty(endpointConfigurationName,"endpointConfigurationName");
12:this.EndpointConfigurationName=endpointConfigurationName;
13:}
14:
15:publicvoidInvoke(Action<TChannel>action)
16:{
17:Invoke<TChannel>(action,this.EndpointConfigurationName);
18:}
19:
20:publicTResultInvoke<TResult>(Func<TChannel,TResult>function)
21:{
22:returnInvoke<TChannel,TResult>(function,this.EndpointConfigurationName);
23:}
24:}
25:}
[/code]
通过传入终结点配置名称创建ServiceInvoker<TChannel>对象,直接通过调用基类的静态方法实现了两个Invoke方法。
在分层设计中,为每一个层定义的组件创建基类是一个很常见的设计方式。在这里,假设所有的服务代理类型均继承自基类:ServiceProxyBase<TChannel>,泛型类型为服务契约类型。同样通过传入终结点配置名称创建服务代理,并借助于通过Invoker属性表示的ServiceInvoker<TChannel>对象进行服务的调用。ServiceProxyBase<TChannel>定义如下:
[code]1:namespaceArtech.Lib
2:{
3:publicclassServiceProxyBase<TChannel>
4:{
5:publicvirtualServiceInvoker<TChannel>Invoker
6:{get;privateset;}
7:
8:publicServiceProxyBase(stringendpointConfigurationName)
9:{
10:Guard.ArgumentNotNullOrEmpty(endpointConfigurationName,"endpointConfigurationName");
11:this.Invoker=newServiceInvoker<TChannel>(endpointConfigurationName);
12:}
13:}
14:}
[/code]
那么,具体的服务代理类型就可以通过如下的方式定义了:
[code]1:usingArtech.Lib;
2:usingArtech.WcfServices.Contracts;
3:namespaceArtech.WcfServices.Clients
4:{
5:publicclassCalculatorProxy:ServiceProxyBase<ICalculator>,ICalculator
6:{
7:publicCalculatorProxy():base(Constants.EndpointConfigurationNames.CalculatorService)
8:{}
9:
10:publicintAdd(intx,inty)
11:{
12:returnthis.Invoker.Invoke<int>(calculator=>calculator.Add(x,y));
13:}
14:}
15:
16:publicclassConstants
17:{
18:publicclassEndpointConfigurationNames
19:{
20:publicconststringCalculatorService="calculatorservice";
21:}
22:}
23:}
[/code]
那么现在服务代理的消费者(一般是Presenter层对象),就可以直接实例化服务代理对象,并调用相应的方法(这里的方法与服务契约方法一致)即可,所有关于服务调用的细节均被封装在服务代理中。
[code]1:usingSystem;
2:usingArtech.Lib;
3:usingArtech.WcfServices.Contracts;
4:namespaceArtech.WcfServices.Clients
5:{
6:classProgram
7:{
8:staticvoidMain(string[]args)
9:{
10:CalculatorProxycalculatorProxy=newCalculatorProxy();
11:intresult=calculatorProxy.Add(1,2);
12:Console.WriteLine("x+y={2}whenx={0}andy={1}",1,2,result);
13:Console.Read();
14:}
15:}
16:}
[/code]
四、局限
这个解决方案有一个很大的局限:服务方式不能包含ref和out参数,因为这两种类型的参数不能作为匿名方法的参数。
相关文章推荐
- Java集合系列之HashMap源码分析
- C++内存管理
- java泛型
- Java集合之HashMap源码分析
- java学习之旅51--面向对象_24_内部类详解
- mutt+msmtp 邮件客户端配置
- 【leetcode-49】Anagrams(java)
- .net4.5使用async和await异步编程实例
- 说说Java生态圈的那些事儿
- C# Lambda表达式 基本知识 (引用网上总结)
- C# 系统日志处理-生产者与消费者模式
- java基础知识笔记
- java内功之jvm加载双亲模式
- Drools学习笔记-01-在eclipse indgo集成Drools5.5
- eclipse不编译生成.class的解决办法
- android-Eclipse,32位的工程导入64位Eclipse里,中文乱码
- 代码实现分析mpeg-2文件
- C++学习笔记27,虚函数作品
- Java工具方法hutool 使用备注
- Python实现分割文件及合并文件的方法