将带有 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。是否可以将服务器上发生的异常传递给客户端而不转换为故障异常,或者确保使用所有内部异常的故障异常进行传输。

将带有 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实现具有ApplyClientBehaviorIContractBehaviour,使您可以指定自定义消息检查器。

(您也可以使用此自定义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/