WCF技术剖析之三十:一个很有用的WCF调用编程技巧[下篇]
原文:WCF技术剖析之三十:一个很有用的WCF调用编程技巧[下篇]
在《上篇》中,我通过使用Delegate的方式解决了服务调用过程中的异常处理以及对服务代理的关闭。对于《WCF技术剖析(卷1)》的读者,应该会知道在第7章中我通过类似于AOP的方式解决了相似的问题,现在我们来讨论这个解决方案。
通过《服务代理不能得到及时关闭会有什么后果?》的介绍,我们知道了及时关闭服务代理的重要意义,并且给出了正确的编程方式。如果严格按照上面的编程方式,就意味着对于每一个服务调用,都要使用相同的代码进行异常处理和关闭或中断服务代理对象。按照我个人的观点,一个应用程序的每一个角落若充斥着相同的代码片断,这是一种很不好的设计。设计的目的在于实现代码的重用(Reuse),绝非代码的重复(Duplicate)。
所以现在我们的目的是将重复使用的代码进行单独维护,在使用到的地方进行重用。思路是这样:通过一个对象实现对客户端进行服务访问的方法调用的劫持,在该对象的内部实现真正的方法调用、服务代理关闭或中断,以及异常处理。这实际上是一种基于AOP的解决方案,在这里通过自定义真实代理(RealProxy)的方式来实现服务调用的AOP,这也是为何在本章的开始会花如此多的笔墨介绍真实代理和透明代理的一个重要原因。
下图所示的顺序图(Sequence Diagram)揭示了具体实现的原理:在定义的RealProxy(ServiceRealProxy)中实现了服务调用、异常处理和信道关闭或中断。客户端代码进行服务调用完全是通过自定义真实代理ServiceRealProxy的透明代理进行的,所以所有的方法调用都会直接分发给ServiceRealProxy对象。ServiceRealProxy根据当前方法调用的上下文(比如参数、MethodBase等)构建ChannelFactory<T>对象并创建真正的服务代理对象。然后ServiceRealProxy借助创建出来的服务代理进行真正的服务调用,如果服务调用正常完成,则调用Close方法关闭服务代理,如果在调用过程中抛出CommunicationException和TimeoutException这两个异常,则调用Abort方法强行中断服务代理。最后,将服务调用的结果或抛出的异常通过TransparentProxy返回给客户端代码。
本例仅仅是为如何通过AOP进行WCF服务调用提供一种思路,并不是一个完备的解决方法(比如,没有考虑安全认证和客户端凭证的设置;没有考虑到双向通信和回调等),有兴趣的读者可以在此继承上进一步地完善。现在,就一步步地进行演示。
步骤一:创建ChannalFactory<T>的静态工厂:ChannelFactoryCreator
由于服务调用通过服务代理完成,而ChannelFactory<T>是服务代理的创建者,所以在这里先定义一个ChannelFactoryCreator的静态工厂类,通过它来创建或获取ChannelFactory<T>方法。由于ChannelFactory<T>的创建是一件费时的工作,为了提供更好的性能,和ClientBase<T>一样采用了ChannelFactory<T>的缓存机制(《ClientBase<T>中对ChannelFactory<T>的缓存机制》)。不过,这里的缓存机制比ClientBase<T>的实现要简单得多,ClientBase<T>通过终结点配置名称、终结点地址和回调对象三者进行缓存,这里仅仅是通过终结点配置名称进行ChannelFactory<T>的缓存,因为我们假设客户端完全使用配置的终结点进行服务调用(这也是我们推荐的使用方式)。下面是整个ChannelFactory<T>的静态工厂类的定义:
- 1: using System;
- 2: using System.Collections;
- 3: using System.ServiceModel;
- 4: namespace Artech.ServiceProxyFactory
- 5: {
- 6: internal static class ChannelFactoryCreator
- 7: {
- 8: private static Hashtable channelFactories = new Hashtable();
- 9:
- 10: public static ChannelFactory<T> Create<T>(string endpointName)
- 11: {
- 12: if (string.IsNullOrEmpty(endpointName))
- 13: {
- 14: throw new ArgumentNullException("endpointName");
- 15: }
- 16:
- 17: ChannelFactory<T> channelFactory = null;
- 18:
- 19: if(channelFactories.ContainsKey(endpointName))
- 20: {
- 21: channelFactory = channelFactories[endpointName] as ChannelFactory<T>;
- 22: }
- 23:
- 24: if (channelFactory == null)
- 25: {
- 26: channelFactory = new ChannelFactory<T>(endpointName);
- 27: lock (channelFactories.SyncRoot)
- 28: {
- 29: channelFactories[endpointName] = channelFactory;
- 30: }
- 31: }
- 32:
- 33: return channelFactory;
- 34: }
- 35: }
- 36: }
ChannelFactoryCreator中通过一个Hashtable类型的静态变量保存所有创建出来ChannelFactory<T>集合,Hashtable的Key为表示终结点配置名称的字符串。在Create<T>方法中,先通过传入的终结点配置名称查看缓存中是否存在已经创建好的ChannelFactory<T>对象,如果存在则直接返回,否则创建新的ChannelFactory<T>对象,并在返回之前将其加入缓存。
步骤二:创建自定义RealProxy:ServiceRealProxy<T>
ServiceRealProxy<T>实现了真正的服务调用、异常处理和对服务代理的关闭或中断。ServiceRealProxy<T>的构造函数参数endpointName表示用于服务调用而采用的终结点配置名称。在Invoke中,先借助于ChannelFactoryCreator获得的ChannelFactory<T>创建服务代理对象。然后通过解析参数msg(表示对方法的调用)获得方法调用的参数,并在try控制块中通过反射,传入参数调用服务代理对象相应的方法,从而实现了对服务的调用。对于正常服务调用的结果,将其封装成ReturnMessage对象,并在返回之前调用Close方法关闭服务代理。
在catch控制块中,对抛出的异常进行处理,由于是通过反射方式实现的方法调用,得到的异常类型基本上都是TargetInvocationException,真正进行服务调用的异常被作为捕获异常的内部异常(InnerException)。所以,我们会判断内部异常是否为CommunicationException或TimeoutException,来决定是否通过调用Abort方法强行中断服务代理。捕获的异常被封装成ReturnMessage对象返回。
- 1: using System;
- 2: using System.Runtime.Remoting.Messaging;
- 3: using System.Runtime.Remoting.Proxies;
- 4: using System.ServiceModel;
- 5: namespace Artech.ServiceProxyFactory
- 6: {
- 7: public class ServiceRealProxy<T>: RealProxy
- 8: {
- 9: private string _endpointName;
- 10:
- 11: public ServiceRealProxy(string endpointName):base(typeof(T))
- 12: {
- 13: if (string.IsNullOrEmpty(endpointName))
- 14: {
- 15: throw new ArgumentNullException("endpointName");
- 16: }
- 17: this._endpointName = endpointName;
- 18: }
- 19:
- 20: public override IMessage Invoke(IMessage msg)
- 21: {
- 22: T channel = ChannelFactoryCreator.Create<T>(this._endpointName).CreateChannel();
- 23: IMethodCallMessage methodCall = (IMethodCallMessage)msg;
- 24: IMethodReturnMessage methodReturn = null;
- 25: object[] copiedArgs = Array.CreateInstance(typeof(object), methodCall.Args.Length) as object[];
- 26: methodCall.Args.CopyTo(copiedArgs, 0);
- 27: try
- 28: {
- 29: object returnValue = methodCall.MethodBase.Invoke(channel, copiedArgs);
- 30: methodReturn = new ReturnMessage(returnValue, copiedArgs, copiedArgs.Length, methodCall.LogicalCallContext, methodCall);
- 31: (channel as ICommunicationObject).Close();
- 32: }
- 33: catch (Exception ex)
- 34: {
- 35: if (ex.InnerException is CommunicationException || ex.InnerException is TimeoutException)
- 36: {
- 37: (channel as ICommunicationObject).Abort();
- 38: }
- 39:
- 40: if (ex.InnerException != null)
- 41: {
- 42: methodReturn = new ReturnMessage(ex.InnerException, methodCall);
- 43: }
- 44: else
- 45: {
- 46: methodReturn = new ReturnMessage(ex, methodCall);
- 47: }
- 48: }
- 49:
- 50: return methodReturn;
- 51: }
- 52: }
- 53: }
步骤三:创建自定义服务代理工厂:ServiceProxyFactory
在本案例中,对于最终的客户端代码来说,它利用的是上面创建的自定义真实代理的透明代理间接地进行服务调用。而该透明代理就是客户端的服务代理,为了便于编程,在这里我们定义一个服务代理的静态工厂类:ServiceProxyFactory
- 1: using System;
- 2: namespace Artech.ServiceProxyFactory
- 3: {
- 4: public static class ServiceProxyFactory
- 5: {
- 6: public static T Create<T>(string endpointName)
- 7: {
- 8: if (string.IsNullOrEmpty(endpointName))
- 9: {
- 10: throw new ArgumentNullException("endpointName");
- 11: }
- 12: return (T)(new ServiceRealProxy<T>(endpointName).GetTransparentProxy());
- 13: }
- 14: }
- 15: }
步骤四:通过ServiceProxyFactory创建服务代理进行服务调用
由于重复繁琐的工作已经在ServiceRealProxy<T>中完成,所以客户端进行服务调用的代码将会变得很简洁。为了验证在每次调用完毕后,是否如我们所愿将信道关闭,我们将ServiceProxyFactory应用到我们熟悉的计算服务的例子(终结点calculatorservice配置的绑定类型为WSHttpBinding)。
- 1: ICalculator calculator = ServiceProxyFactory.Create<ICalculator>("calculatorservice");
- 2: for (int i = 1; i < 2000; i++)
- 3: {
- 4: Console.WriteLine("{3}: x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2), i);
- 5: }
输出结果:
- 1 : x + y = 3 when x = 1 and y = 2
- 2 : x + y = 3 when x = 1 and y = 2
- ......
- 1999: x + y = 3 when x = 1 and y = 2
- 2000: x + y = 3 when x = 1 and y = 2
从输出的结果可以看出,2000次服务调用成功完成,由此可以证明每次服务调用结束后,会话信道都被成功关闭。会话信道的自动关闭或中断还带来一个好处,由于每次使用的是新信道,所以即使上一个服务调用出错,也不会影响后续的调用。下面的例子证明了这一点:
- 1: ICalculator calculator = ServiceProxyFactory.Create<ICalculator>("calculatorservice");
- 2: try
- 3: {
- 4: Console.WriteLine("x / y = {2} when x = {0} and y = {1}",2,0,calculator.Divide(2,0));
- 5: }
- 6: catch(Exception ex)
- 7: {
- 8: Console.WriteLine(ex.Message);
- 9: }
- 10: Console.WriteLine("x + y = {2} when x = {0} and y = {1}",2,0,calculator.Add(2,0));
输出结果:
- The server was unable to process the request due to an internal error. For more information about the error, either turn on IncludeExceptionDetailInFaults (either from ServiceBehaviorAttribute or from the <serviceDebug> configuration behavior) on the server in order to send the exception information back to the client, or turn on tracing as per the Microsoft .NET Framework 3.0 SDK documentation and inspect the server trace logs.
- x + y = 2 when x = 2 and y = 0
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
WCF技术剖析之三十:一个很有用的WCF调用编程技巧[下篇]的更多相关文章
- WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇]
原文:WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇] 在进行基于会话信道的WCF服务调用中,由于受到并发信道数量的限制,我们需要及时的关闭信道:当遇到某些异常,我们需要强行中止(Abor ...
- WCF技术剖析之十五:数据契约代理(DataContractSurrogate)在序列化中的作用
原文:WCF技术剖析之十五:数据契约代理(DataContractSurrogate)在序列化中的作用 [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经> ...
- WCF技术剖析之十四:泛型数据契约和集合数据契约(下篇)
原文:WCF技术剖析之十四:泛型数据契约和集合数据契约(下篇) [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经>为此录制的节目视频(苏州话)]]在.NE ...
- WCF技术剖析之十八:消息契约(Message Contract)和基于消息契约的序列化
原文:WCF技术剖析之十八:消息契约(Message Contract)和基于消息契约的序列化 [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经>为此录制 ...
- WCF技术剖析之十六:数据契约的等效性和版本控制
原文:WCF技术剖析之十六:数据契约的等效性和版本控制 数据契约是对用于交换的数据结构的描述,是数据序列化和反序列化的依据.在一个WCF应用中,客户端和服务端必须通过等效的数据契约方能进行有效的数据交 ...
- WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)
原文:WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer) [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济 ...
- WCF技术剖析之三:如何进行基于非HTTP的IIS服务寄宿
原文:[原创]WCF技术剖析之三:如何进行基于非HTTP的IIS服务寄宿 在上面一篇文章中,我们对不同版本的IIS,以及ASP.NET得的实现机制进行了详细而深入的分析.在介绍IIS7.0的时候,我们 ...
- WCF技术剖析之十九:深度剖析消息编码(Encoding)实现(下篇)
原文:WCF技术剖析之十九:深度剖析消息编码(Encoding)实现(下篇) [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经>为此录制的节目视频(苏州话 ...
- WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理
原文:WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理 在前面一片文章(服务代理不能得到及时关闭会有什么后果?)中,我们谈到及时关闭服务代理(Service Proxy)在一个高并发环境 ...
随机推荐
- Task线程 同时执行多个任务
Task taskTest = Task.Factory.StartNew(() => { Console.WriteLine("); },TaskCreationOptions.No ...
- LightOJ 1033 Generating Palindromes(dp)
LightOJ 1033 Generating Palindromes(dp) 题目链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid= ...
- JavaSE学习总结第21天_IO流3
21.01 转换流出现的原因及格式 由于字节流操作中文不是特别方便,所以,java就提供了转换流. 字符流 = 字节流 + 编码表 21.02 编码表概述和常见编码表 编码表:计算机只能识别二 ...
- Java map取value最大值和最小值
/** * 求Map<K,V>中Value(值)的最小值 * * @param map * @return */ public static Object getMinValue(Map& ...
- Python学习之路——类
类: 类是将抽象的实物进行的划分. 在现实世界中如果我们将: 人类包含:男人.女人.孩子.老人等动物类包含:小猫.小狗.小兔子等 在代码世界中我也可以分类,例如将相同功能的代码放到一起,这就是分类. ...
- python Unable to find vcvarsall.bat 错误
今天遇到了这个方面的问题,目前找到两种办法.一种是换编译器如mingw,另一种是装vc.第一种方法没成功,现在正在等第二种. 第一种: 首先安装MinGW: 把MinGW的路径添加到环境变量path中 ...
- MFC基础,MFC自绘控件学习总结.---转
前言:从这学期开始就一直在学习自绘控件(mfc),目标是做出一款播放器界面,主要是为了打好基础,因为我基础实在是很烂....说说我自己心得体会以及自绘控件的方法吧,算是吐槽吧,说的不对和不全的地方,或 ...
- IT第二十一天 - Collections、ArrayList集合、LinkedList集合、Set集合、HashMap集合、集合的操作注意【修20130828】
NIIT第二十一天 上午 集合 1. 集合Collection存储数据的形式是单个存储的,而Map存储是按照键值对来存储的,键值对:即键+值同时存储的,类似align="center&quo ...
- BZOJ 1617: [Usaco2008 Mar]River Crossing渡河问题
题目 1617: [Usaco2008 Mar]River Crossing渡河问题 Time Limit: 5 Sec Memory Limit: 64 MB Description Farmer ...
- Windbg调试命令详解(2)
转载注明>> [作者:张佩][原文:http://blog.csdn.net/blog_index] 2. 符号与源码 符号与源码是调试过程中的重要因素,它们使得枯燥生硬的调试内容更容易 ...