WCF开发之异常与错误处理

对于WCF中的异常与错误处理我们必须先了解一个概念SOAP Faults,它实际上就是在服务端和客户端之间来传递错误信息的一种载体。

• 公共语言运行时(CLR)异常无法跨越服务边界
– 未捕捉异常最多到达服务通道(service channel)
– 在报告给客户端之前必须要进行序列化
• 所有的异常都被序列化为SOAP faults
– 基于标准的,可互操作的
– 给予SOAP 1.1 或者 SOAP 1.2 格式

SOAP1.1

1.1代码
<s:Envelope
xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  
<s:Body>
    
<s:Fault>
      
<faultcode xmlns="">s:Client</faultcode>
      
<faultstring xml:lang="en-US" xmlns="">
        An invalid operation has occurred.
      
</faultstring>
    
</s:Fault>
  
</s:Body>
</s:Envelope>

SOAP1.2

代码
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:a
="http://www.w3.org/2005/08/addressing">
  
<s:Header>
    
<a:Action s:mustUnderstand="1">
      http://www.w3.org/2005/08/addressing/soap/fault
    
</a:Action>
    
<a:RelatesTo>
      urn:uuid:64c5619c-99c3-4a83-9bdcfcbb6f399f93
</
a:RelatesTo
>
    
</s:Header>
  
<s:Body>
    
<s:Fault>
      
<s:Code>
        
<s:Value>s:Sender</s:Value>
      
</s:Code>
      
<s:Reason>
        
<s:Text xml:lang="en-US">
          An invalid operation
          has occurred.
        
</s:Text>
      
</s:Reason>
    
</s:Fault>
  
</s:Body>
</s:Envelope>

非捕捉的异常
• CLR异常会从业务逻辑组件传递到服务层
• 在缺省状态下,异常的细节不会与客户端应用程序共享
• 返回一个通用的SOAP Fault

抛出异常
• 考虑下面这个CLR异常:
throw new InvalidOperationException("The method or operation is not implemented.");
• 如果没有被捕捉到,服务模型将会返回一个SOAP Fault
– 缺省情况下,异常中不包含细节信息
– 能够选择添加细节信息
• SOAP 格式依赖于绑定(binding)

IncludeExceptionDetailsInFaults(打开开关[true]就可以看到具体的异常信息了)
• IncludeExceptionDetailsInFaults
– Debugging行为控制如何对待未捕捉的异常
– 如果被打开,生成的SOAP fault中,异常细节部分将包括栈跟踪的信息
• 发送调用栈跟踪细节是有风险的
• 只用于调试目的

在<serviceDebug>区域中配置服务的行为:

代码
<system.serviceModel>
  
<services>
    
<service name="ExceptionService.Service"
    behaviorConfiguration
="serviceBehavior">
      
<endpoint address="http://localhost:8000/Service"
      contract
="ExceptionService.IService" binding="wsHttpBinding" />
    
</service>
  
</services>
  
<behaviors>
    
<serviceBehaviors>
      
<behavior name="serviceBehavior" >
        
<serviceDebug includeExceptionDetailInFaults ="true"/>
      
</behavior>
    
</serviceBehaviors>
  
</behaviors>
</system.serviceModel>

 

使用ServiceBehaviorAttribute来初始化行为:(code方式)
[ServiceBehaviorAttribute(IncludeExceptionDetailsInFaults=true)]
public class Service : IService

下面看一个Demo:

先提出一个知识点:

[OperationContract(IsOneWay=true)]的含义。

IsOneWay=false是默认的设置,是一种阻塞方式的调用,可以理解为同步调用,当客户端发送调用服务的请求后,客户端被阻塞,直到收到服务端处理的返回结果才继续工作,我们绝大多数的程序都是这种方式。

IsOneWay=true和异步调用方式类似,但是不完全相同。还是有区别的:纯异步的调用方式,当客户端发出调用请求后,客户端不会被阻塞,而是可以继续工作,此时客户端不会理会请求的何时被处理了,或者说是不是会被WCF处理。OneWay的话是这样的,只有当WCF通知了客户端准备开始处理请求了此时客户端才可以继续工作,不被阻塞;但是如果客户端提交的请求一直不被WCF处理,处于挂起状态的话,此时客户端会处于阻塞状态。

 

