我基本上实现IErrorHandler
接口来捕获WCF服务中的各种异常,并通过实现ProvideFault
方法将其发送到客户端。
然而,我面临一个关键问题。所有异常都以FaultException
的形式发送给客户端,但这会禁止客户端处理他可能在服务中定义的特定异常。
考虑:SomeException
已在OperationContract
个实现之一中定义并抛出。抛出异常时,使用以下代码将其转换为错误:
var faultException = new FaultException(error.Message);
MessageFault messageFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, messageFault, faultException.Action);
这会将错误作为字符串发送,但客户端必须捕获一般异常,如:
try{...}
catch(Exception e){...}
而不是:
try{...}
catch(SomeException e){...}
不仅像SomeException这样的自定义异常,而且使用上述过程也无法捕获像InvalidOperationException这样的系统异常。
有关如何实现此行为的任何想法?
答案 0 :(得分:10)
在WCF中,希望使用描述为契约的特殊异常,因为您的客户端可能不是具有标准.NET异常信息的.NET应用程序。
为此,您可以在服务中定义FaultContract
,然后使用FaultException类。
服务器端
[ServiceContract]
public interface ISampleService
{
[OperationContract]
[FaultContractAttribute(typeof(MyFaultMessage))]
string SampleMethod(string msg);
}
[DataContract]
public class MyFaultMessage
{
public MyFaultMessage(string message)
{
Message = message;
}
[DataMember]
public string Message { get; set; }
}
class SampleService : ISampleService
{
public string SampleMethod(string msg)
{
throw new FaultException<MyFaultMessage>(new MyFaultMessage("An error occurred."));
}
}
此外,您可以在配置文件中指定服务器在其FaultExceptions中返回异常详细信息,但不建议在生产应用程序中使用此方法:
<serviceBehaviors>
<behavior>
<!-- 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>
之后,您可以重写处理异常的方法:
var faultException = error as FaultException;
if (faultException == null)
{
//If includeExceptionDetailInFaults = true, the fault exception with details will created by WCF.
return;
}
MessageFault messageFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, messageFault, faultException.Action);
<强>客户端:强>
try
{
_client.SampleMethod();
}
catch (FaultException<MyFaultMessage> e)
{
//Handle
}
catch (FaultException<ExceptionDetail> exception)
{
//Getting original exception detail if includeExceptionDetailInFaults = true
ExceptionDetail exceptionDetail = exception.Detail;
}
答案 1 :(得分:8)
这篇文章可能有所帮助:
http://www.olegsych.com/2008/07/simplifying-wcf-using-exceptions-as-faults/
当我不想枚举可能抛出的异常时,我已经成功使用的一种方法是创建一个PassthroughExceptionHandlingBehavior类,它实现服务器端的IErrorHandler行为和客户端的IClientMessageInspector。 IErrorHandler行为将异常序列化为故障消息。 IClientMessageInspector反序列化并抛出异常。
您必须将此行为附加到WCF客户端和WCF服务器。您可以使用配置文件或通过将[PassthroughExceptionHandlingBehavior]属性应用于合同来附加行为。
这是行为类:
public class PassthroughExceptionHandlingBehavior : Attribute, IClientMessageInspector, IErrorHandler,
IEndpointBehavior, IServiceBehavior, IContractBehavior
{
#region IClientMessageInspector Members
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
if (reply.IsFault)
{
// Create a copy of the original reply to allow default processing of the message
MessageBuffer buffer = reply.CreateBufferedCopy(Int32.MaxValue);
Message copy = buffer.CreateMessage(); // Create a copy to work with
reply = buffer.CreateMessage(); // Restore the original message
var exception = ReadExceptionFromFaultDetail(copy) as Exception;
if (exception != null)
{
throw exception;
}
}
}
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
return null;
}
private static object ReadExceptionFromFaultDetail(Message reply)
{
const string detailElementName = "detail";
using (XmlDictionaryReader reader = reply.GetReaderAtBodyContents())
{
// Find <soap:Detail>
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element &&
detailElementName.Equals(reader.LocalName, StringComparison.InvariantCultureIgnoreCase))
{
return ReadExceptionFromDetailNode(reader);
}
}
// Couldn't find it!
return null;
}
}
private static object ReadExceptionFromDetailNode(XmlDictionaryReader reader)
{
// Move to the contents of <soap:Detail>
if (!reader.Read())
{
return null;
}
// Return the deserialized fault
try
{
NetDataContractSerializer serializer = new NetDataContractSerializer();
return serializer.ReadObject(reader);
}
catch (SerializationException)
{
return null;
}
}
#endregion
#region IErrorHandler Members
public bool HandleError(Exception error)
{
return false;
}
public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message fault)
{
if (error is FaultException)
{
// Let WCF do normal processing
}
else
{
// Generate fault message manually including the exception as the fault detail
MessageFault messageFault = MessageFault.CreateFault(
new FaultCode("Sender"),
new FaultReason(error.Message),
error,
new NetDataContractSerializer());
fault = Message.CreateMessage(version, messageFault, null);
}
}
#endregion
#region IContractBehavior Members
public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
ApplyClientBehavior(clientRuntime);
}
public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
ApplyDispatchBehavior(dispatchRuntime.ChannelDispatcher);
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
}
#endregion
#region IEndpointBehavior Members
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
ApplyClientBehavior(clientRuntime);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
ApplyDispatchBehavior(endpointDispatcher.ChannelDispatcher);
}
public void Validate(ServiceEndpoint endpoint)
{
}
#endregion
#region IServiceBehavior Members
public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
{
ApplyDispatchBehavior(dispatcher);
}
}
public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
}
#endregion
#region Behavior helpers
private static void ApplyClientBehavior(System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
foreach (IClientMessageInspector messageInspector in clientRuntime.MessageInspectors)
{
if (messageInspector is PassthroughExceptionHandlingBehavior)
{
return;
}
}
clientRuntime.MessageInspectors.Add(new PassthroughExceptionHandlingBehavior());
}
private static void ApplyDispatchBehavior(System.ServiceModel.Dispatcher.ChannelDispatcher dispatcher)
{
// Don't add an error handler if it already exists
foreach (IErrorHandler errorHandler in dispatcher.ErrorHandlers)
{
if (errorHandler is PassthroughExceptionHandlingBehavior)
{
return;
}
}
dispatcher.ErrorHandlers.Add(new PassthroughExceptionHandlingBehavior());
}
#endregion
}
#region PassthroughExceptionHandlingElement class
public class PassthroughExceptionExtension : BehaviorExtensionElement
{
public override Type BehaviorType
{
get { return typeof(PassthroughExceptionHandlingBehavior); }
}
protected override object CreateBehavior()
{
System.Diagnostics.Debugger.Launch();
return new PassthroughExceptionHandlingBehavior();
}
}
#endregion
答案 2 :(得分:0)
FaultException有一个Code属性,你可以使用它进行异常处理。
try
{
...
}
catch (FaultException ex)
{
switch(ex.Code)
{
case 0:
break;
...
}
}