如何将受管异常从WCF客户端发送到服务器(用于日志记录)? [英] How to send a managed Exception from WCF client to server (for logging)?
问题描述
我正在为我的WCF客户端应用程序构建一个错误记录机制,所以他们可以将任何可能在WCF上下文中发生的异常报告回服务。
在这种情况下,我绝对保证所有的客户端都是.NET。
我正在找到大量关于如何配置的信息,文章和示例我们希望在客户端上做这样的事情:
b
$ b
Dim i As Integer
尝试
i = String.Empty
Catch ex作为异常
Client.Create.Call(Sub(S As IService)
S.Log(LogLevels.Error,ex)
End Sub)
End尝试
FaultException(Of T)
不工作;我试过这个:
Dim i As Integer
尝试
i = String.Empty
Catch ex As Exception
尝试
抛出新的FaultException(异常)(ex)
捕获fex作为FaultException
Client.Create。 Call(Sub(S As IService)
S.Log(LogLevels.Error,fex.Message,fex)
End Sub)
结束尝试
结束尝试
它返回一个隐藏的隐藏的错误消息:
键入'System.InvalidCastException'与数据合约名称'InvalidCastException: http://schemas.datacontract.org/2004/07/System '不是预期的,考虑使用DataContractResolver或将所有不知道的类型静态添加到已知类型的列表中 - 例如,使用KnownTypeAttribute属性或者将它们添加到传递给Da的已知类型的列表中taContractSerializer。
异常的二进制序列化也不起作用;我在反序列化中收到错误:
输入流不是有效的二进制格式。
我的代码为:
类测试
公开Sub Test
Dim i As Integer
Dim aEx As Byte()
Dim oEx As Exception
尝试
i = String.Empty
Catch ex As Exception
aEx = ex.ToBytes
oEx = aEx.ToObject(Of Exception)()
结束尝试
End Sub
结束类
公共模块Generic
< Extension>
公共函数ToBytes(Of T)([Object] As T)As Byte()
使用oStream作为新的MemoryStream
使用新的BinaryFormatter
.Serialize(oStream,[Object] )
结束
返回oStream.ToArray
结束使用
结束功能
<扩展> ;
公共函数ToObject(Of T)(Data As Byte())As T
使用oStream作为新的MemoryStream
oStream.Write(Data,0,Data.Length)
oStream
使用新的BinaryFormatter
返回.Deserialize(oStream)
结束
结束使用
结束函数
结束模块
如何在线上发送此非WCF异常?
编辑
这是我的服务界面,为简洁起见进行了修改:
< ServiceContract>
公共接口IService
< OperationContract>子日志(Level As LogLevels,Message As String,Exception As Exception)
结束接口
那是一次半途而废。希望我带来一些TrailMix。
解决方案证明是微软的红头狮子, NetDataContractSerializer
简要介绍了此处。你可以阅读我为什么称之为这里。
Ron Jacobs的帖子(到Aaron Skonnard的帖子)的底部有一个重要的链接,现在已经过期了。我发现了一个这里(评论很重要,看看)。看起来像 Tim Scott 使用了亚伦的代码略有变化。
在蒂姆的更彻底的解释中,我发现了我的圣杯。 (我一直想要这样做一段时间!)
Tim正在使用属性
装饰,但那些仅在使用服务器上的CLR对象时才适用。在这种情况下,我们正在使用客户端。
最后,修复程序的简洁性很好。只需使用 NetDataContractSerializer
将 System.Exception
打包成 Byte()
,将数据发送到服务器,然后将其反序列化。没有诵经,烟雾镜,需要眼睛。
有一个性能损失,所以保持你的异常
s至少!
这是我最后的结果,其次是Tim的代码,可能需要它的任何人:
发送一个 System.Exception
从客户端到服务器
公共类客户端
Private Sub ClientTest()
Dim i As Integer
尝试
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尝试
End Sub
结束类
< ServiceContract>
公共接口IService
< OperationContract>子日志(级别为LogLevels,消息为字符串,数据为字节())
结束接口
公共类服务
实现IService
公共子日志(级别为LogLevels,消息为字符串,数据为字节())实现IService.Log
'Log4Net记录器单独创建,超出了此问题的范围'
选择案例级别
案例LogLevels.Debug:Main.Logger.Debug(Message,Data.ToException)
案例LogLevels.Info:Main.Logger.Info(Message,Data.ToException)
案例LogLevels .Warn:Main.Logger.Warn(Message,Data.ToException)
案例LogLevels.Error:Main.Logger.Error(Message,Data.ToException)
案例LogLevels.Fatal:Main.Logger.Fatal (Message,Data.ToException)
结束选择
End Sub
结束类
公共模块扩展
<分机>
公共函数ToBytes(Instance As Exception)As Byte()
使用oStream作为新的MemoryStream()
使用新的NetDataContractSerializer
.Serialize(oStream,Instance)
结束
返回oStream.ToArray
结束使用
结束功能
<扩展名>
公共函数ToException(作为Byte())作为异常
使用oStream作为新的MemoryStream(数据)
新的NetDataContractSerializer
返回.Deserialize(oStream)
结束
结束使用
结束功能
结束模块
将CLR对象(包括泛型)发送到客户端
public class NetDataContractOperationBehavior:DataContractSerializerOperationBehavior
{
public NetDataContractOperationBehavior(OperationDescription操作)
:base(operation)
{
}
public NetDataContractOperationBehavior(OperationDescription操作,DataContractFormatAttribute dataContractFormatAttribute)
: base(operation,dataContractFormatAttribute)
{
}
public override XmlObjectSerializer CreateSerializer(Type type,string name,string ns,
IList< Type> knownTypes)
{
返回新的NetDataContractSerializer(name,ns);
}
public override XmlObjectSerializer CreateSerializer(Type type,XmlDictionaryString name,
XmlDictionaryString ns,IList< Type> knownTypes)
{
返回新的NetDataContractSerializer名称,ns);
$
public class UseNetDataContractSerializerAttribute:Attribute,IOperationBehavior
{
public void AddBindingParameters(OperationDescription description,BindingParameterCollection参数)
{
}
public void ApplyClientBehavior(OperationDescription description,
System.ServiceModel.Dispatcher.ClientOperation proxy)
{
ReplaceDataContractSerializerOperationBehavior (描述);
}
public void ApplyDispatchBehavior(OperationDescription description,
System.ServiceModel.Dispatcher.DispatchOperation dispatch)
{
ReplaceDataContractSerializerOperationBehavior(description);
}
public void验证(OperationDescription描述)
{
}
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));
}
}
}
//然后在服务合同中,对于我们添加UseNetDataContractSerializer属性的每个方法,如下所示:
[UseNetDataContractSerializer]
[OperationContractAttribute]
公司保存公司(CompanyUpdater companyUpdater);
I'm trying to build an error logging mechanism for my WCF client applications, so they can report any exceptions that may occur outside of the WCF context back to the service.
I'm absolutely guaranteed in this situation that all clients will be .NET.
I'm finding a plethora of information, articles and examples on how to configure for going the other way (e.g. service to client), but nothing on client to service.
I wish to do something like this on the client:
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)
doesn't work; I tried this:
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
It returns a hideously obscure error message of its own:
"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."
Binary serialization of the exception doesn't work either; I get an error at deserialization:
"The input stream is not a valid binary format."
My code for that:
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
How does one send this non-WCF exception across the wire?
EDIT
Here's my service interface, edited for brevity:
<ServiceContract>
Public Interface IService
<OperationContract> Sub Log(Level As LogLevels, Message As String, Exception As Exception)
End Interface
Well, that was a trip and a half. Wish I'd brought some TrailMix.
The solution turned out to be Microsoft's red-headed stepchild, NetDataContractSerializer
, mentioned briefly here. You can read about why I call it that here.
There's an important link at the bottom of Ron Jacobs' post (to Aaron Skonnard's post) that's now expired. I found a copy of that here (the comments are important, have a look). It looks like Tim Scott used a slight variation of Aaron's code as well.
It was in Tim's more thorough explanation that I found my Holy Grail. (I've been wanting to do this for some time!)
Tim is using Attribute
decorations, but those are applicable only when working with CLR objects at the server. In this case we're working at the client.
So in the end, the fix is beautiful in its simplicity. Just use NetDataContractSerializer
to package up the System.Exception
into a Byte()
, send the data to the server, and then deserialize it there. No chanting, smoke-n-mirrors, eye-of-the-newt required.
There's a performance penalty, so keep your Exception
s to a minimum!
Here's what I ended up with, followed by Tim's code for anyone who may need it:
Send a System.Exception
from client to server
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
Send CLR objects (including generics) to the client
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客户端发送到服务器(用于日志记录)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!