将带有 InnerException 的异常从服务器传输到客户端
本文关键字:服务器 传输 客户端 异常 InnerException | 更新日期: 2023-09-27 18:34:01
我有错误处理程序,可以在FaultException中转换异常并将其发送到客户端。法典:
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
var exceptionDetail = new ExceptionDetail(error);
var faultException = new FaultException<ExceptionDetail>(exceptionDetail, error.Message);
MessageFault messageFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, messageFault, faultException.Action);
}
但在这种情况下,服务在没有 InnerException 的情况下获得 Exception,并且 InnerException 可能包含另一个 InnerException。是否可以将服务器上发生的异常传递给客户端而不转换为故障异常,或者确保使用所有内部异常的故障异常进行传输。
把Web服务/"服务器"上发生的.NET Exception
打包/序列化成一个FaultException
,然后当"客户端"收到它时,它被解压缩并作为原始的常规.NET Exception
提出......而不仅仅是一个FaultException
- 这被称为"异常编组"。
当您无法修改客户端代码以添加FaultException
处理时,或者您不希望客户端代码被"Webservice"特定知识污染时,有时需要这样做,即您的代码只处理 .NET 异常。
(注意:这是绕过"异常屏蔽"...这有点不受欢迎,因为您可能会在传输到客户端的异常数据中暴露详细信息,这可能会允许攻击媒介......此外,它可能会使您的"网络服务"的互操作性降低......即非 .NET 客户端需要知道如何理解序列化的 .NET 异常信息(。
在服务器上
因此,在"服务器"上,您对向服务添加实现IErrorHandler
的行为有正确的想法。
但是您应该尝试"序列化"Exception
但如果无法完成,则应回退到Exception.Message
。
几种不同的ProvideFault
实现可以做到这一点......选择你最喜欢的一个。
http://www.olegsych.com/2008/07/simplifying-wcf-using-exceptions-as-faults/
https://code.google.com/p/ppwcode/source/browse/dotnet/Vernacular/Persistence/trunk/I/Dao/Wcf/Helpers/Errors/ExceptionMarshallingErrorHandler.cs?r=5303
https://social.msdn.microsoft.com/Forums/vstudio/en-US/f15a5edf-b3ff-43e6-be90-b943141a286e/throwing-exception-from-wcf-service-client-not-receiving-same-error-message?forum=wcf
"ppwcode"是最好的,因为它重建堆栈跟踪,并递归链以记录InnerExceptions
。不过这并不完全正确,因为它只检查"根"异常上的可序列化属性。因此,如果任何InnerExceptions
不可序列化,那么它将无法正常工作。
这是我最终用来在服务器上打包异常的方法:
void IErrorHandler.ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (error is FaultException)
{
// Let WCF do normal processing
}
else
{
try
{
// This takes an exception, and tries to serialize it, so
// that it can be encoded into a <soap:Fault> <soap:Detail>
// element. The client, can then deserialize the detail to
// rebuild the exception information.
//
// Not all exceptions are serializable! Thus if our
// serialization fails, then we will instead return a
// general exception whose content will be string
// representatiion of the exception.
MessageFault messageFault = MessageFault.CreateFault(
new FaultCode("ExceptionMarshallingErrorHandlerV1"),
new FaultReason(error.Message),
error,
new NetDataContractSerializer());
if (messageFault != null)
{
fault = Message.CreateMessage(version, messageFault, null);
}
}
catch (Exception)
{
try
{
// This isn't strictly correct, as the "stack trace" information
// is lost, and the InnerExceptions too....but I didn't think them
// important at the time...and anyway all the exceptions I expected
// were serializable, so this shouldn't have been hit.
//
// This is effectively a different way to create the MessageFault
// compared to yours.
//
// If you look at the "ppwcode" code, then it tries to
// handle the non-serializable exceptions in a better way...by
// recording the stack trace and InnerExceptions.
//
// So I would suggest using:
//
// Exception ex = HandleNonSerializableException(error);
Exception ex = new Exception(error.ToString());
MessageFault messageFault = MessageFault.CreateFault(
new FaultCode("ExceptionMarshallingErrorHandlerV1"),
new FaultReason(error.Message),
ex,
new NetDataContractSerializer());
if (messageFault != null)
{
fault = Message.CreateMessage(version, messageFault, null);
}
}
catch
{
// Pass the "exception" back as a FaultException
MessageFault messageFault = MessageFault.CreateFault(
new FaultCode("Exception (non-serializable)"),
new FaultReason(error.Message));
if (messageFault != null)
{
fault = Message.CreateMessage(version, messageFault, null);
}
fault = Message.CreateMessage(version, messageFault, null);
}
}
}
}
从"ppwcode":
private static Exception HandleNonSerializableException(Exception e)
{
string msg = e.Message;
msg = string.Format(
"Exception of type {1} wasn't serializable, rethrown as plain exception.{0}{0}Original message:{0}{2}{0}{0}Original Stacktrace:{0}{3}",
Environment.NewLine,
e.GetType().Name,
msg,
e.StackTrace);
Exception inner = e.InnerException;
while (inner != null)
{
msg = string.Format(
"{1}{0}{0}InnerException:{0}{2}{0}{0}StackTrace:{0}{3}",
Environment.NewLine,
msg,
inner.Message,
inner.StackTrace);
inner = inner.InnerException;
}
return new ProgrammingError(msg);
}
在客户端上
现在,在客户端上,您需要访问返回给它的FaultException
并解压缩其中保存的异常并将其作为.NET Exception
引发。
所以你需要一些"钩子"到客户端通道堆栈的东西......并"检查"收到的消息回复 - IClientMessageInspector
有一个AfterReceiveReply
....这给了你这个机会。
有几种不同的方法可以将"检查器"应用于客户端通道堆栈。
一种方法是创建自定义Attribute
,您可以使用它来装饰服务合同。此Attribute
实现具有ApplyClientBehavior
的IContractBehaviour
,使您可以指定自定义消息检查器。
(您也可以使用此自定义Attribute
作为通过ApplyDispatchBehavior
应用所需的自定义IErrorHandler
Web服务/"服务器"的一种方式(
olegsych"链接有一个合适的"消息检查器"来解压缩异常和一个名为ExceptionMarshallingBehavior
的自定义属性,您可以使用它来应用它(它还有一个IErrorHandler
也可以为您打包(....只需将其应用于服务合同即可。
所以:
[ServiceContract(
......
)
]
[ExceptionMarshallingBehavior]
[ServiceKnownType(typeof(.....))]
[ServiceKnownType(typeof(.....))]
public interface IMyWebService
{
[OperationContract]
string GetVersion();
...
}
供参考
- http://www.olegsych.com/2008/07/simplifying-wcf-using-exceptions-as-faults/