使用InnerException将异常从服务器传输到客户端

时间:2014-12-12 08:53:20

标签: c# .net wcf

我有错误处理程序,在FaultException中转换Exception并在客户端发送它。 代码:

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

1 个答案:

答案 0 :(得分:1)

我想你想要将在WebService /“server”上发生的.NET Exception打包/序列化为FaultException,然后当“客户端”收到它时,它会被解压缩并且提升为原始常规.NET Exception ....而不仅仅是FaultException - 这称为“异常编组”。

当您无法修改客户端代码以添加FaultException处理,或者您不希望客户端代码被“webservice”特定知识污染时,有时需要这样做,即您的代码只是处理.NET Exceptions。

(注意:这是绕过“异常屏蔽”......这有点令人不悦,因为您可能会在传输到客户端的异常数据中公开可能允许攻击向量的细节......此外它可能会使您的“webservice” “不太可互操作......即非.NET客户端需要知道如何理解序列化的.NET异常信息。”

在服务器上

因此,在“服务器”上,您有正确的想法,即在您的服务中添加一个实现IErrorHandler的行为。

但你应该尝试“序列化”Exception,但如果无法完成,请回到Exception.Message

ProvideFault的一些不同实现可以做到这一点......选择你最喜欢的那个。

“ppwcode”是最好的,因为它重建堆栈跟踪,并递归链以记录InnerExceptions。这不完全正确,因为它只检查“root”异常的Serializable属性....所以如果任何InnerExceptions不可序列化,那么它将无效。

这是我最终用于在服务器上打包Exception的内容:

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提升。

所以你需要一些“挂钩”到客户端通道堆栈的东西......并“检查”收到的消息回复 - IClientMessageInspectorAfterReceiveReply ....这会给你那个机会。

有几种不同的方法可以将“检查器”应用于客户端通道堆栈。

一种方法是创建自定义服务合同的自定义Attribute。此Attribute实施IContractBehaviour,其中ApplyClientBehavior为您提供了指定自定义消息检查器的机会。

(您也可以使用此自定义Attribute作为通过IErrorHandler

应用您想要的webservice /“server”的自定义ApplyDispatchBehavior的方法

“olegsych”链接有一个合适的“消息检查器”来解压异常和一个名为ExceptionMarshallingBehavior的自定义属性,您可以使用它来应用它(它还有一个IErrorHandler可以执行也包装你....)只需将它应用于服务合同。

所以:

[ServiceContract(
    ......
    )
]
[ExceptionMarshallingBehavior]
[ServiceKnownType(typeof(.....))]
[ServiceKnownType(typeof(.....))]
public interface IMyWebService
{
    [OperationContract]
    string GetVersion();

    ...
}

供参考