(原创:灰灰虫的家http://hi.baidu.com/grayworm)
一、初识WCF异常
1.在WCF中异常机制特点:
a.当一个客户端调用WCF服务产生异常后,只会导致当前调用结束,它并不影响其它客户端对该WCF的调用,WCF仍然可以为其它客户端继续服务。
b.WCF产生的异常对象是CLR中的对象类型,而WCF客户端却不一定是.NET,可能是Java或PHP等。所以WCF服务不能向客户端返回CLR异常对象。
下面看一段代码:
服务端代码:
[ServiceContract]
public interface ITestFault
{
[OperationContract]
int TestMethod(int num);
}
public class TestFault:ITestFault
{
//这个方法接收一个整型的数值,然后把该值作为除数进行运算,并返回运算结果
public int TestMethod(int num)
{
return 100 / num;
}
}
客户端代码:
public static void Main(string[] args)
{
SRTestFault.TestFaultClient client = new Client.SRTestFault.TestFaultClient();
client.Open();
while (true)
{
string str = Console.ReadLine();
int num = int.Parse(str);
try
{
int result = client.TestMethod(num);
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine(DateTime.Now.ToString() + "得到运算结果:" + result);
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(DateTime.Now.ToString() + "产生异常:" + ex.Message);
}
finally
{
Console.ResetColor();
}
}
client.Close();
}
运行结果:
《图1》
我们启动了两个客户端,蓝色字代表运行正常,红色字代表运行出错。
“客户端一”在接收0的时候,会在服务端产生除数为0的异常,从图中显示的异常信息我们可以看到到:服务端的异常对象并没有传递到客户端,客户端不知道服务端产生什么样的异常。并且,一旦产生异常后,该客户端与服务端的信道就会被中断无法再进行通信。
虽然“客户端一”与服务端的通信信道被中断了,但服务端并没有因为异常而停止运行。我们可以看到“客户端二”中仍然可以与服务端进行通信。
2.WCF异常的分类:
a.通信错误,诸如网络错误,地址错误,服务器没有启动等等。客户端表现为Communication Exception。
b.状态异常,代理已经关闭,或者通道Fault,等问题,这个比较常见。一般通道闲置时间过久,通道会出现这个状态错误的问题。安全验证失败也会导致这个错误。
c.服务异常,服务调用时抛出的异常,这个服务内部异常会序列化传递给客户端,被客户端捕获。
3.WCF异常与实例的关系:
a.Pre-Call实例模型: 服务实例被释放,客户端抛出 FaultException,产生异常的信道会被中断,相应的客户端代理对象无法继续使用,只能关闭。不影响其它客户端对服务的调用。
b.Pre-Session实例模型:服务实例被释放,会话终止。客户端抛出 FaultException,产生异常的信道会被中断,相应的客户端代理对象无法继续使用,只能关闭。不影响其它客户端对服务的调用。
c.Singleton实例模型:服务实例依旧运行。客户端抛出 FaultException,产生异常的信道会被中断,相应的客户端代理对象无法继续使用,只能关闭。不影响其它客户端对服务的调用。
二、服务端返回异常的详细信息
默认情况下,服务端产生运行异常后,并不会把异常的运行时信息传递到客户端。我们在客户端所得到的只是“由于内部错误,服务器无法处理请求”这样的错误提示。这样虽然能够有效地保护好服务器的内部信息,但也为我们客户端编程造成了很大的不方便。如何把服务端的错误信息传递到客户端是我们这篇文章中主要要解决的问题,下面我们用最简单的方法把服务端的异常信息发送到客户端。
1.使用ServiceBehavior特性向客户端传递异常信息。
把服务声明为[ServiceBehavior(IncludeExceptionDetailInFaults=true)],其中的IncludeExceptionDetailInFaults=true代表向客户端发送异常的详细信息
服务端代码:
[ServiceContract]
public interface ITestFault
{
[OperationContract]
int TestMethod(int num);
}
[ServiceBehavior(IncludeExceptionDetailInFaults=true)]
public class TestFault:ITestFault
{
public int TestMethod(int num)
{
return 100 / num;
}
}
客户端代码保持不变。
运行结果:
2.使用配置文件中的serviceDebug配置节向客户端传递异常信息。
除了可以使用ServiceBehavior把服务端信息传递到客户端外,我们还可以修改Host的配置文件的serviceDebug配置节来向客户端传递异常信息。
这种方式我们可以保持服务端代码与客户端代码不作任何修改。只修改App.Config文件,如图:
《图3》
这种两种方式可以很简单地把服务端的错误信息传送到客户端,但他们也有两个很大的缺陷:
1.会把服务端敏感的信息直接暴露给客户端,如:数据库的用户名、服务器堆栈信息等。有些信息可能被人恶意利用,来对服务端进行攻击。
2.客户端虽然把服务端产生的异常信息获取到了,但是客户端与服务端的信道会被破坏,导致客户端代理无法再与服务端通信,而只能关闭。
《图5》
(原创:灰灰虫的家http://hi.baidu.com/grayworm)
三、使用FaultException抛出简单异常
在WCF服务中我们可以使用FaultException以标准Soap方式向客户端传递简单Fault信息。
在研究FaultException之前我们先来看一下标准Soap格式的Fault
《图6》
从上面的图中我们可以看出一个SoapFault包含Code和Reason。
Code:Soap错误的代码;
Reason:Soap错误的原因,即错误信息;
FaultException常用构造函数:
public FaultException();
public FaultException(FaultReason reason);
public FaultException(string reason);
public FaultException(FaultReason reason,FaultCode code);
public FaultException(string reason,FaultCode code);
下面我们把上面的例子修改一下,WCF服务用FaultException向客户端抛出错误信息。
服务端代码:
[ServiceContract]
public interface ITestFault
{
[OperationContract]
int TestMethod(int num);
}
public class TestFault:ITestFault
{
public int TestMethod(int num)
{
int returnValue = 0;
try
{
returnValue = 100 / num;
}
catch (Exception ex)
{
FaultException fault = null;
if (ex is DivideByZeroException)
{
fault = new FaultException(new FaultReason("产生了除0的异常"), new FaultCode("Error:ox001"));
}
else
{
fault = new FaultException(ex.Message);
}
throw fault;
}
return returnValue;
}
}
在服务端代码我们使用了try-catch语句块把产生的异常包装成FaultException对象,抛向客户端
客户端代码:
SRTestFault.TestFaultClient client = new Client.SRTestFault.TestFaultClient();
client.Open();
while (true)
{
string str = Console.ReadLine();
int num = int.Parse(str);
try
{
int result = client.TestMethod(num);
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine(DateTime.Now.ToString() + "得到运算结果:" + result);
}
catch (FaultException ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(DateTime.Now.ToString() + "产生异常:错误代号-" + ex.Code.Name+",错误原因-" + ex.Reason);
}
catch(Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(DateTime.Now.ToString() + "产生异常:" + ex.Message);
}
finally
{
Console.ResetColor();
}
}
//client.Close();
在客户端代码中,我们也使用了try-catch-finally语句块对服务端返回的错误信息进行捕捉,并显示在界面上。
运行结果:
从图中可以看出,我们把服务器端产生的错误捕获出来,并显示在界面上;但客户端与服务端的信道并没有被破坏,我们仍可以继续向服务端发起新的调用。
优点:
1.把服务端异常信息封装成标准的Soap错误格式,通过Http传递,在客户端可以得到服务端返回标准Soap错误,并从中取得出服务端异常信息。
2.服务端抛出FaultException后,并不会破坏客户端与服务端的信道,客户端仍可以通过此信道向服务端发出调用。
缺点:只能向客户端返回简单的错误。
(原创:灰灰虫的家http://hi.baidu.com/grayworm)