原文:WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇]

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

一、正常的服务调用方式

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

  1. class Program

  1. {

  1. static void Main(string[] args)

  1. {

  1. using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorservice"))

  1. {

  1. ICalculator calculator = channelFactory.CreateChannel();

  1. using (calculator as IDisposable)

  1. {

  1. try

  1. {

  1. Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2));

  1. }

  1. catch (TimeoutException)

  1. {

  1. (calculator as ICommunicationObject).Abort();

  1. throw;

  1. }

  1. catch (CommunicationException)

  1. {

  1. (calculator as ICommunicationObject).Abort();

  1. throw;

  1. }

  1. }

  1. }

  1.  

  1. Console.Read();

  1. }

  1. }

二、借助通过Delegate实现异常处理和服务代理的关闭

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

  1. 1: using System;

  1. 2: using System.Collections.Generic;

  1. 3: using System.ServiceModel;

  1. 4: namespace Artech.Lib

  1. 5: {

  1. 6: public class ServiceInvoker

  1. 7: {

  1. 8: private static Dictionary<string, ChannelFactory> channelFactories = new Dictionary<string, ChannelFactory>();

  1. 9: private static object syncHelper = new object();

  1. 10: 

  1. 11: private static ChannelFactory<TChannel> GetChannelFactory<TChannel>(string endpointConfigurationName)

  1. 12: {

  1. 13: ChannelFactory<TChannel> channelFactory = null;

  1. 14: if (channelFactories.ContainsKey(endpointConfigurationName))

  1. 15: {

  1. 16: channelFactory = channelFactories[endpointConfigurationName] as ChannelFactory<TChannel>;

  1. 17: }

  1. 18: 

  1. 19: if (null == channelFactory)

  1. 20: {

  1. 21: channelFactory = new ChannelFactory<TChannel>(endpointConfigurationName);

  1. 22: lock (syncHelper)

  1. 23: {

  1. 24: channelFactories[endpointConfigurationName] = channelFactory;

  1. 25: }

  1. 26: }

  1. 27: return channelFactory;

  1. 28: }

  1. 29: 

  1. 30: public static void Invoke<TChannel>(Action<TChannel> action, TChannel proxy)

  1. 31: {

  1. 32: ICommunicationObject channel = proxy as ICommunicationObject;

  1. 33: if (null == channel)

  1. 34: {

  1. 35: throw new ArgumentException("The proxy is not a valid channel implementing the ICommunicationObject interface", "proxy");

  1. 36: }

  1. 37: try

  1. 38: {

  1. 39: action(proxy);

  1. 40: }

  1. 41: catch (TimeoutException)

  1. 42: {

  1. 43: channel.Abort();

  1. 44: throw;

  1. 45: }

  1. 46: catch (CommunicationException)

  1. 47: {

  1. 48: channel.Abort();

  1. 49: throw;

  1. 50: }

  1. 51: finally

  1. 52: {

  1. 53: channel.Close();

  1. 54: }

  1. 55: }

  1. 56: 

  1. 57: public static TResult Invoke<TChannel, TResult>(Func<TChannel, TResult> function, TChannel proxy)

  1. 58: {

  1. 59: ICommunicationObject channel = proxy as ICommunicationObject;

  1. 60: if (null == channel)

  1. 61: {

  1. 62: throw new ArgumentException("The proxy is not a valid channel implementing the ICommunicationObject interface", "proxy");

  1. 63: }

  1. 64: try

  1. 65: {

  1. 66: return function(proxy);

  1. 67: }

  1. 68: catch (TimeoutException)

  1. 69: {

  1. 70: channel.Abort();

  1. 71: throw;

  1. 72: }

  1. 73: catch (CommunicationException)

  1. 74: {

  1. 75: channel.Abort();

  1. 76: throw;

  1. 77: }

  1. 78: finally

  1. 79: {

  1. 80: channel.Close();

  1. 81: }

  1. 82: }

  1. 83: 

  1. 84: public static void Invoke<TChannel>(Action<TChannel> action, string endpointConfigurationName)

  1. 85: {

  1. 86: Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName");

  1. 87: Invoke<TChannel>(action, GetChannelFactory<TChannel>(endpointConfigurationName).CreateChannel());

  1. 88: }

  1. 89: 

  1. 90: public static TResult Invoke<TChannel, TResult>(Func<TChannel, TResult> function, string endpointConfigurationName)

  1. 91: {

  1. 92: Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName");

  1. 93: return Invoke<TChannel, TResult>(function, GetChannelFactory<TChannel>(endpointConfigurationName).CreateChannel());

  1. 94: }

  1. 95: }

  1. 96: }

