【问题标题】:How to send a managed Exception from WCF client to server (for logging)?如何将托管异常从 WCF 客户端发送到服务器(用于日志记录)?
【发布时间】:2015-02-26 03:45:09
【问题描述】:

我正在尝试为我的 WCF 客户端应用程序构建错误日志记录机制,以便它们可以将 WCF 上下文之外可能发生的任何异常报告回服务。

在这种情况下,我绝对保证所有客户端都是 .NET。

我找到了大量关于如何配置以另一种方式(例如服务到客户端)的信息、文章和示例,但没有关于客户端到服务的信息。

我希望在客户端上做这样的事情:

Dim i As Integer

Try
  i = String.Empty

Catch ex As Exception
  Client.Create.Call(Sub(S As IService)
                       S.Log(LogLevels.Error, ex)
                     End Sub)

End Try

FaultException(Of T) 不起作用;我试过这个:

Dim i As Integer

Try
  i = String.Empty

Catch ex As Exception
  Try
    Throw New FaultException(Of Exception)(ex)

  Catch fex As FaultException
    Client.Create.Call(Sub(S As IService)
                         S.Log(LogLevels.Error, fex.Message, fex)
                       End Sub)

  End Try
End Try

它自己返回一条极其晦涩难懂的错误消息:

“不应使用数据协定名称‘InvalidCastException:http://schemas.datacontract.org/2004/07/System’键入‘System.InvalidCastException’。考虑使用 DataContractResolver 或将任何静态未知的类型添加到已知类型列表中 - 例如,通过使用 KnownTypeAttribute属性或将它们添加到传递给 DataContractSerializer 的已知类型列表中。”

异常的二进制序列化也不起作用;我在反序列化时遇到错误:

“输入流不是有效的二进制格式。”

我的代码:

Class Test
  Public Sub Test
    Dim i As Integer
    Dim aEx As Byte()
    Dim oEx As Exception

    Try
      i = String.Empty

    Catch ex As Exception
      aEx = ex.ToBytes
      oEx = aEx.ToObject(Of Exception)()

    End Try
  End Sub
End Class

Public Module Generic
  <Extension>
  Public Function ToBytes(Of T)([Object] As T) As Byte()
    Using oStream As New MemoryStream
      With New BinaryFormatter
        .Serialize(oStream, [Object])
      End With

      Return oStream.ToArray
    End Using
  End Function



  <Extension>
  Public Function ToObject(Of T)(Data As Byte()) As T
    Using oStream As New MemoryStream
      oStream.Write(Data, 0, Data.Length)
      oStream.Seek(0, SeekOrigin.Begin)

      With New BinaryFormatter
        Return .Deserialize(oStream)
      End With
    End Using
  End Function
End Module

如何通过网络发送此非 WCF 异常?

编辑

这是我的服务界面,为简洁而编辑:

<ServiceContract>
Public Interface IService
  <OperationContract> Sub Log(Level As LogLevels, Message As String, Exception As Exception)
End Interface

【问题讨论】:

    标签: wcf exception serialization exception-handling


    【解决方案1】:

    我假设您的合同只是要求一个通用的基本例外 - System.Exception。如果是这种情况,那么 DataContractSerializer 只会反序列化该类型。它不会反序列化任何扩展 Exception 的东西;它只会反序列化 Exception 类型的异常。

    换句话说,你捕捉到的所有其他异常——而且你真的永远不会捕捉到一个基本的System.Exception 类型——不会被合约处理。

    那么你怎么能处理这个?两种选择:

    1. 将其他类型添加到您的合同中。您的合约仍将只接受Exception,但如果您使用KnownTypeAttribute,您可以允许子类型为Exception 类的对象。这将是您的FaultException、您的NullReferenceException 或您想要的任何其他内容。您还可以在服务内部检查 Exception 是否是这些类型之一,对其进行转换并使用可能存在的其他属性。这是我的首选选项,但您需要将要处理的每个类型指定为“已知类型”。如果出现新类型,它就会失败。

    2. 将您的异常克隆到新的System.Exception。即创建一个新的基础System.Exception,将消息、堆栈跟踪以及您从子类中获得的任何其他值放入其中。新对象的类型正确,因此它会正确反序列化。

    这两个选项都不是很好,但是当您具体考虑它时,您无法创建一个接受object 并发送您想要的任何内容的合同。这是同一件事。您只能交付反序列化程序已知的类型。

    【讨论】:

    • #1 听起来很冒险。 #2 可能有效,但我们不会遇到同样的问题吗?那个 WCF 不能序列化/反序列化 System.Exception? p.s.我在服务端使用 Log4Net,您可能知道它需要一个 System.Exception 对象。
    • Re #2 不,我们不会遇到同样的问题。我们可以反序列化System.Exception——只是我们不能在没有命名的情况下反序列化它的任何子类。通过发送一个简单的异常来测试您的服务,它会起作用。
    • 我刚才尝试发送System.Exception,但我收到与FaultException(Of T) 相同的System.InvalidCastException 消息。我是否需要向我的IService 界面添加一些属性?如果有,是哪一个,在哪里?
    • 用你的界面和测试客户端更新问题(发送简单的异常),我会看看什么时候可以,或者至少其他人会有更多的事情要做。
    • 已更新。它非常简单,几乎是你能得到的基础。
    【解决方案2】:

    嗯,那是一趟半的旅程。希望我带了一些 TrailMix。

    解决方案原来是微软的红发继子NetDataContractSerializer,简单提到了here。你可以阅读我为什么称它为here

    Ron Jacobs 的帖子底部有一个重要链接(指向 Aaron Skonnard 的帖子),该链接现已过期。我找到了那个here 的副本(cmets 很重要,看看)。看起来Tim Scott 也使用了 Aaron 代码的细微变化。

    正是在蒂姆更彻底的解释中,我找到了我的圣杯。 (我一直想做这个有一段时间了!)

    Tim 正在使用Attribute 装饰,但这些装饰仅适用于服务器 上的CLR 对象。在这种情况下,我们在 client 工作。

    所以最后,修复的简单之处在于它是美丽的。只需使用NetDataContractSerializerSystem.Exception 打包成Byte(),将数据发送到服务器,然后在那里进行反序列化。无需吟唱、烟雾镜、蝾螈之眼。

    存在性能损失,因此请将您的Exceptions 保持在最低限度!

    这是我最终得到的结果,然后是 Tim 的代码,供任何可能需要的人使用:

    从客户端向服务器发送System.Exception

    Public Class Client
      Private Sub ClientTest()
        Dim i As Integer
    
        Try
          i = String.Empty
    
        Catch ex As Exception
          Client.Create.Call(Sub(S As IService)
                               S.Log(LogLevels.Error, ex.Message, ex.ToBytes)
                             End Sub)
        End Try
      End Sub
    End Class
    
    
    
    <ServiceContract>
    Public Interface IService
      <OperationContract> Sub Log(Level As LogLevels, Message As String, Data As Byte())
    End Interface
    
    
    
    Public Class Service
      Implements IService
    
      Public Sub Log(Level As LogLevels, Message As String, Data As Byte()) Implements IService.Log
        ' Log4Net logger created separately, out of scope of this question '
        Select Case Level
          Case LogLevels.Debug : Main.Logger.Debug(Message, Data.ToException)
          Case LogLevels.Info : Main.Logger.Info(Message, Data.ToException)
          Case LogLevels.Warn : Main.Logger.Warn(Message, Data.ToException)
          Case LogLevels.Error : Main.Logger.Error(Message, Data.ToException)
          Case LogLevels.Fatal : Main.Logger.Fatal(Message, Data.ToException)
        End Select
      End Sub
    End Class
    
    
    
    Public Module Extensions
      <Extension>
      Public Function ToBytes(Instance As Exception) As Byte()
        Using oStream As New MemoryStream()
          With New NetDataContractSerializer
            .Serialize(oStream, Instance)
          End With
    
          Return oStream.ToArray
        End Using
      End Function
    
    
    
      <Extension>
      Public Function ToException(Data As Byte()) As Exception
        Using oStream As New MemoryStream(Data)
          With New NetDataContractSerializer
            Return .Deserialize(oStream)
          End With
        End Using
      End Function
    End Module
    

    向客户端发送 CLR 对象(包括泛型)

    public class NetDataContractOperationBehavior : DataContractSerializerOperationBehavior
    {
        public NetDataContractOperationBehavior(OperationDescription operation)
            : base(operation)
        {
        }   
    
        public NetDataContractOperationBehavior(OperationDescription operation, DataContractFormatAttribute dataContractFormatAttribute)
            : base(operation, dataContractFormatAttribute)
        {
        }   
    
        public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns,
            IList<Type> knownTypes)
        {
            return new NetDataContractSerializer(name, ns);
        }   
    
        public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name,
            XmlDictionaryString ns, IList<Type> knownTypes)
        {
            return new NetDataContractSerializer(name, ns);
        }
    }   
    
    
    
    public class UseNetDataContractSerializerAttribute : Attribute, IOperationBehavior
    {
        public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
        {
        }   
    
        public void ApplyClientBehavior(OperationDescription description,
            System.ServiceModel.Dispatcher.ClientOperation proxy)
        {
            ReplaceDataContractSerializerOperationBehavior(description);
        }   
    
        public void ApplyDispatchBehavior(OperationDescription description,
            System.ServiceModel.Dispatcher.DispatchOperation dispatch)
        {
            ReplaceDataContractSerializerOperationBehavior(description);
        }   
    
        public void Validate(OperationDescription description)
        {
        }   
    
        private static void ReplaceDataContractSerializerOperationBehavior( OperationDescription description)
        {
            DataContractSerializerOperationBehavior dcsOperationBehavior =
            description.Behaviors.Find<DataContractSerializerOperationBehavior>();   
    
            if (dcsOperationBehavior != null)
            {
                description.Behaviors.Remove(dcsOperationBehavior);
                description.Behaviors.Add(new NetDataContractOperationBehavior(description));
            }
        }
    }
    
    
    // Then in the service contract, to every method we added the UseNetDataContractSerializer attribute, like so:
    [UseNetDataContractSerializer]
    [OperationContractAttribute]
    Company SaveCompany(CompanyUpdater companyUpdater);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-02
      • 1970-01-01
      • 2011-07-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多