IMyServiceA代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace BusinessServices
{
    
// NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in App.config.
    [ServiceContract]
    
public interface IMyServiceA
    {
        [OperationContract]
        
void ThrowException();
        [OperationContract(IsOneWay
=true)]
        
void ThrowExceptionOneWay();
    }
}

 

 

MyService代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace BusinessServices
{
    
// NOTE: If you change the class name "Service1" here, you must also update the reference to "Service1" in App.config.
    public class MyService : IMyServiceA
    {
        
public void ThrowException()
        {
            
throw new Exception("This Exception is thrown by Charles");
        }

        
public void ThrowExceptionOneWay()
        {
            
throw new Exception("This Exception is thrown by Charles");
        }
    }
}

 

Config代码
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  
<system.web>
    
<compilation debug="true" />
  
</system.web>
  
<!-- When deploying the service library project, the content of the config file must be added to the host's 
  app.config file. System.Configuration does not support config files for libraries. 
-->
  
<system.serviceModel>
    
<services>
      
<service behaviorConfiguration="BusinessServices.Service1Behavior"
        name
="BusinessServices.MyService">
        
<endpoint address="" binding="wsHttpBinding" contract="BusinessServices.IMyServiceA">
          
<identity>
            
<dns value="localhost" />
          
</identity>
        
</endpoint>
        
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        
<host>
          
<baseAddresses>
            
<add baseAddress="http://localhost:8731/WCF/Charlesliu" />
          
</baseAddresses>
        
</host>
      
</service>
    
</services>
    
<behaviors>
      
<serviceBehaviors>
        
<behavior name="BusinessServices.Service1Behavior">
          
<!-- To avoid disclosing metadata information, 
          set the value below to false and remove the metadata endpoint above before deployment 
-->
          
<serviceMetadata httpGetEnabled="True"/>
          
<!-- To receive exception details in faults for debugging purposes, 
          set the value below to true.  Set to false before deployment 
          to avoid disclosing exception information 
-->
          
<serviceDebug includeExceptionDetailInFaults="true" />
        
</behavior>
      
</serviceBehaviors>
    
</behaviors>
  
</system.serviceModel>
</configuration>

 

 

ClientTest代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace ClientTest
{
    
class Program
    {
        
static void Main(string[] args)
        {
            MyServiceReference.MyServiceAClient proxy 
= new MyServiceReference.MyServiceAClient();

            
try
            {
                Console.ForegroundColor 
= ConsoleColor.Yellow;
                Console.WriteLine(
"Calling proxy.ThrowException()");
                Console.WriteLine(
"");
                Console.ResetColor();

                proxy.ThrowException();

            }
            
catch (Exception ex)
            {

                Console.WriteLine(ex.GetType().ToString());
                Console.WriteLine(
"ERROR: {0}", ex.Message);
            }

            Console.WriteLine();
            Console.WriteLine(
"Proxy state after exception: {0}", proxy.State);
            Console.WriteLine();

            
if (proxy.State == CommunicationState.Faulted)
                proxy 
= new MyServiceReference.MyServiceAClient();

            
try
            {
                Console.ForegroundColor 
= ConsoleColor.Yellow;
                Console.WriteLine(
"Calling proxy.ThrowExceptionOneWay()");
                Console.ResetColor();

                proxy.ThrowExceptionOneWay();

            }
            
catch (Exception ex)
            {

                Console.WriteLine(ex.GetType().ToString());
                Console.WriteLine(
"ERROR:  {0}", ex.Message);
            }


            Console.WriteLine();
            Console.WriteLine(
"Proxy state after exception: {0}", proxy.State);
            Console.WriteLine();


            
try
            {
                proxy.Close();

            }
            
catch (Exception ex)
            {

                Console.WriteLine(ex.GetType().ToString());
                Console.WriteLine(
"ERROR:  {0}", ex.Message);
            }

            Console.WriteLine();
            Console.ForegroundColor 
= ConsoleColor.Blue;
            Console.WriteLine(
"Press <ENTER> to terminate Client.");
            Console.ReadLine();
        }
    }
}

