WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇]
2010-01-04 19:46
946 查看
在进行基于会话信道的WCF服务调用中,由于受到并发信道数量的限制,我们需要及时的关闭信道;当遇到某些异常,我们需要强行中止(Abort)信道,相关的原理,可以参考我的文章《服务代理不能得到及时关闭会有什么后果?》。在真正的企业级开发中,正如我们一般不会让开发人员手工控制数据库连接的开启和关闭一样,我们一般也不会让开发人员手工去创建、开启、中止和关闭信道,这些工作是框架应该完成的操作。这篇文章,我们就来介绍如果通过一些编程技巧,让开发者能够无视“信道”的存在,像调用一个普通对象一样进行服务调用。
处于对性能的考虑,避免对ChannelFactory<TChannel>的频繁创建,通过一个字典对象将创建出来的ChannelFactory<TChannel>缓存起来;两个Invoke方法中,服务的调用通过两个Delegate对象(Action<TChannel>和Func<TChannel, TResult>)表示,另一个参数表示终结点的配置名称。那么这时的服务调用就会变得相当简单:
通过传入终结点配置名称创建ServiceInvoker<TChannel>对象,直接通过调用基类的静态方法实现了两个Invoke方法。
在分层设计中,为每一个层定义的组件创建基类是一个很常见的设计方式。在这里,假设所有的服务代理类型均继承自基类:ServiceProxyBase<TChannel>,泛型类型为服务契约类型。同样通过传入终结点配置名称创建服务代理,并借助于通过Invoker属性表示的ServiceInvoker<TChannel>对象进行服务的调用。ServiceProxyBase<TChannel>定义如下:
那么,具体的服务代理类型就可以通过如下的方式定义了:
那么现在服务代理的消费者(一般是Presenter层对象),就可以直接实例化服务代理对象,并调用相应的方法(这里的方法与服务契约方法一致)即可,所有关于服务调用的细节均被封装在服务代理中。
四、局限
这个解决方案有一个很大的局限:服务方式不能包含ref和out参数,因为这两种类型的参数不能作为匿名方法的参数。
一、正常的服务调用方式
如果通过ChannelFactory<TChannel>创建用于服务调用的代理,下面的代码片段描述了客户端典型的服务调用形式:将服务调用在基于代理对象的using块中,并通过try/catch进一步对服务调用操作进行异常处理。当TimeoutException或者CommunicationException被捕获后,调用Abort方法将信道中止。当程序执行到using的末尾,Dispose方法会进一步调用Close方法对信道进行关闭。using System; using System.Collections.Generic; using System.ServiceModel; namespace Artech.Lib { public class ServiceInvoker { private static Dictionary<string, ChannelFactory> channelFactories = new Dictionary<string, ChannelFactory>(); private static object syncHelper = new object(); private static ChannelFactory<TChannel> GetChannelFactory<TChannel>(string endpointConfigurationName) { ChannelFactory<TChannel> channelFactory = null; if (channelFactories.ContainsKey(endpointConfigurationName)) { channelFactory = channelFactories[endpointConfigurationName] as ChannelFactory<TChannel>; } if (null == channelFactory) { channelFactory = new ChannelFactory<TChannel>(endpointConfigurationName); lock (syncHelper) { channelFactories[endpointConfigurationName] = channelFactory; } } return channelFactory; } public static void Invoke<TChannel>(Action<TChannel> action, TChannel proxy) { ICommunicationObject channel = proxy as ICommunicationObject; if (null == channel) { throw new ArgumentException("The proxy is not a valid channel implementing the ICommunicationObject interface", "proxy"); } try { action(proxy); } catch (TimeoutException) { channel.Abort(); throw; } catch (CommunicationException) { channel.Abort(); throw; } finally { channel.Close(); } } public static TResult Invoke<TChannel, TResult>(Func<TChannel, TResult> function, TChannel proxy) { ICommunicationObject channel = proxy as ICommunicationObject; if (null == channel) { throw new ArgumentException("The proxy is not a valid channel implementing the ICommunicationObject interface", "proxy"); } try { return function(proxy); } catch (TimeoutException) { channel.Abort(); throw; } catch (CommunicationException) { channel.Abort(); throw; } finally { channel.Close(); } } public static void Invoke<TChannel>(Action<TChannel> action, string endpointConfigurationName) { Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName"); Invoke<TChannel>(action, GetChannelFactory<TChannel>(endpointConfigurationName).CreateChannel()); } public static TResult Invoke<TChannel, TResult>(Func<TChannel, TResult> function, string endpointConfigurationName) { Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName"); return Invoke<TChannel, TResult>(function, GetChannelFactory<TChannel>(endpointConfigurationName).CreateChannel()); } } }
处于对性能的考虑,避免对ChannelFactory<TChannel>的频繁创建,通过一个字典对象将创建出来的ChannelFactory<TChannel>缓存起来;两个Invoke方法中,服务的调用通过两个Delegate对象(Action<TChannel>和Func<TChannel, TResult>)表示,另一个参数表示终结点的配置名称。那么这时的服务调用就会变得相当简单:
using System; using Artech.Lib; using Artech.WcfServices.Contracts; namespace Artech.WcfServices.Clients { class Program { static void Main(string[] args) { int result = ServiceInvoker.Invoke<ICalculator, int>(calculator => calculator.Add(1, 2), "calculatorservice"); Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, result); Console.Read(); } } }
三、对ServiceInvoker的改进
实际上,为了对服务调用实现细节进行进一步的封装,一般地我们可以将其定义在一个独立的层中,比如服务代理层(这里的层不一定像数据访问层、业务逻辑层一样需要一个明显的界限,这里可能就是一个单独的类型而已)。在这种情况下,我们可以上面的ServiceInvoker方法进行一定的改造,使之更加符合这种分层的场景。上面我们调用静态方法的形式进行服务的调用,现在我们需要的是:实例化服务代理对象,并调用相应的方法。为此,我创建了一个泛型的ServiceInvoker<TChannel>类型,该类型继承自上述的ServiceInvoker,泛型类型表示服务契约类型。ServiceInvoker<TChannel>定义如下:using System; namespace Artech.Lib { public class ServiceInvoker<TChannel>:ServiceInvoker { public string EndpointConfigurationName {get; private set;} public ServiceInvoker(string endpointConfigurationName) { Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName"); this.EndpointConfigurationName = endpointConfigurationName; } public void Invoke(Action<TChannel> action) { Invoke<TChannel>(action, this.EndpointConfigurationName); } public TResult Invoke<TResult>(Func<TChannel, TResult> function) { return Invoke<TChannel, TResult>(function, this.EndpointConfigurationName); } } }
通过传入终结点配置名称创建ServiceInvoker<TChannel>对象,直接通过调用基类的静态方法实现了两个Invoke方法。
在分层设计中,为每一个层定义的组件创建基类是一个很常见的设计方式。在这里,假设所有的服务代理类型均继承自基类:ServiceProxyBase<TChannel>,泛型类型为服务契约类型。同样通过传入终结点配置名称创建服务代理,并借助于通过Invoker属性表示的ServiceInvoker<TChannel>对象进行服务的调用。ServiceProxyBase<TChannel>定义如下:
namespace Artech.Lib { public class ServiceProxyBase<TChannel> { public virtual ServiceInvoker<TChannel> Invoker { get; private set; } public ServiceProxyBase(string endpointConfigurationName) { Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName"); this.Invoker = new ServiceInvoker<TChannel>(endpointConfigurationName); } } }
那么,具体的服务代理类型就可以通过如下的方式定义了:
using Artech.Lib; using Artech.WcfServices.Contracts; namespace Artech.WcfServices.Clients { public class CalculatorProxy : ServiceProxyBase<ICalculator>, ICalculator { public CalculatorProxy():base(Constants.EndpointConfigurationNames.CalculatorService) { } public int Add(int x, int y) { return this.Invoker.Invoke<int>(calculator => calculator.Add(x, y)); } } public class Constants { public class EndpointConfigurationNames { public const string CalculatorService = "calculatorservice"; } } }
那么现在服务代理的消费者(一般是Presenter层对象),就可以直接实例化服务代理对象,并调用相应的方法(这里的方法与服务契约方法一致)即可,所有关于服务调用的细节均被封装在服务代理中。
using System; using Artech.Lib; using Artech.WcfServices.Contracts; namespace Artech.WcfServices.Clients { class Program { static void Main(string[] args) { CalculatorProxy calculatorProxy = new CalculatorProxy(); int result = calculatorProxy.Add(1, 2); Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, result); Console.Read(); } } }
四、局限
这个解决方案有一个很大的局限:服务方式不能包含ref和out参数,因为这两种类型的参数不能作为匿名方法的参数。
相关文章推荐
- WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇]
- WCF技术剖析之三十:一个很有用的WCF调用编程技巧[下篇]
- WCF技术剖析之三十:一个很有用的WCF调用编程技巧[下篇]
- WCF技术剖析之三十一:WCF事务编程[上篇]
- WCF技术剖析之十一:异步操作在WCF中的应用(上篇)
- WCF技术剖析之十四:泛型数据契约和集合数据契约(上篇)
- WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于WS-MEX的实现](提供模拟程序)
- [原创] WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理
- WCF技术剖析之十一:异步操作在WCF中的应用(上篇)
- WCF技术剖析之十四:泛型数据契约和集合数据契约(上篇)
- WCF热门问题编程示例(2)多个实例调用一个WCF服务操作,需要等待服务响应吗
- WCF技术剖析之一:通过一个ASP.NET程序模拟WCF基础架构
- WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理
- WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于HTTP-GET的实现](提供模拟程序)
- [原创] WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[上篇]
- WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[上篇]
- [原创]WCF技术剖析之二十一: WCF基本的异常处理模式[上篇]
- WCF技术剖析之二十三:服务实例(Service Instance)生命周期如何控制[上篇](转)
- WCF热门问题编程示例(2)多个实例调用一个WCF服务操作,需要等待服务响应吗
- WCF技术剖析之十一:异步操作在WCF中的应用(上篇)