博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇]
阅读量:6639 次
发布时间:2019-06-25

本文共 9291 字,大约阅读时间需要 30 分钟。

原文:

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

一、正常的服务调用方式

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

class Program
{
static void Main(string[] args)
{
using (ChannelFactory
channelFactory = new ChannelFactory
("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
channelFactories = new Dictionary
();
9:         private static object syncHelper = new object();
10: 
11:         private static ChannelFactory
GetChannelFactory
(string endpointConfigurationName)
12:         {
13:             ChannelFactory
channelFactory = null;
14:             if (channelFactories.ContainsKey(endpointConfigurationName))
15:             {
16:                 channelFactory = channelFactories[endpointConfigurationName] as ChannelFactory
;
17:             }
18: 
19:             if (null == channelFactory)
20:             {
21:                 channelFactory = new ChannelFactory
(endpointConfigurationName);
22:                 lock (syncHelper)
23:                 {
24:                     channelFactories[endpointConfigurationName] = channelFactory;
25:                 }
26:             }
27:             return channelFactory;
28:         }
29: 
30:         public static void Invoke
(Action
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
(Func
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
(Action
action, string endpointConfigurationName)
85:         {
86:             Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName");
87:             Invoke
(action, GetChannelFactory
(endpointConfigurationName).CreateChannel());
88:         }
89: 
90:         public static TResult Invoke
(Func
function, string endpointConfigurationName)
91:         {
92:             Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName");
93:             return Invoke
(function, GetChannelFactory
(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
(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
: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
action)
16:         {
17:             Invoke
(action, this.EndpointConfigurationName);
18:         }
19: 
20:         public TResult Invoke
(Func
function)
21:         {
22:             return Invoke
(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
4:     {
5:         public virtual ServiceInvoker
Invoker
6:         { get; private set; }
7: 
8:         public ServiceProxyBase(string endpointConfigurationName)
9:         {
10:             Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName");
11:             this.Invoker = new ServiceInvoker
(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
6:     {
7:         public CalculatorProxy():base(Constants.EndpointConfigurationNames.CalculatorService)
8:         { }
9: 
10:         public int Add(int x, int y)
11:         {
12:             return this.Invoker.Invoke
(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: }

四、局限

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

作者:
出处:
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
你可能感兴趣的文章
去掉字符串间的各种符号
查看>>
Openstack 实现技术分解 (4) 通用技术 — TaskFlow
查看>>
C# DataTable 转换为 实体类对象方法
查看>>
序言 Preface
查看>>
逆序数
查看>>
spoj 2211. Mars Map
查看>>
apache 的虚拟主机和 web访问时序图
查看>>
Block介绍(二)内存管理与其他特性
查看>>
《R语言实战》读书笔记-- 第六章 基本图形
查看>>
烽火传递
查看>>
VS2013编写的C#程序,在xp下会报错说“不是合法的win32程序”。
查看>>
PL SQL显示的字段长度不全
查看>>
微信支付开发h5发起支付再次签名,返回给h5前端
查看>>
[Altera]PLL仿真
查看>>
PM2 指令简介
查看>>
【C语言学习趣事】_32_平胸的尴尬,嫁不出去的姑娘
查看>>
获取近30天的数据的时间方式
查看>>
Android AChartEngine 个性化设置
查看>>
Cool tool: Linux字符画figlet
查看>>
[转]windows下安装python MySQLdb及问题解决
查看>>