运行后:

可以看到当<serviceDebug includeExceptionDetailInFaults="true" />时,客户端可以看到具体的Exception信息了,否则只能看到一个通用的信息。对于OnWay的调用Exception是没有显示出来的,因为在Exception还没有传到客户端之前,客户端代码继续往下运行了,这就是OneWay在try-catch中特点的体现。我们还可以发现当WCF服务有异常发生时,proxy实例的state会发生变化,所以在编程中要考虑怎样处理。

 

抛出Fault(Fault指的是SOAP的fault,这和.net的Exception是两个不同的概念):他的意思是把Fault作为数据对象发送到客户端,他的主要作用就是对异常做个包装。

• 未捕获异常表明服务模型发生了一个潜在的致命错误
• 类型:
– FaultException
– FaultException<T>
– MessageFault

FaultException
• 用于抛出简单的错误
• 可以提供错误原因与代码
• 能够提供额外的SOAP错误元素
代码
throw new FaultException("An invalid operation has occurred.");
throw new FaultException(new FaultReason("An invalid operation has occurred."));
throw new FaultException(new FaultReason("An invalid operation has occurred."), FaultCode.CreateSenderFaultCode(null));
FaultException<T>
• 为SOAP fault提供一个强类型的<T>的简单方法
– T 必须是数据契约或者可序列化类型
• 也能够使用CLR异常类型
– 不利于互操作
throw new FaultException<InvalidOperationException>(new InvalidOperationException("An invalid operation has occured."), "Invalid operation.",FaultCode.CreateSenderFaultCode(null));

 

• 为服务错误创建数据契约
–更好地用于互操作

代码
[ServiceContract(Name = "PhotoUploadContract", Namespace =
"http://www.thatindigogirl.com/samples/2006/06")]
public interface IPhotoUpload
{
    [OperationContract]
    [FaultContract(
typeof(ReceiverFault))]
    [FaultContract(
typeof(SenderFault))]
    
void UploadPhoto(PhotoLink fileInfo, byte[] fileData);
}
代码
[DataContract(Namespace =
"http://schemas.thatindigogirl.com/samples/2006/06")]
public class ReceiverFault
{
    
private string m_message;
    
private string m_description;
    [DataMember(Name 
= "Message", IsRequired = true, Order = 0)]
    
public string Message
    {
        
get { return m_message; }
        
set { m_message = value; }
    }
    [DataMember(Name 
= "Description", IsRequired = false, Order = 1)]
    
public string Description
    {
        
get { return m_description; }
        
set { m_description = value; }
    }
}

