WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理(转)
2011-09-18 13:03
1381 查看
using System.ServiceModel;
namespace Artech.ExceptionHandlingDemo.Contracts
{
[ServiceContract(Namespace = "urn:artech.com")]
public interface ICalculator
{
[OperationContract]
int Divide(int x, int y);
}
}
using System;
using System.ServiceModel;
using Artech.ExceptionHandlingDemo.Contracts;
namespace Artech.ExceptionHandlingDemo.Services
{
class CalcualtorService : ICalculator{ public int Divide(int x, int y){ return x / y;}}
}为了确保服务代理的及时关闭,按照典型的编程方式,我们需要采用try/catch/finally的方式才操作服务代理对象,并把服务代理的关闭放在finally块中。WCF服务在客户端的调用程序如下所示:
using System;
using System.ServiceModel;
using Artech.ExceptionHandlingDemo.Contracts;
namespace Client
{
class Program
{
static void Main(string[] args)
{
using (ChannelFactory<ICalculator> channelFatory = new ChannelFactory<ICalculator>(new WSHttpBinding(), "http://127.0.0.1:3721/calculatorservice"))
{
ICalculator calcultor = channelFatory.CreateChannel(); try
{
calcultor.Divide(1, 0);
}
catch (Exception ex){ Console.WriteLine(ex.Message);}
finally
{
(calcultor as ICommunicationObject).Close();
}
}
}
}
}由于传入的参数为1和0,在服务执行除法运算的时候,会抛出DividedByZero的异常。当服务端程序执行到finally块中对服务代理进 行关闭的时候,会抛出如下一个CommunicationObjectFaultedException异常,提示SerivceChannel的状态为 Faulted,不能用于后续Communication。
二、原理分析
要解释具体的原因,还得从信道(Channel)的两种分类形式说起。在上面一篇文章中,我们就谈到过:WCF通过信道栈实现了消息的编码、传输及 基于某些特殊功能对消息的特殊处理,而绑定对象是信道栈的缔造者,不同的绑定类型创建出来的信道栈具有不同的特性。就对会话的支持来讲,我们可以将信道分 为以下两种:
会话信道(Sessionful Channel):会话信道确保客户端和服务端之间传输的消息能够相互关联,但是信道的错误(Fault)会影响后续的消息交换;
数据报信道(Datagram Channel):即使在同一个数据报信道中,每次消息的交换都是相互独立,信道的错误也不会影响后续的消息交换。
由于上面的例子中,我们采用了WsHttpBinding,所以在默认条件下创建的信道(Channel)是会话信道(SessionfulChannel)。异常抛出后,当前信道的状态将变成Faulted,表示信道出现错误。错误的信道将不能继续用于后续的通信,即使是调用Close方法 试图将其关闭也不行。
也就是说异常导致信道错误(Faulted)的特性仅仅对于会话信道而言,而对于数据报信道,则没有这样的问题。对于WsHttpBinding在如下两种情况下下具有创建会话信道的能力:
采用任何一种非None的SecurityMode
采用ReliableSession
再默认的情况下,WsHttpBinding采用的SecurityMode为Message,所以其创建的信道是会话信道。如果我们将其 SecurityMode设为None,则在执行Close方法的时候则不会抛出任何异常(而实际上,服务代理的关闭与否对于数据报信道来讲,没有任何意 义)。具体实现如下面的代码所示:
using System;
using System.ServiceModel;
using Artech.ExceptionHandlingDemo.Contracts;
namespace Client
{
class Program
{
static void Main(string[] args)
{
using (ChannelFactory<ICalculator> channelFatory = new ChannelFactory<ICalculator>(new WSHttpBinding(SecurityMode.None), "http://127.0.0.1:3721/calculatorservice"))
{ //......
}
}
}
}三、Close() V.S. Abort()
在这种情况下,一般会调用另一个方法:Abort,强行中断当前信道。一般情况下,对于客户端来说,信道在下面两种情况下状态会变成Faulted:
调用超时,抛出TimeoutException
调用失败,抛出CommunicationException
所以正确的客户端进行服务调用的代码应该如下面的代码所示:通过try/catch控制服务调用,在try控制块中进行正常服务调用并正常关闭服务 代理进程(调用Close方法);在catch控制块中,捕获CommunicationException和TimeoutException这两个异 常,并将服务代理对象强行关闭(调用Abort方法)。下面的代码演示了基于ChannelFactory<T>创建服务代理的WCF客户端 编程方式,对于直接通过强类型服务代理(继承ClientBase<T>的服务代理类型)进行服务调用具有相同的结构。
using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculateservice"))
{
ICalculator calculator = channelFactory.CreateChannel();
try
{
int result = calculator.Divide(1, 0);
(calculator as ICommunicationObject).Close();
}
catch (CommunicationException ex)
{
//Exception Handling
(calculator as ICommunicationObject).Abort();
}
catch (TimeoutException ex)
{
//Exception Handling
(calculator as ICommunicationObject).Abort();
}
catch (Exception ex)
{
//Exception Handling
}
}四、通过一些编程技巧避免重复代码
如果严格按中上面的编程方式对CommunicationException和TimeoutException进出捕获和处理,那么你的客户端代 码就会到处充斥中相同的代码片断。我不知一次说过,如果你的代码中重复频率过高,或者编程人员广泛地采用Ctrl+C|Ctrl+V这样的编程方式,那么 这就是你进行代码重构的信号。
为此,我们可以通过对Delegate的利用来进行代码的分离(服务调用代码和异常处理代码)。比如,我写了下面两个Invoke方法:
using System;
using System.ServiceModel;
using Artech.ExceptionHandlingDemo.Contracts;
namespace Client
{
class Program
{
static void Invoke<TContract>(TContract proxy, Action<TContract> action)
{
try
{
action(proxy);
(proxy as ICommunicationObject).Close();
}
catch (CommunicationException)
{
(proxy as ICommunicationObject).Abort();
//Handle Exception
throw;
}
catch (TimeoutException )
{
(proxy as ICommunicationObject).Abort();
//Handle Exception
throw;
}
catch (Exception)
{
//Handle Exception
(proxy as ICommunicationObject).Close();
}
}
static TReturn Invoke<TContract, TReturn>(TContract proxy, Func<TContract, TReturn> func)
{
TReturn returnValue = default(TReturn);
try
{
returnValue =func(proxy);
}
catch (CommunicationException)
{
(proxy as ICommunicationObject).Abort();
//Handle Exception
throw;
}
catch (TimeoutException)
{
(proxy as ICommunicationObject).Abort();
//Handle Exception
throw;
}
catch (Exception)
{
//Handle Exception
}
return returnValue;
}
}
}那么,对CalculatorService就可以简化成:
using System;
using System.ServiceModel;
using Artech.ExceptionHandlingDemo.Contracts;
namespace Client
{
class Program
{
static void Main(string[] args)
{
using (ChannelFactory<ICalculator> channelFatory = new ChannelFactory<ICalculator>(new WSHttpBinding(), "http://127.0.0.1:3721/calculatorservice"))
{
ICalculator calcultor = channelFatory.CreateChannel(); int result = Invoke<ICalculator, int>(calcultor, proxy => proxy.Divide(2, 1)); //......
}
}
}
}
相关文章推荐
- WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理
- WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理
- [原创] WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理
- WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理
- 【转】WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理
- [原创]WCF技术剖析之三:如何进行基于非HTTP的IIS服务寄宿
- [转载]WCF技术剖析之三:如何进行基于非HTTP的IIS服务寄宿
- WCF技术剖析之三:如何进行基于非HTTP的IIS服务寄宿
- WCF技术剖析之二十四: ServiceDebugBehavior服务行为是如何实现异常的传播的?
- WCF技术剖析之二十四: ServiceDebugBehavior服务行为是如何实现异常的传播的?
- WCF技术剖析之三:如何进行基于非HTTP的IIS服务寄宿
- [原创]WCF技术剖析之三:如何进行基于非HTTP的IIS服务寄宿
- WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[中篇]
- [原创]WCF技术剖析之二十一: WCF基本的异常处理模式[上篇]
- WCF技术剖析之二十六:如何导出WCF服务的元数据(Metadata)[实现篇]
- [原创] WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[上篇]
- WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[中篇]
- WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[下篇]
- WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于WS-MEX的实现](提供模拟程序)
- [原创]WCF技术剖析之二十一:WCF基本异常处理模式[下篇]