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

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

在进行基于会话信道的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: }
时间: 2024-10-01 07:54:46

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

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

原文:WCF技术剖析之三十:一个很有用的WCF调用编程技巧[下篇] 在<上篇>中,我通过使用Delegate的方式解决了服务调用过程中的异常处理以及对服务代理的关闭.对于<WCF技术剖析(卷1)>的读者,应该会知道在第7章中我通过类似于AOP的方式解决了相似的问题,现在我们来讨论这个解决方案. 通过<服务代理不能得到及时关闭会有什么后果?>的介绍,我们知道了及时关闭服务代理的重要意义,并且给出了正确的编程方式.如果严格按照上面的编程方式,就意味着对于每一个服务调用,都要

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

在本篇文章中,我们将讨论WCF四大契约(服务契约.数据契约.消息契约和错误契约)之一的消息契约(Message Contract).服务契约关注于对服务操作的描述,数据契约关注于对于数据结构和格式的描述,而消息契约关注的是类型成员与消息元素的匹配关系. 我们知道只有可序列化的对象才能通过服务调用在客户端和服务端之间进行传递.到目前为止,我们知道的可序列化类型有两种:一种是应用了System.SerializableAttribute特性或者实现了System.Runtime.Serializat

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

原文:[原创]WCF技术剖析之三:如何进行基于非HTTP的IIS服务寄宿 在上面一篇文章中,我们对不同版本的IIS,以及ASP.NET得的实现机制进行了详细而深入的分析.在介绍IIS7.0的时候,我们谈到,HTTP.SYS+W3SVC实现了基于HTTP的请求监听,在此基础上引入了以下三组网络监听器(Listener)和监听适配器(Adapter),实现了基于TCP.Named Pipes和MSMQ的网络监听,图1揭示了IIS7的总体结构. TCPListener|TCP Listener Ada

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

原文:WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理 在前面一片文章(服务代理不能得到及时关闭会有什么后果?)中,我们谈到及时关闭服务代理(Service Proxy)在一个高并发环境下的重要意义,并阐明了其根本原因.但是,是否直接调用ICommunicationObject的Close方法将服务代理关闭就万事大吉了呢?事情远不会这么简单,这其中还会涉及关于异常处理的一些操作,这就是本篇文章需要讨论的话题. 一.异常的抛出与Close的失败 一般情况下,当服务端抛出异常,客户客户

《WCF技术剖析》博文系列汇总[持续更新中]

http://www.cnblogs.com/artech/archive/2009/11/21/1607686.html 近半年以来,一直忙于我的第一本WCF专著<WCF技术剖析(卷1)>的写作,一直无暇管理自己的Blog.在<WCF技术剖析(卷1)>写作期间,对WCF又有了新的感悟,为此以书名开始本人的第三个WCF系列.本系列的目的在于对<WCF技术剖析>的补充,会对书中的一些内容进行展开讲述,同时会囊括很多由于篇幅的原因忍痛割弃的内容. [第1篇] 通过一个ASP

WCF技术剖析之一:通过一个ASP.NET程序模拟WCF基础架构

原文:WCF技术剖析之一:通过一个ASP.NET程序模拟WCF基础架构 细算起来,已经有好几个月没有真正的写过文章了.近半年以来,一直忙于我的第一本WCF专著<WCF技术剖析>的写作,一直无暇管理自己的Blog.到目前为止<WCF技术剖析(卷1)>的写作暂告一段落,初步预计于下个月由武汉博文视点出版.在<WCF技术剖析>写作期间,对WCF又有了新的感悟,为此以书名开始本人的第三个WCF系列.本系列的目的在于对<WCF技术剖析>的补充,会对书中的一些内容进行展

WCF技术剖析之二十九:换种不同的方式调用WCF服务[提供源代码下载]

原文:WCF技术剖析之二十九:换种不同的方式调用WCF服务[提供源代码下载] 我们有两种典型的WCF调用方式:通过SvcUtil.exe(或者添加Web引用)导入发布的服务元数据生成服务代理相关的代码和配置:通过ChannelFactory<TChannel>创建服务代理对象.在这篇文章中,我们采用一种独特的方式进行服务的调用.从本质上讲,我们只要能够创建于服务端相匹配的终结点,就能够实现正常的服务调用.在WCF客户端元数据架构体系中,利用MetadataExchangeClient可以获取服

WCF技术剖析之二:再谈IIS与ASP.NET管道

原文:WCF技术剖析之二:再谈IIS与ASP.NET管道 在2007年9月份,我曾经写了三篇详细介绍IIS架构和ASP.NET运行时管道的文章,深入介绍了IIS 5.x与IIS 6.0HTTP请求的监听与分发机制,以及ASP.NET运行时管道对HTTP请求的处理流程: [原创]ASP.NET Process Model之一:IIS 和 ASP.NET ISAPI [原创]ASP.NET Process Model之二:ASP.NET Http Runtime Pipeline - Part I

WCF技术剖析之九:服务代理不能得到及时关闭会有什么后果?

原文:WCF技术剖析之九:服务代理不能得到及时关闭会有什么后果? 我们想对WCF具有一定了解的人都会知道:在客户端通过服务调用进行服务调用过程中,服务代理应该及时关闭.但是如果服务的代理不等得到及时的关闭,到底具有怎样的后果?什么要关闭服务代理?在任何时候都需要关闭服务代理吗?是否有一些例外呢?本篇文章将会围绕着这些问题展开. 一.会话信道(Sessionful Channel) V.S. 数据报信道(Datagram Channel) WCF通过信道栈实现了消息的编码.传输及基于某些特殊功能对