[DataContract(Namespace 
= "http://schemas.thatindigogirl.com/samples/2006/06")]
public class SenderFault
{
    
private string m_message;
    
private string m_description;
    
private List<string> m_failedBodyElements = new List<string>();
    [DataMember(Name 
= "Message", IsRequired = true, Order = 0)]
    
public string Message
    {
        
get { return m_message; }
        
set { m_message = value; }
    }
    [DataMember(Name 
= "Description", IsRequired = false, Order = 1)]
    
public string Description
    {
        
get { return m_description; }
        
set { m_description = value; }
    }
    [DataMember(Name 
= "FailedBodyElements", IsRequired = true, Order = 2)]
    
public List<string> FailedBodyElements
    {
        
get { return m_failedBodyElements; }
        
set { m_failedBodyElements = value; }
    }
}
MessageFault
• SOAP Fault的CLR表示
–为了更好地控制错误元素
代码
MessageFault mfault = MessageFault.CreateFault(
FaultCode.CreateSenderFaultCode(
null),
new FaultReason("Invalid operation"),
new InvalidOperationException("An invalid operation has
occurred."), null, """");
FaultException fe = FaultException.CreateFault(mfault,
typeof(InvalidOperationException));
throw fe;

 

看一个Demo:

 

代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace BusinessServices
{
    [ServiceContract]
    
public interface IFaultExceptionService
    {
        [OperationContract]
        [FaultContract(
typeof(InvalidOperationException))]
        
void ThrowSimpleFault();

        [OperationContract]
        [FaultContract(
typeof(InvalidOperationException))]
        
void ThrowMessageFault();

        [OperationContract()]
        [FaultContract(
typeof(InvalidOperationException))]
        
void ThrowFaultException();

    }
}

 

 

代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.ServiceModel.Channels;

namespace BusinessServices
{
    
public class FaultExceptionService : IFaultExceptionService
    {
        
#region IService Members

        
public void ThrowSimpleFault()
        {

            
throw new FaultException("An invalid operation has occurred.");
        }

        
public void ThrowMessageFault()
        {
            InvalidOperationException error 
= new InvalidOperationException("An invalid operation has occurred.");
            MessageFault mfault 
= MessageFault.CreateFault(new FaultCode("Server"new FaultCode(String.Format("Server.{0}", error.GetType().Name))), new FaultReason(error.Message), error);
            FaultException fe 
= FaultException.CreateFault(mfault, typeof(InvalidOperationException));

            
throw fe;
        }

        
public void ThrowFaultException()
        {
            FaultException
<InvalidOperationException> fe = new FaultException<InvalidOperationException>(new InvalidOperationException("An invalid operation has occured."), "Invalid operation."new FaultCode("Server"new FaultCode(String.Format("Server.{0}"typeof(NotImplementedException)))));
            
throw fe;
        }

        
#endregion
    }
}

 

代码
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  
<system.web>
    
<compilation debug="true" />
  
</system.web>
  
<!-- When deploying the service library project, the content of the config file must be added to the host's 
  app.config file. System.Configuration does not support config files for libraries. 
-->
  
<system.serviceModel>
    
<services>
      
<service behaviorConfiguration="BusinessServices.Service1Behavior"
        name
="BusinessServices.FaultExceptionService">
        
<endpoint address="" binding="wsHttpBinding" contract="BusinessServices.IFaultExceptionService">
          
<identity>
            
<dns value="localhost" />
          
</identity>
        
</endpoint>
        
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        
<host>
          
<baseAddresses>
            
<add baseAddress="http://localhost:8731/WCF/Charlesliu" />
          
</baseAddresses>
        
</host>
      
</service>
    
</services>
    
<behaviors>
      
<serviceBehaviors>
        
<behavior name="BusinessServices.Service1Behavior">
          
<!-- To avoid disclosing metadata information, 
          set the value below to false and remove the metadata endpoint above before deployment 
-->
          
<serviceMetadata httpGetEnabled="True"/>
          
<!-- To receive exception details in faults for debugging purposes, 
          set the value below to true.  Set to false before deployment 
          to avoid disclosing exception information 
-->
          
<serviceDebug includeExceptionDetailInFaults="false" />
        
</behavior>
      
</serviceBehaviors>
    
</behaviors>
  
</system.serviceModel>
</configuration>

 注意:includeExceptionDetailInFaults="false"

Client代码:

代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace ClientTest
{
    
class Program
    {
        
static void Main(string[] args)
        {

            MyServiceReference.FaultExceptionServiceClient proxy 
= new ClientTest.MyServiceReference.FaultExceptionServiceClient();

            
try
            {
                Console.ForegroundColor 
= ConsoleColor.Yellow;
                Console.WriteLine(
"Calling proxy.ThrowSimpleFault()");
                Console.WriteLine(
"");
                Console.ResetColor();

                proxy.ThrowSimpleFault();

            }
            
catch (FaultException fe)
            {

                Console.WriteLine(fe.GetType().ToString());
                Console.WriteLine(
"ERROR: {0}", fe.Message);

            }
            
catch (Exception ex)
            {

                Console.WriteLine(ex.GetType().ToString());
                Console.WriteLine(
"ERROR: {0}", ex.Message);
            }

            Console.WriteLine();

            
try
            {
                Console.ForegroundColor 
= ConsoleColor.Yellow;
                Console.WriteLine(
"Calling proxy.ThrowMessageFault()");
                Console.ResetColor();

                proxy.ThrowMessageFault();

            }
            
catch (FaultException fe)
            {

                Console.WriteLine(fe.GetType().ToString());
                Console.WriteLine(
"ERROR: {0}", fe.Message);

            }
            
catch (Exception ex)
            {

                Console.WriteLine(ex.GetType().ToString());
                Console.WriteLine(
"ERROR:  {0}", ex.Message);
            }

            Console.WriteLine();

            
try
            {
                Console.ForegroundColor 
= ConsoleColor.Yellow;
                Console.WriteLine(
"Calling proxy.ThrowFaultException()");
                Console.ResetColor();

                proxy.ThrowFaultException();
            }
            
catch (FaultException fe)
            {

                Console.WriteLine(fe.GetType().ToString());
                Console.WriteLine(
"ERROR: {0}", fe.Message);

            }
            
catch (Exception ex)
            {

                Console.WriteLine(ex.GetType().ToString());
                Console.WriteLine(
"ERROR:  {0}", ex.Message);
            }
            proxy.Close();
            Console.WriteLine();
            Console.ForegroundColor 
= ConsoleColor.Blue;
            Console.WriteLine(
"Press <ENTER> to terminate Client.");
            Console.ReadLine();
        }
    }
}

运行结果:

Fault声明 (其实上面的例子已经给出了声明的方法)
• 缺省情况下,客户端不会意识到可能抛出的错误操作
• Fault可以作为WSDL的一部分进行声明
–描述<T> 元素
– 生成包含可适用类型信息的代理
• 提供带有强类型异常的客户端(这个好处是可以根据特定的异常,进行相应的处理,比如:内存异常,IO异常,网络异常,进行相应的逻辑处理)

FaultContractAttribute
• 应用于服务契约或者操作
• 为Fault细节提供数据契约或者可序列化类型
• 操作应该抛出声明的Fault

Fault声明的方法
• 使用CLR异常提供细节信息
• 定义一些带有特性规范的核心数据契约
– ReceiverFault
– SenderFault
• 为Fault的每个特定类型定义数据契约
– FileUploadFault
– CustomerDataFault

也就是说上例中的服务端定义的具体Fault和客户端对应起来是一种好的方法,如服务端声明的是InvalidOperationException的Fault,客户款的catch改为: catch (FaultException<InvalidOperationException> fe)。

 

实现错误处理逻辑

• WCF支持集中化错误处理
–报告未捕捉的异常
– 将适当的异常转换为Fault
–修改Fault保证一致性
• 为IErrorHandler提供实现
• 添加到配置好的服务行为(service behaviors)中

 

• IErrorHandler 方法:作用就是实现了把CLR异常到Fault的包装
– ProvideFault() (这个方法的执行是在WCFServer端发生异常和WCFserver把异常传递给客户端这两个阶段之间。)
• 在发生异常后,异常消息返回并且终止会话前被调用
• 用于修改和包装返回的异常消息
• 客户端处于阻塞状态
– 不要在其内部做长时间的处理,以免客户端超时
– HandleError() (这个方法当异常返回给客户端后,在服务端被触发)
• 在异常返回给客户端之后被触发
• 不会阻塞通讯
• 通常用于记录异常,在服务端进行错误提示等操作

具体的看一个Demo:

 

IMyService 代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace WcfServiceLibrary1
{
    
// NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in App.config.
    [ServiceContract]
    
public interface IMyService
    {
        [OperationContract]
        [FaultContract(
typeof(ReceiverFaultDetail))]
        [FaultContract(
typeof(SenderFaultDetail))]
        
void GetData(int flag);
    }
}

 

 

MyService 代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.ServiceModel.Description;
using System.Collections.ObjectModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;

namespace WcfServiceLibrary1
{
    
// NOTE: If you change the class name "Service1" here, you must also update the reference to "Service1" in App.config.
    public class MyService : IMyService
    {
        
public void GetData(int flag)
        {
            
if (flag == 0)
            {
                
throw new InvalidOperationException("error - InvalidOperationException");
            }
        }
    }
}

 

 

Fault.cs 代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace WcfServiceLibrary1
{
    [DataContract]
    
public class ReceiverFaultDetail
    {

        
private string m_message;
        
private string m_description;
        
private bool m_contactAdministrator;

        
public ReceiverFaultDetail(string message, bool contactAdmin)
            : 
this(message, "", contactAdmin)
        {
        }

        
public ReceiverFaultDetail(string message, string description, bool contactAdmin)
        {
            
this.m_message = message;
            
this.m_description = description;
            
this.m_contactAdministrator = contactAdmin;
        }

        [DataMember(Name 
= "Message", IsRequired = true, Order = 0)]
        
public string Message
        {
            
get { return m_message; }
            
set { m_message = value; }
        }

        [DataMember(Name 
= "Description", IsRequired = false, Order = 1)]
        
public string Description
        {
            
get { return m_description; }
            
set { m_description = value; }
        }

        [DataMember(Name 
= "ContactAdministrator", IsRequired = true, Order = 2)]
        
public bool ContactAdministrator
        {
            
get { return m_contactAdministrator; }
            
set { m_contactAdministrator = value; }
        }
    }

    [DataContract]
    
public class SenderFaultDetail
    {

        
private string m_message;
        
private string m_description;
        
private List<string> m_failedBodyElements = new List<string>();


        
public SenderFaultDetail(string message, List<string> bodyElements)
            : 
this(message, "", bodyElements)
        {
        }

        
public SenderFaultDetail(string message)
            : 
this(message, ""null)
        {
        }

        
public SenderFaultDetail(string message, string description, List<string> bodyElements)
        {
            
this.m_message = message;
            
this.m_description = description;

            
if (bodyElements != null)
                
this.m_failedBodyElements = bodyElements;
        }

        [DataMember(Name 
= "Message", IsRequired = true, Order = 0)]
        
public string Message
        {
            
get { return m_message; }
            
set { m_message = value; }
        }

        [DataMember(Name 
= "Description", IsRequired = false, Order = 1)]
        
public string Description
        {
            
get { return m_description; }
            
set { m_description = value; }
        }

        [DataMember(Name 
= "FailedBodyElements", IsRequired = true, Order = 2)]
        
public List<string> FailedBodyElements
        {
            
get { return m_failedBodyElements; }
            
set { m_failedBodyElements = value; }
        }
    }
}

 

 

ServiceErrorHandler 代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Configuration;
using System.Collections.ObjectModel;

namespace WcfServiceLibrary1
{
    
public class ServiceErrorHandlerBehaviorExtensionElement : BehaviorExtensionElement
    {
        
public override Type BehaviorType
        {
            
get { return typeof(ServiceErrorHandler); }
        }

        
protected override object CreateBehavior()
        {
            
return new ServiceErrorHandler();
        }
    }

    
public class ServiceErrorHandler : ServiceErrorHandlerBehaviorExtensionElement, IErrorHandler, IServiceBehavior
    {
        
public bool HandleError(Exception error)
        {
            Console.WriteLine(
"HandleError");
            
return true;
        }

        
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            
if (fault == null)
            {
                
if (error is InvalidOperationException)
                {
                    FaultException
<ReceiverFaultDetail> fe = new FaultException<ReceiverFaultDetail>(new ReceiverFaultDetail(error.Message, true), error.Message, FaultCode.CreateReceiverFaultCode(new FaultCode("InvalidOperationException")));
                    MessageFault mf 
= fe.CreateMessageFault();

                    fault 
= Message.CreateMessage(version, mf, fe.Action);
                }
            }
        }

        
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
        {
            
return;
        }

        
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            
foreach (ChannelDispatcher channDisp in serviceHostBase.ChannelDispatchers)
            {
                channDisp.ErrorHandlers.Add(
this);
            }
        }

        
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            
foreach (var svcEndpoint in serviceDescription.Endpoints)
            {
                
if (svcEndpoint.Contract.Name != "IMetadataExchange")
                {
                    
foreach (var opDesc in svcEndpoint.Contract.Operations)
                    {
                        
if (opDesc.Faults.Count == 0)
                        {
                            
string msg =
                                
string.Format("ServiceErrorHandlerBehavior requires a FaultContract(typeof(ApplicationFault)) on each operation contract. The {0} contains no FaultContracts.", opDesc.Name);
                            
throw new InvalidOperationException(msg);
                        }

                        var fcExists 
= from fc in opDesc.Faults
                                       
where fc.DetailType == typeof(ReceiverFaultDetail)
                                       select fc;

                        
if (fcExists.Count() == 0)
                        {
                            
string msg = string.Format("ServiceErrorHandlerBehavior requires a FaultContract(typeof(ApplicationFault)) on each operation contract.");
                            
throw new InvalidOperationException(msg);
                        }
                    }
                }
            }
        }
    }
}

 

 

只完成上边的代码还不够,由于使用了ExtensionElement所以Config文件里也要相应的配置

App.Config 代码
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  
<system.web>
    
<compilation debug="true" />
  
</system.web>
  
<!-- When deploying the service library project, the content of the config file must be added to the host's 
  app.config file. System.Configuration does not support config files for libraries. 
-->
  
<system.serviceModel>

    <extensions>
      
<behaviorExtensions>
        
<add name="ServiceErrorHandler" type="WcfServiceLibrary1.ServiceErrorHandlerBehaviorExtensionElement, WcfServiceLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      
</behaviorExtensions>
    
</extensions>
    
    
<services>
      
<service behaviorConfiguration="WcfServiceLibrary1.Service1Behavior"
        name
="WcfServiceLibrary1.MyService">
        
<endpoint address="" binding="wsHttpBinding" contract="WcfServiceLibrary1.IMyService">
          
<identity>
            
<dns value="localhost" />
          
</identity>
        
</endpoint>
        
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        
<host>
          
<baseAddresses>
            
<add baseAddress="http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary1/Service1/" />
          
</baseAddresses>
        
</host>
      
</service>
    
</services>
    
<behaviors>
      
<serviceBehaviors>
        
<behavior name="WcfServiceLibrary1.Service1Behavior">
          
<!-- To avoid disclosing metadata information, 
          set the value below to false and remove the metadata endpoint above before deployment 
-->
          
<serviceMetadata httpGetEnabled="True"/>
          
<!-- To receive exception details in faults for debugging purposes, 
          set the value below to true.  Set to false before deployment 
          to avoid disclosing exception information 
-->
          
<serviceDebug includeExceptionDetailInFaults="False" />
          <ServiceErrorHandler/>
        
</behavior>
      
</serviceBehaviors>
    
</behaviors>
  
</system.serviceModel>
</configuration>

 

客户端代码:

 

Client 代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ConsoleApplication1.ServiceReference1;
using System.ServiceModel;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                ServiceReference1.MyServiceClient proxy = new ConsoleApplication1.ServiceReference1.MyServiceClient();
                proxy.GetData(0);
            }
            catch (FaultException
<ReceiverFaultDetail> ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

 

 

调试程序可以看出,函数运行的顺序为:

客户端调用服务方法发生异常后,现执行ProvideFault方法,然后执行HandleError方法,客户端收到exception执行Catch。

 

总结下:

 

错误处理策略(1)
• 简单的实现
–从业务逻辑/数据访问层抛出CLR异常
– 在服务层捕捉到异常并且转化为适当的Fault
–使用handler记录未捕捉的异常

错误处理策略(2)
• 带有CLR异常的实现
–从业务逻辑/数据访问层抛出CLR异常
– 将CLR异常声明为Fault
–创建错误处理器(error handler)自动将已知的异
常转换为Fault
–记录未捕捉的异常

错误处理策略(3)
• 可互操作的方法
– 从业务逻辑/数据访问层抛出自定义CLR异常
– 将异常定义为数据契约
– 声明自定义异常类型为Fault
– 创建错误处理器(error handler)将自定义异常转换为Fault
– 记录非自定义异常
• 注意:
– 需要对错误处理策略进行良好的定义
• 中立性

 

posted @ 2010-03-09 14:09  烟鬼  阅读(4357)  评论(0编辑  收藏  举报