编辑:问题的根本原因
我正在开发一个应用程序,该应用程序通过XmlMessageFormatter
使用System.Messaging
和XML序列化。
我想发送一个对象,比如说Class1
,有一个 ID 字段:
public class Class1{
public long Id1;
}
我还想发送另一个对象,比如说Class16
,具有另一个 ID 字段:
public class Class16{
public long Id16;
}
在 XML 中,两者都需要如下所示:
<HM>Human_Message
<ID>Own_Identifier</ID>
</HM>
为了实现这一点,我正在使用以下类似[Xml]
的配置:
第1类:
[XmlRoot(ElementName = "HM")]
public class Class1{
[XmlElement(ElementName = "ID")]
public long Id1;
}
16类:
[XmlRoot(ElementName = "HM")]
public class Class16{
[XmlElement(ElementName = "ID")]
public long Id16;
}
如您所见,两个类的 XML 主体确实是相等的。
这可能吗?
编辑:原始问题
我有一个基本类(简单类),其中有几个子类(大约 27 个),从中继承。
我正在使用标准的 C#System.Messaging
系统来回发送对象。
非常简化:
发送方:
我有一个消息队列,正在做:
subClass1 Obj1 = subClass1(...);
...
Basic_Class Obj_To_Be_Sent = Basic_Class(Obj1);
System.Messaging.Message message = new System.Messaging.Message(Obj_To_Be_Sent);
obj_MessageQueue.Send(message, ...);
检查Obj_To_Be_Sent
时,类型是正确的。
发送后,当我查看"计算机管理"、">服务和应用程序"、"消息队列"等"属性"时,我会看到该消息,但我无法验证类型是否仍然正确。
接收方:
我有一个_xmlMessageFormatter
,包含(除其他外):
System.Type[] messageTypes = new System.Type[27];
messageTypes[0] = typeof(SubClass1);
...
messageTypes[15] = typeof(SubClass16);
...
message = this._receiveQueue.Receive();
Basic_Class general = (Basic_Class)this._xmlMessageFormatter.Read(message);
Type objectType= general.GetType();
令我惊讶的是,objectType
错了(据信是SubClass16
)。
此应用程序以前运行良好,但现在似乎有些失败了。我遇到的最大问题是我不知道如何检查发送消息和获取接收消息类型之间的步骤。
有没有人了解计算机管理,服务和应用程序,消息队列,...,我如何检查发送端的对象类型是否正常?
有人有关于_xmlFormatter.Read()
和GetType()
的知识吗?(在Read()
之后,监视窗口提到general
类型是错误的)
提前致谢
在更多调查后编辑
我删除了自己的答案,因为问题没有完全解决。
同时,我发现[XmlRoot]
条目导致了上述问题:我一直在为不同的类使用相同的[XmlRoot]
条目。
有没有办法区分?
供您参考,我已经尝试了以下方法,但没有奏效:
第1类:
[XmlRoot(ElementName = "HM", DataType = "subClass1", Namespace="Namespace")]
public class subClass1 : Basic_Class
第2类:
[XmlRoot(ElementName = "HM", DataType = "subClass16", Namespace="Namespace")]
public class subClass16 : Basic_Class
而_xmlFormatter.TargetTypes
包含以下条目:
Name = "subClass1" FullName="Namespace.Class1"
Name = "subClass16" FullName="Namespace.Class16"
有人有什么想法吗?
TL/DR
XmlMessageFormatter
通过检查 XML 根元素名称和命名空间 URI 并在提供的TargetTypes
中找到兼容的类型来识别收到的对象的类型:
指定格式化程序将从提供的消息中反序列化的可能类型集。
因此,您需要为每个派生类型指定不同的XmlRootAttribute.ElementName
根元素名称,而不是对所有类型使用相同的名称"HM"
。 这样做后,您应该能够使用XmlMessageFormatter
发送和接收它们,而不会丢失类型信息。
如果由于某种原因无法执行此操作,并且您需要在所有类上使用相同的根名称"HM",则需要基于通过其他机制(如xsi:type
属性)对类型进行编码的XmlMessageFormatter
实现自己的自定义IMessageFormatter
。
解释
您正在使用XmlMessageFormatter
发送和接收格式化为 XML 的多态类型层次结构的实例。 在内部,此序列化程序使用XmlSerializer
对 XML 进行序列化和反序列化。 此序列化程序支持两种用于交换多态类型的不同机制:
-
可以通过对每种类型使用不同的(根)元素名称来指定类型信息。
由于根元素名称默认由类名给出,因此您可能不需要通过元数据指示不同的根元素名称,但如果这样做,请使用
XmlRootAttribute.ElementName
:在 XML 文档实例中生成和识别的 XML 根元素的名称。默认值为序列化类的名称。
如果您选择此机制,您的类将如下所示:
[XmlRoot(ElementName = "Basic_Class", Namespace="Namespace")] public class Basic_Class { } [XmlRoot(ElementName = "Class1", Namespace="Namespace")] public class Class1 : Basic_Class { } [XmlRoot(ElementName = "Class16", Namespace="Namespace")] public class Class16 : Basic_Class { }
使用此机制时,请为要序列化的具体类型构造一个
XmlSerializer
。 -
如果为所有子类型发出公共根元素,则可以通过
xsi:type
属性指定类型信息。此属性是
{http://www.w3.org/2001/XMLSchema-instance}type
的缩写,是一个 w3c 标准属性,它允许元素显式断言其类型,例如,当它是预期元素类型的多态子类型时。XmlSerializer
支持此属性,并将使用它来确定要为此类多态类型反序列化的对象的实际类型。如果您选择此机制,您的类将如下所示:
[XmlRoot(ElementName = "Basic_Class", Namespace="Namespace")] [XmlInclude(typeof(Class1)), XmlInclude(typeof(Class16))] // Include all subtypes here! public class Basic_Class { } [XmlRoot(ElementName = "Class1", Namespace="Namespace")] public class Class1 : Basic_Class { } [XmlRoot(ElementName = "Class16", Namespace="Namespace")] public class Class16 : Basic_Class { }
使用此机制时,请为共享基类型
Basic_Class
而不是具体类型构造序列化程序。
但是,在这两种机制中,只有第一种得到XmlMessageFormatter
的支持,从参考来源可以看出。Write(Message message, object obj)
只是为传入对象的具体类型构造或使用默认序列化程序:
Type serializedType = obj.GetType(); XmlSerializer serializer = null; if (this.targetSerializerTable.ContainsKey(serializedType)) serializer = (XmlSerializer)this.targetSerializerTable[serializedType]; else { serializer = new XmlSerializer(serializedType); this.targetSerializerTable[serializedType] = serializer; } serializer.Serialize(stream, obj);
由于序列化程序是使用obj.GetType()
构造的,因此根元素名称将为派生的具体类指定的名称,并且不包括xsi:type
信息。
Read(Message message)
方法循环使用为TargetTypes
构造的默认序列化程序,并使用XmlSerializer.CanDeserialize(XmlReader)
返回true
的第一个序列化程序:
foreach (XmlSerializer serializer in targetSerializerTable.Values) { if (serializer.CanDeserialize(reader)) return serializer.Deserialize(reader); }
此方法反过来只是检查根元素名称和命名空间是否与要反序列化的类型兼容。 这就是为什么您需要为每个具体类型使用不同的根元素名称的原因。
实现自己的IMessageFormatter
。
如上所述,XmlMessageFormatter
仅支持通过不同的根元素名称进行多态性。 如果这不可接受,则需要实现通过某种其他机制(如上述xsi:type
属性)对类型进行编码的IMessageFormatter
。
例如,以下IMessageFormatter
支持通过提供的XmlSerializer
进行序列化和反序列化:
public class OverrideXmlMessageFormatter : IMessageFormatter
{
readonly XmlSerializer serializer;
public OverrideXmlMessageFormatter(XmlSerializer serializer)
{
if (serializer == null)
throw new ArgumentNullException();
this.serializer = serializer;
}
#region IMessageFormatter Members
public bool CanRead(Message message)
{
if (message == null)
throw new ArgumentNullException();
var stream = message.BodyStream;
bool canRead;
try
{
using (var reader = XmlReader.Create(message.BodyStream))
canRead = serializer.CanDeserialize(reader);
}
catch (Exception)
{
canRead = false;
}
message.BodyStream.Position = 0; // reset stream in case CanRead is followed by Deserialize
return canRead;
}
public object Read(Message message)
{
if (message == null)
throw new ArgumentNullException();
using (var reader = XmlReader.Create(message.BodyStream))
return serializer.Deserialize(reader);
}
public void Write(Message message, object obj)
{
if (message == null || obj == null)
throw new ArgumentNullException();
var stream = new MemoryStream();
serializer.Serialize(stream, obj);
message.BodyStream = stream;
message.BodyType = 0;
}
#endregion
#region ICloneable Members
public object Clone()
{
return new OverrideXmlMessageFormatter(serializer);
}
#endregion
}
然后要发送和接收类型为Class1
和/或Class16
的消息,请定义以下XmlSerializerCache.MessagingSerializer
:
public static class XmlSerializerCache
{
static XmlSerializer CreateSharedSerializer(Type baseType, Type[] extraTypes, string rootName, string rootNamespace)
{
// baseType could be typeof(object) if there is no common base type.
return new XmlSerializer(baseType,
null,
extraTypes,
new XmlRootAttribute { ElementName = rootName, Namespace = rootNamespace },
null);
}
static XmlSerializer serializer = CreateSharedSerializer(
// The base type for the classes you want to send. Could be object if there is no more derived base type.
typeof(object),
// Add all the derived types of the classes you want to send
new[] { typeof(Class1), typeof(Class16) },
// Your required root element name.
"MH",
// Your required root element namespace.
"");
// The serializer must be statically cached to avoid a severe memory leak, see https://stackoverflow.com/questions/23897145/memory-leak-using-streamreader-and-xmlserializer
public static XmlSerializer MessagingSerializer { get { return serializer; } }
}
并设置
IMessageFormatter _xmlMessageFormatter = new OverrideXmlMessageFormatter(XmlSerializerCache.MessagingSerializer);
您需要在发送端和接收端使用此格式化程序。
发送的 XML 将如下所示(对于Class1
):
<MH xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xsi:type="Class1">
<ID>10101</ID>
</MH>
笔记
XmlRootAttribute.DataType
不用于指定多态类型信息。 相反,它可以用来指示元素包含某些特定 XML 基元类型的值,例如dateTime
、duration
等:XSD(XML 架构文档)数据类型。
因此,设置此值在应用程序中没有帮助。
关于
XmlSerializer
和多态性的一些相关问题包括:使用 XmlSerializer 序列化派生类
如果返回类型可能是错误或成功对象,如何反序列化 XML
使用具有属性重写的
XmlSerializer
序列化为 XML 时,必须静态缓存并重用序列化程序以避免严重的内存泄漏。 请参阅使用 StreamReader 和 XmlSerializer 的内存泄漏了解原因。如果您确实实现了自己的
IMessageFormatter
则可以假设使用不同的序列化程序(如 Json.NET)实现自己的消息传递格式。