WCF足迹10:异常2

来源:互联网 发布:nsga2算法讲解 编辑:程序博客网 时间:2024/05/17 23:23

(灰灰虫的家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();
    }
    
运行结果:



《图9》
(灰灰虫的家http://hi.baidu.com/grayworm)

原创粉丝点击