对于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
– 记录非自定义异常
• 注意:
– 需要对错误处理策略进行良好的定义
• 中立性