处于对性能的考虑,避免对ChannelFactory<TChannel>的频繁创建,通过一个字典对象将创建出来的ChannelFactory<TChannel>缓存起来;两个Invoke方法中,服务的调用通过两个Delegate对象(Action<TChannel>和Func<TChannel, TResult>)表示,另一个参数表示终结点的配置名称。那么这时的服务调用就会变得相当简单:

  1. 1: using System;

  1. 2: using Artech.Lib;

  1. 3: using Artech.WcfServices.Contracts;

  1. 4: namespace Artech.WcfServices.Clients

  1. 5: {

  1. 6: class Program

  1. 7: {

  1. 8: static void Main(string[] args)

  1. 9: {

  1. 10: int result = ServiceInvoker.Invoke<ICalculator, int>(calculator => calculator.Add(1, 2), "calculatorservice");

  1. 11: Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, result);

  1. 12: Console.Read();

  1. 13: }

  1. 14: }

  1. 15: }

三、对ServiceInvoker的改进

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

  1. 1: using System;

  1. 2: namespace Artech.Lib

  1. 3: {

  1. 4: public class ServiceInvoker<TChannel>:ServiceInvoker

  1. 5: {

  1. 6: public string EndpointConfigurationName

  1. 7: {get; private set;}

  1. 8: 

  1. 9: public ServiceInvoker(string endpointConfigurationName)

  1. 10: {

  1. 11: Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName");

  1. 12: this.EndpointConfigurationName = endpointConfigurationName;

  1. 13: }

  1. 14: 

  1. 15: public void Invoke(Action<TChannel> action)

  1. 16: {

  1. 17: Invoke<TChannel>(action, this.EndpointConfigurationName);

  1. 18: }

  1. 19: 

  1. 20: public TResult Invoke<TResult>(Func<TChannel, TResult> function)

  1. 21: {

  1. 22: return Invoke<TChannel, TResult>(function, this.EndpointConfigurationName);

  1. 23: }

  1. 24: }

  1. 25: }

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

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

  1. 1: namespace Artech.Lib

  1. 2: {

  1. 3: public class ServiceProxyBase<TChannel>

  1. 4: {

  1. 5: public virtual ServiceInvoker<TChannel> Invoker

  1. 6: { get; private set; }

  1. 7: 

  1. 8: public ServiceProxyBase(string endpointConfigurationName)

  1. 9: {

  1. 10: Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName");

  1. 11: this.Invoker = new ServiceInvoker<TChannel>(endpointConfigurationName);

  1. 12: }

  1. 13: }

  1. 14: }

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

  1. 1: using Artech.Lib;

  1. 2: using Artech.WcfServices.Contracts;

  1. 3: namespace Artech.WcfServices.Clients

  1. 4: {

  1. 5: public class CalculatorProxy : ServiceProxyBase<ICalculator>, ICalculator

  1. 6: {

  1. 7: public CalculatorProxy():base(Constants.EndpointConfigurationNames.CalculatorService)

  1. 8: { }

  1. 9: 

  1. 10: public int Add(int x, int y)

  1. 11: {

  1. 12: return this.Invoker.Invoke<int>(calculator => calculator.Add(x, y));

  1. 13: }

  1. 14: }

  1. 15: 

  1. 16: public class Constants

  1. 17: {

  1. 18: public class EndpointConfigurationNames

  1. 19: {

  1. 20: public const string CalculatorService = "calculatorservice";

  1. 21: }

  1. 22: }

  1. 23: }

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

  1. 1: using System;

  1. 2: using Artech.Lib;

  1. 3: using Artech.WcfServices.Contracts;

  1. 4: namespace Artech.WcfServices.Clients

  1. 5: {

  1. 6: class Program

  1. 7: {

  1. 8: static void Main(string[] args)

  1. 9: {

  1. 10: CalculatorProxy calculatorProxy = new CalculatorProxy();

  1. 11: int result = calculatorProxy.Add(1, 2);

  1. 12: Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, result);

  1. 13: Console.Read();

  1. 14: }

  1. 15: }

  1. 16: }

四、局限

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

作者:Artech

