(灰灰虫的家http://hi.baidu.com/grayworm)
四、使用FaultContract抛出更多的异常信息。
从上面的例子中我们可以看出,不管服务端产生了什么类型的异常,最终传递到客户端只有一种异常类型--FaultException,只是异常的Reason不同。
如果服务端要向客户端传递更多的异常细节信息,或者服务端被要求向客户端传递强类型的错误对象的话,那我们可以使用FaultContract(错误契约)来实现。
一般FaultContract和FaultException<T>配合使用向客户端返回强类型的异常。
1.定义自定义的异常类:
与定义数据契约一样,定义一个特定的类来承载特定的异常信息。
public enum FaultLevel
{
Lower,
Normal,
Higher,
Serious
};
[DataContract]
public class CustomFaultContract
{
[DataMember]
public DateTime Time
{ get; set; }
[DataMember]
public FaultLevel Level
{ get; set; }
}
上面的代码我们定义了一个FaultLevel枚举类型,代表错误的级别。另外定义了一个类CustomFaultContract用来保存特定的异常信息,在这个类中我们定义了两个属性Time和Level,Time是异常的产生时间,Level是异常的错误级别。
在WCF服务中,如果产生异常,就向客户端返回错误,并在Soap Fault中附加上自定义的异常细节。
2.定义服务契约:
[ServiceContract]
public interface ITestFault
{
[OperationContract]
[FaultContract(typeof(CustomFaultContract))] //声明错误契约,指定方法中可以返回CustomFaultContract类型的错误对象
int TestMethod(int num);
}
public class TestFault:ITestFault
{
public int TestMethod(int num)
{
int returnValue = 0;
try
{
returnValue = 100 / num;
}
catch (Exception ex)
{
//定义CustomFaultContract的泛型FaultException
FaultException<CustomFaultContract> fault = null;
if (ex is DivideByZeroException)
{
//生成CustomFaultContract对象
CustomFaultContract contract = new CustomFaultContract();
contract.Time = DateTime.Now;
contract.Level = FaultLevel.Lower;
//实例化CustomFaultContract泛型FaultException对象
fault = new FaultException<CustomFaultContract>(contract, "除为不能为0,运算发生错误!", new FaultCode("Error:ox001"));
}
else
{
CustomFaultContract contract = new CustomFaultContract();
contract.Time = DateTime.Now;
contract.Level = FaultLevel.Normal;
fault = new FaultException<CustomFaultContract>(contract,ex.Message);
}
throw fault;
}
return returnValue;
}
}
1.在方法契约加上声明FaultContract,指明方法中可能抛出哪种类型的泛型FaultException。可以在方法契约上加多个FaultContract。如果方法中抛出的FaultException泛型没有声明为FaultContract,就会破坏信道。
2.抛出FaultException<CustomFaultContract>类型的错误,把需要传递到客户端的错误信息封装在CustomFaultContract对象中。
3.客户端代码:
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 (FaultException<SRTestFault.CustomFaultContract> ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(
DateTime.Now.ToString() + "产生异常:\n\t错误代号-" + ex.Code.Name +
";\n\t错误原因-" + ex.Reason +
";\n\t出错时间-" + ex.Detail.Time.ToString() +
";\n\t错误级别-" + ex.Detail.Level.ToString()
);
}
finally
{
Console.ResetColor();
}
}
client.Close();
}
在catch后的小括号中,我们声明的异常类型不再是Exception,也不是FaultException,而是FaultException<SRTestFault.CustomFaultContract>类型的异常对象,这个对象中包含了CustomFaultContract类型的异常信息。我们可以使用ex.Detail来取得其中的异常特殊信息。
4.运行结果:
《图8》
从图中我们可以看到,在客户端显示出来的错误信息中除了“错误代号”和“错误原因”外,还包含着“出错时间”和“错误级别”两个特殊信息。这两个信息就是通过ex.Detail来取得的。
在服务运行错误后,并不会破坏信道,客户端还可以继续对服务发起新调用。
(原创:灰灰虫的家http://hi.baidu.com/grayworm)
五、使用IErrorHandler实现自定义错误逻辑处理。
除了可以使用上面的方式来处理异常外,WCF还为我们提供了处理自定义异常的扩展功能。这种对自定义异常的扩展,我们可以通过IErrorHandler接口来实现。
IErrorHandler接口定义如下:
public interface IErrorHandler
{
bool HandleError(Exception error);
void ProvideFault(Exception error,MessageVersion version,ref Message fault);
}
接口在定义了两个方法的声明:
1.void ProvideFault(Exception error,MessageVersion version,ref Message fault)方法:
触发时机:异常发生之后,向客户端发送错误之前被触发。
在这个方法中被触发的过程中,客户端一直等待服务端运行结束,客户端处于阻塞状态。所以尽量不要在这个方法中编写耗时太长的代码,防止客户端等待超时。
参数:
error:刚刚产生的异常对象,它可以是常规的CLR异常,也可以是SoapFault异常。如果在ProvideFault不编写任何代码的话,就会把该异常对象做为“未处理的异常”继续往上仍。
version:产生的Soap错误的版本。以Soap1.1或Soap1.2格式向客户端返回错误信息。
(ref) fault:输出参数,返回给客户端的错误消息。我们可以把刚刚的生的异常对象error经过包装或替换,形成一个新的错误消息fault,再把这个消息返回给客户端。
示例:
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
//根据刚产生的异常对象,封装新的Fault异常
FaultException e = new FaultException(error.Message.ToString(), new FaultCode("Error:ox003"));
//根据Fault异常生成内存中的Soap错误对象。
MessageFault m = e.CreateMessageFault();
//根据内存中的Soap错误对象,生成错误的消息,并返回给客户端。
fault = Message.CreateMessage(version, m, e.Action);
}
2.bool HandleError(Exception error)方法:
触发时机:向客户端发送错误之后触发。该方法只被服务端调用与客户端无关。同时,它是以一个独立的线程运行的(即与ProvideFault不是同一线程运行)。
这个方法被触发时,服务端已向客户端返回运行结果了,所以在这个方法中编写耗时较长的代码,是不会影响客户端运行的。
参数error:最初产生的异常对象。
返回值bool:是否已经把异常处理完成。true-已处理完成,无需再向上反升异常信息。false-未处理完成,继续向上反升错误异常,让上一级能够捕获并处理该异常。
示例:
class MyErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
try
{
LogbookServiceClient proxy = new LogbookServiceClient( );
proxy.Log(...);
proxy.Close( );
}
catch
{}
finally
{
return false;
}
}
public void ProvideFault(Exception error,MessageVersion version,ref Message fault)
{...}
}
上面的代码中实现的功能是:在向客户端返回错误后,调用另一个服务LogbookService来记录出错日志。
3.把我们自定义的错误处理类注册到通信信道中去。
虽然我们通过实现IErrorHandler接口,定义了自己的错误处理类,但事情还没有完全完成,我们还需要在信道中注册我们的异常处理程序。
通过实现IServiceBehavior接口,把我们自定义的错误类当成自定义的服务行为。服务行为可以参与信道交互,我们可以借此把自定义异常处理程序注册到信道通信中去。
IServiceBehavior接口的定义如下:
public interface IServiceBehavior
{
void AddBindingParameters(ServiceDescription description,ServiceHostBase host,Collection<ServiceEndpoint> endpoints,BindingParameterCollection parameters);
void ApplyDispatchBehavior(ServiceDescription description,ServiceHostBase host);
void Validate(ServiceDescription description,ServiceHostBase host);
}
在这个接口中,我们只需要实现ApplyDispatchBehavior()方法就可以了,另外两个方法可以不用管它们,保持“空实现”即可。
在ApplyDispatchBehavior()方法中,我们把自定义异常类注册到相应的信道中就可以了,一般我们会把它注册到所有的信道。
代码如下:
public class MyFault : IMyFault, IServiceBehavior
{
public void TestConnection(string uid, string pwd)
{
//.......
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
MyErrorHandler error = new MyErrorHandler();
foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
{
dispatcher.ErrorHandlers.Add(error);
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
}
在Host打开之前,系统会调用IServiceBehavior接口的ApplyDispatchBehavior()方法,向所有信道中注册自定义错误类。一旦运行发生错误就是触发相应的自定义错误处理程序。
这样并不会破坏通信信道。
4.完整代码:
自定义错误处理类:
public class MyErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
Console.WriteLine(error.Message);
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
FaultException e = new FaultException(error.Message.ToString(), new FaultCode("Error:ox003"));
MessageFault m = e.CreateMessageFault();
fault = Message.CreateMessage(version, m, e.Action);
}
}
服务契约:
[ServiceContract]
public interface IMyFault
{
[OperationContract]
void TestConnection(string uid,string pwd);
}
服务:
public class MyFault : IMyFault, IServiceBehavior
{
public void TestConnection(string uid, string pwd)
{
string str = "server=.;database=mydb;uid=" + uid + ";pwd=" + pwd;
SqlConnection conn = new SqlConnection(str);
try
{
conn.Open();
}
finally
{
conn.Close();
}
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
MyErrorHandler error = new MyErrorHandler();
foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
{
dispatcher.ErrorHandlers.Add(error);
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
}
客户端代码:
public static void Main(string[] args)
{
SRMyFault.MyFaultClient client = new Client.SRMyFault.MyFaultClient();
client.Open();
while (true)
{
Console.Write("请输入用户名:");
string uid = Console.ReadLine();
Console.Write("请输入密码:");
string pwd = Console.ReadLine();
try
{
client.TestConnection(uid, pwd);
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("服务器连接成功");
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(ex.Message);
}
finally
{
Console.ResetColor();
}
}
client.Close();
}
运行结果: