如何将托管异常从 WCF 客户端发送到服务器(用于日志记录)?
How to send a managed Exception from WCF client to server (for logging)?
我正在尝试为我的 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
它 returns 它自己的一条极其晦涩的错误消息:
"Type 'System.InvalidCastException' with data contract name 'InvalidCastException:http://schemas.datacontract.org/2004/07/System' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer."
异常的二进制序列化也不起作用;我在反序列化时遇到错误:
"The input stream is not a valid binary format."
我的代码:
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
我假设你的合同只要求一个通用的基本例外 - System.Exception
。如果是这样,那么 DataContractSerializer
只会反序列化该类型。它不会反序列化任何扩展Exception
的东西;它只会反序列化 Exception
.
类型的异常
换句话说,你捕捉到的所有其他异常 - 你永远不会捕捉到基本 System.Exception
类型 - 不会被合约处理。
那你怎么处理这个呢?两个选项:
将其他类型添加到您的合同中。您的合同仍然只接受 Exception
,但如果您使用 KnownTypeAttribute,您可以允许子类型为 Exception
class 的对象。这可以是您的 FaultException
、您的 NullReferenceException
或您想要的任何其他内容。您还可以在服务内部检查 Exception
是否是这些类型之一,对其进行转换并使用可能存在的其他属性。这是我的首选选项,但您需要将要处理的每个类型指定为 "Known Type"。如果出现新类型,它将失败。
将您的异常克隆到新的 System.Exception
。即创建一个新的基础 System.Exception
,将消息、堆栈跟踪以及您从 subclass 获得的任何其他值放入其中。新对象的类型正确,因此它将正确反序列化。
这两个选项都不好,但是当你具体考虑时,你无法创建一个接受 object
并发送你想要的任何内容的合同。这是一回事。您只能传递反序列化器已知的类型。
好吧,那是一次半的旅行。希望我带了一些 TrailMix。
解决方案原来是微软的红发继子NetDataContractSerializer
,简要提及here. You can read about why I call it that here。
Ron Jacobs 的 post(Aaron Skonnard 的 post)底部有一个重要的 link,现在已过期。我发现 here (the comments are important, have a look). It looks like Tim Scott 的副本也使用了 Aaron 代码的细微变化。
正是在蒂姆更详尽的解释中,我找到了我的圣杯。 (我想做这个已经有一段时间了!)
Tim 正在使用 Attribute
装饰,但这些装饰仅适用于在 服务器 上使用 CLR 对象时。在这种情况下,我们在 客户端 .
工作
因此,最终,该修复程序在其简单性中表现出色。就是用NetDataContractSerializer
把System.Exception
打包成一个Byte()
,发送数据到服务器,然后在那里反序列化。无需吟唱、烟雾 n-镜子、蝾螈眼。
存在性能损失,因此请将您的 Exception
保持在最低限度!
这是我最后得到的,然后是蒂姆的代码,供可能需要的人使用:
从客户端向服务器发送一个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);
我正在尝试为我的 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
它 returns 它自己的一条极其晦涩的错误消息:
"Type 'System.InvalidCastException' with data contract name 'InvalidCastException:http://schemas.datacontract.org/2004/07/System' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer."
异常的二进制序列化也不起作用;我在反序列化时遇到错误:
"The input stream is not a valid binary format."
我的代码:
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
我假设你的合同只要求一个通用的基本例外 - System.Exception
。如果是这样,那么 DataContractSerializer
只会反序列化该类型。它不会反序列化任何扩展Exception
的东西;它只会反序列化 Exception
.
换句话说,你捕捉到的所有其他异常 - 你永远不会捕捉到基本 System.Exception
类型 - 不会被合约处理。
那你怎么处理这个呢?两个选项:
将其他类型添加到您的合同中。您的合同仍然只接受
Exception
,但如果您使用 KnownTypeAttribute,您可以允许子类型为Exception
class 的对象。这可以是您的FaultException
、您的NullReferenceException
或您想要的任何其他内容。您还可以在服务内部检查Exception
是否是这些类型之一,对其进行转换并使用可能存在的其他属性。这是我的首选选项,但您需要将要处理的每个类型指定为 "Known Type"。如果出现新类型,它将失败。将您的异常克隆到新的
System.Exception
。即创建一个新的基础System.Exception
,将消息、堆栈跟踪以及您从 subclass 获得的任何其他值放入其中。新对象的类型正确,因此它将正确反序列化。
这两个选项都不好,但是当你具体考虑时,你无法创建一个接受 object
并发送你想要的任何内容的合同。这是一回事。您只能传递反序列化器已知的类型。
好吧,那是一次半的旅行。希望我带了一些 TrailMix。
解决方案原来是微软的红发继子NetDataContractSerializer
,简要提及here. You can read about why I call it that here。
Ron Jacobs 的 post(Aaron Skonnard 的 post)底部有一个重要的 link,现在已过期。我发现 here (the comments are important, have a look). It looks like Tim Scott 的副本也使用了 Aaron 代码的细微变化。
正是在蒂姆更详尽的解释中,我找到了我的圣杯。 (我想做这个已经有一段时间了!)
Tim 正在使用 Attribute
装饰,但这些装饰仅适用于在 服务器 上使用 CLR 对象时。在这种情况下,我们在 客户端 .
因此,最终,该修复程序在其简单性中表现出色。就是用NetDataContractSerializer
把System.Exception
打包成一个Byte()
,发送数据到服务器,然后在那里反序列化。无需吟唱、烟雾 n-镜子、蝾螈眼。
存在性能损失,因此请将您的 Exception
保持在最低限度!
这是我最后得到的,然后是蒂姆的代码,供可能需要的人使用:
从客户端向服务器发送一个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);