出处:http://artech.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇]的更多相关文章

  1. WCF技术剖析之三十:一个很有用的WCF调用编程技巧[下篇]

    原文:WCF技术剖析之三十:一个很有用的WCF调用编程技巧[下篇] 在<上篇>中,我通过使用Delegate的方式解决了服务调用过程中的异常处理以及对服务代理的关闭.对于<WCF技术 ...

  2. WCF技术剖析之十五:数据契约代理(DataContractSurrogate)在序列化中的作用

    原文:WCF技术剖析之十五:数据契约代理(DataContractSurrogate)在序列化中的作用 [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经> ...

  3. WCF技术剖析之十四:泛型数据契约和集合数据契约(下篇)

    原文:WCF技术剖析之十四:泛型数据契约和集合数据契约(下篇) [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经>为此录制的节目视频(苏州话)]]在.NE ...

  4. WCF技术剖析之十八:消息契约(Message Contract)和基于消息契约的序列化

    原文:WCF技术剖析之十八:消息契约(Message Contract)和基于消息契约的序列化 [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经>为此录制 ...

  5. WCF技术剖析之十六:数据契约的等效性和版本控制

    原文:WCF技术剖析之十六:数据契约的等效性和版本控制 数据契约是对用于交换的数据结构的描述,是数据序列化和反序列化的依据.在一个WCF应用中,客户端和服务端必须通过等效的数据契约方能进行有效的数据交 ...

  6. WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)

    原文:WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer) [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济 ...

  7. WCF技术剖析之三:如何进行基于非HTTP的IIS服务寄宿

    原文:[原创]WCF技术剖析之三:如何进行基于非HTTP的IIS服务寄宿 在上面一篇文章中,我们对不同版本的IIS,以及ASP.NET得的实现机制进行了详细而深入的分析.在介绍IIS7.0的时候,我们 ...

  8. WCF技术剖析之十九:深度剖析消息编码(Encoding)实现(下篇)

    原文:WCF技术剖析之十九:深度剖析消息编码(Encoding)实现(下篇) [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经>为此录制的节目视频(苏州话 ...

  9. WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理

    原文:WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理 在前面一片文章(服务代理不能得到及时关闭会有什么后果?)中,我们谈到及时关闭服务代理(Service Proxy)在一个高并发环境 ...

随机推荐

  1. SQL Server 中 RAISERROR 的用法(转)

    在存储过程中进程会处理一些逻辑性的错误,如:将RMB转换为USD时,没有查询到想要的汇率 这个时候最好在存储过程中抛个异常,方便自己查找错误信息... 其语法如下: RAISERROR ( { msg ...

  2. ajax 基础

    <html><head><script type="text/javascript">function showHint(str){var xm ...

  3. Phases of translation

    Phases of translation--翻译阶段 The C++ source file is processed by the compiler as if the following pha ...

  4. C# Socket select模型

    http://www.cnblogs.com/Clingingboy/archive/2011/07/04/2097806.html http://www.cnblogs.com/RascallySn ...

  5. 类似jquery的一个demo

    通过以下的demo,可以大体知道jquery的一些组织结构以及一些实现方法. 实际上jquery就是一个全局变量,只是在这个变量上添加了各种属性和方法. 首先我们要理解什么是匿名函数自执行,简单点就是 ...

  6. CXF 开发 WebService

    什么是CXF: Apache CXF = Celtix + Xfire 支持多种协议: SOAP1.1,1.2 XML/HTTP CORBA(Common Object Request Broker ...

  7. 转: requirejs中文api (详细)

    RequireJS的目标是鼓励代码的模块化,它使用了不同于传统<script>标签的脚本加载步骤.可以用它来加速.优化代码,但其主要目的还是为了代码的模块化.它鼓励在使用脚本时以modul ...

  8. VC++对象布局的奥秘:虚函数、多继承、虚拟继承

    哈哈,从M$ Visual C++ Team的Andy Rich那里又偷学到一招:VC8的隐含编译项/d1reportSingleClassLayout和/d1reportAllClassLayout ...

  9. TextView textSize 文字大小

    TextView,很常见的控件.关于文字大小的方法有: android.widget.TextView#getTextSize  返回值的单位是PX /** * @return the size (i ...

  10. 【充电器】小米手机2S电池座充——小米手机官网

    ligh@local-host$ ssh-copy-id -i ~/.ssh/id_rsa.pub root@192.168.0.3 [充电器]小米手机2S电池座充--小米手机官网 小米手机2S电池座 ...