SOAP 错误将错误条件信息从服务传送到客户端,并且在双工情况下,将这些信息以互操作方式从客户端传送到服务。 通常,服务定义自定义故障内容,并指定哪些操作可以返回这些故障。 (有关详细信息,请参阅 “定义和指定错误”。)本主题讨论服务或双向客户端在发生相应错误条件时如何发送这些错误,以及客户端或服务应用程序如何处理这些错误的方法。 有关 Windows Communication Foundation (WCF) 应用程序中错误处理的概述,请参阅 “在协定和服务中指定和处理错误”。
发送 SOAP 错误
声明的 SOAP 错误是指具有 System.ServiceModel.FaultContractAttribute 指定自定义 SOAP 错误类型的操作。 未声明的 SOAP 错误是指那些未在相应操作的协定中指定的错误。
发送已声明的错误
若要发送声明的 SOAP 故障,请检测适用于该 SOAP 故障的错误条件,并抛出一个新的 System.ServiceModel.FaultException<TDetail>,其中类型参数是为该操作中 FaultContractAttribute 指定的类型的新对象。 下面的代码示例展示如何使用 FaultContractAttribute 来指定 SampleMethod
操作可以返回一种包含 GreetingFault
细节类型的 SOAP 错误。
[OperationContract]
[FaultContractAttribute(
typeof(GreetingFault),
Action="http://www.contoso.com/GreetingFault",
ProtectionLevel=ProtectionLevel.EncryptAndSign
)]
string SampleMethod(string msg);
<OperationContract, FaultContractAttribute(GetType(GreetingFault), Action:="http://www.contoso.com/GreetingFault", ProtectionLevel:=ProtectionLevel.EncryptAndSign)> _
Function SampleMethod(ByVal msg As String) As String
若要向客户端传送 GreetingFault
错误信息,需要捕捉相应的错误条件,并引发一个类型为 System.ServiceModel.FaultException<TDetail> 的新 GreetingFault
,其参数为一个新的 GreetingFault
对象,如下面的代码示例所示。 如果客户端是 WCF 客户端应用程序,它会将其视为托管异常,其中,异常类型是 System.ServiceModel.FaultException<TDetail> 类型的 GreetingFault
。
throw new FaultException<GreetingFault>(new GreetingFault("A Greeting error occurred. You said: " + msg));
Throw New FaultException(Of GreetingFault)(New GreetingFault("A Greeting error occurred. You said: " & msg))
End If
发送未声明的错误
发送未声明的错误对于快速诊断和调试 WCF 应用程序中的问题非常有用,但其作为调试工具的有用性有限。 更通常,在调试时,建议使用该 ServiceDebugBehavior.IncludeExceptionDetailInFaults 属性。 在将此值设置为 true 时,客户端会将此类错误视为类型为 FaultException<TDetail> 的 ExceptionDetail 异常。
重要
由于托管异常可以公开内部应用程序信息,因此设置ServiceBehaviorAttribute.IncludeExceptionDetailInFaults或ServiceDebugBehavior.IncludeExceptionDetailInFaultstrue
允许 WCF 客户端获取有关内部服务作异常的信息,包括个人身份或其他敏感信息。
因此,仅建议将 ServiceBehaviorAttribute.IncludeExceptionDetailInFaults 或 ServiceDebugBehavior.IncludeExceptionDetailInFaults 设置为 true
以暂时调试服务应用程序。 此外,以这种方式返回未处理的托管异常的方法的 WSDL 并不包含类型为 FaultException<TDetail> 的 ExceptionDetail 的协定。 客户端必须准备好可能出现未知的 SOAP 错误(作为 System.ServiceModel.FaultException 对象返回到 WCF 客户端),以便正确获取调试信息。
若要发送未声明的 SOAP 错误,请引发对象 System.ServiceModel.FaultException (即非泛型类型 FaultException<TDetail>),并将字符串传递给构造函数。 这会作为 System.ServiceModel.FaultException 抛出的异常向 WCF 客户端应用程序公开,其中可以通过调用 FaultException<TDetail>.ToString 方法获取该字符串。
注释
如果您声明一个类型为字符串的 SOAP 错误,并在服务中将其抛出为 FaultException<TDetail>,其中类型参数是 System.String,那么字符串值将被分配给 FaultException<TDetail>.Detail 属性,而不能从 FaultException<TDetail>.ToString 访问。
处理错误
在 WCF 客户端中,客户端应用程序特别关注的通信过程中发生的 SOAP 错误会作为托管异常抛出。 尽管在执行任何程序的过程中可能会发生许多异常,但使用 WCF 客户端编程模型的应用程序可能会由于通信而处理以下两种类型的异常。
TimeoutException对象在操作超过指定的超时期限时被抛出。
CommunicationException 对象会在服务端或客户端出现某些可恢复的通信错误条件时被抛出。
该 CommunicationException 类有两个重要的派生类型, FaultException 以及泛型 FaultException<TDetail> 类型。
当侦听器接收到操作协定中未预料到或未指定的错误时,引发 FaultException 异常;这通常在对应用程序进行调试并且服务的 ServiceDebugBehavior.IncludeExceptionDetailInFaults 属性设置为 true
时发生。
FaultException<TDetail> 当客户端在响应双向操作(即具有 OperationContractAttribute 属性,且 IsOneWay 设置为 false
的方法)时收到操作契约中指定的错误,将抛出异常。
注释
当 WCF 服务将ServiceBehaviorAttribute.IncludeExceptionDetailInFaults或ServiceDebugBehavior.IncludeExceptionDetailInFaults属性设置为true
时,客户端会将其视为一种未声明的FaultException<TDetail>类型ExceptionDetail。 客户端可以捕捉这一特定错误,也可以在 FaultException 的 catch 块中处理该错误。
通常,只有FaultException<TDetail>、TimeoutException和CommunicationException异常对客户端和服务有意义。
注释
当然,其他异常确实会发生。 意外异常包括灾难性故障,例如 System.OutOfMemoryException;通常应用程序不应捕获此类方法。
按照正确的顺序捕捉错误异常
由于 FaultException<TDetail> 派生自 FaultException并 FaultException 派生自 CommunicationException,因此必须按正确的顺序捕获这些异常。 例如,如果在 try/catch 块中首先捕获 CommunicationException,那么所有指定和未指定的 SOAP 错误都会在该处得到处理;用于处理自定义 FaultException<TDetail> 异常的任何后续 catch 块将永远不会被调用。
请记住,一个操作可以返回指定数量的任意错误。 每个错误都是唯一的类型,必须单独处理。
在关闭通道时处理异常
前面的大多数讨论都与在处理应用程序消息的过程中发送的错误有关,即客户端应用程序在调用 WCF 客户端对象的操作时显式发送的消息。
即使对于本地对象,释放对象也会引发或者屏蔽在回收过程中发生的异常。 使用 WCF 客户端对象时可能会出现类似情况。 在调用操作时,通过已建立的连接发送消息。 如果无法完全关闭连接或连接已经关闭,那么即使所有操作都正确返回,关闭通道也会引发异常。
通常,客户端对象通道通过以下方式之一关闭:
在回收 WCF 客户端对象时。
客户端应用程序调用 ClientBase<TChannel>.Close时。
客户端应用程序调用 ICommunicationObject.Close时。
当客户端应用程序调用会话的终止操作时。
在所有情况下,关闭通道会指示通道开始关闭可能发送消息以支持应用程序级别的复杂功能的任何基础通道。 例如,当协定需要使用会话时,绑定便会尝试通过与服务通道交换消息来建立会话,直到会话建立。 关闭通道后,基础会话通道会通知服务会话终止。 在这种情况下,如果通道已中止、关闭或其他不可用(例如,取消连接网络电缆时),客户端通道无法通知服务通道会话已终止,并且可能导致异常。
在需要时中止通道
因为关闭通道还会引发异常,所以,建议您除了按照正确的顺序捕捉错误异常以外,还需要在 catch 块中中止在进行调用时使用的通道。
如果错误传达特定于某项操作的错误信息,并且仍然有可能被其他人使用,则无需中止通道(的确这些情况很少见)。 在所有其他情况下,建议您中止该频道。 有关演示所有这些点的示例,请参阅 预期异常。
下面的代码示例演示如何在基本客户端应用程序中处理 SOAP 错误异常,包括声明的错误和未声明的错误。
注释
此示例代码不使用构造 using
。 由于关闭通道可能会引发异常,因此建议应用程序先创建 WCF 客户端,然后在同一尝试块中打开、使用和关闭 WCF 客户端。 有关详细信息,请参阅 WCF 客户端概述 和使用 Close 和 Abort 释放 WCF 客户端资源。
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Microsoft.WCF.Documentation;
public class Client
{
public static void Main()
{
// Picks up configuration from the config file.
SampleServiceClient wcfClient = new SampleServiceClient();
try
{
// Making calls.
Console.WriteLine("Enter the greeting to send: ");
string greeting = Console.ReadLine();
Console.WriteLine("The service responded: " + wcfClient.SampleMethod(greeting));
Console.WriteLine("Press ENTER to exit:");
Console.ReadLine();
// Done with service.
wcfClient.Close();
Console.WriteLine("Done!");
}
catch (TimeoutException timeProblem)
{
Console.WriteLine("The service operation timed out. " + timeProblem.Message);
Console.ReadLine();
wcfClient.Abort();
}
catch (FaultException<GreetingFault> greetingFault)
{
Console.WriteLine(greetingFault.Detail.Message);
Console.ReadLine();
wcfClient.Abort();
}
catch (FaultException unknownFault)
{
Console.WriteLine("An unknown exception was received. " + unknownFault.Message);
Console.ReadLine();
wcfClient.Abort();
}
catch (CommunicationException commProblem)
{
Console.WriteLine("There was a communication problem. " + commProblem.Message + commProblem.StackTrace);
Console.ReadLine();
wcfClient.Abort();
}
}
}
Imports System.ServiceModel
Imports System.ServiceModel.Channels
Imports Microsoft.WCF.Documentation
Public Class Client
Public Shared Sub Main()
' Picks up configuration from the config file.
Dim wcfClient As New SampleServiceClient()
Try
' Making calls.
Console.WriteLine("Enter the greeting to send: ")
Dim greeting As String = Console.ReadLine()
Console.WriteLine("The service responded: " & wcfClient.SampleMethod(greeting))
Console.WriteLine("Press ENTER to exit:")
Console.ReadLine()
' Done with service.
wcfClient.Close()
Console.WriteLine("Done!")
Catch timeProblem As TimeoutException
Console.WriteLine("The service operation timed out. " & timeProblem.Message)
Console.ReadLine()
wcfClient.Abort()
Catch greetingFault As FaultException(Of GreetingFault)
Console.WriteLine(greetingFault.Detail.Message)
Console.ReadLine()
wcfClient.Abort()
Catch unknownFault As FaultException
Console.WriteLine("An unknown exception was received. " & unknownFault.Message)
Console.ReadLine()
wcfClient.Abort()
Catch commProblem As CommunicationException
Console.WriteLine("There was a communication problem. " & commProblem.Message + commProblem.StackTrace)
Console.ReadLine()
wcfClient.Abort()
End Try
End Sub
End Class