"System.Messaging" 系统如何识别它发送和接收的对象的类型?

How does "System.Messaging" system recognises the types of the objects it sends and receives?

编辑:问题的根本原因

我正在开发一个使用 System.Messaging and XML serialisation via XmlMessageFormatter.

的应用程序

我想发送一个对象,比方说 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 正文对于两个 class 确实是相等的。

这可能吗?

编辑:原问题

我有一个基本的class(简单的class),从它继承了几个子classes(大约27个)

我使用标准 C# System.Messaging 系统来回发送对象。

非常简单:

发送端:

我有一个 MessageQueue,正在做:

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() 之后,watch-window 提到 general 的类型是错误的)

提前致谢

进一步调查后编辑

由于问题没有完全解决,我删除了自己的答案。

与此同时,我发现 [XmlRoot] 条目导致了上述问题:我一直在为不同的 classes 使用相同的 [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 recognizes the type of object received by inspecting the XML root element name and namespace URI and finding a compatible type among the provided TargetTypes:

Specifies the set of possible types that will be deserialized by the formatter from the message provided.

因此,您需要为每个派生类型指定不同的 XmlRootAttribute.ElementName 根元素名称,而不是对所有派生类型使用相同的名称 "HM"。这样做之后,您应该能够使用 XmlMessageFormatter 发送和接收它们而不会丢失类型信息。

如果由于某种原因 无法 完成并且您需要在所有 classes,您将需要基于 XmlMessageFormatter 实现您自己的自定义 IMessageFormatter,通过一些其他机制(例如 xsi:type 属性对类型进行编码。

说明

您正在使用 XmlMessageFormatter 发送和接收格式化为 XML 的多态类型层次结构的实例。在内部,此序列化器使用 XmlSerializer 序列化到 XML 和从 XML 反序列化。此序列化程序支持两种不同的机制来交换多态类型:

  1. 可以通过为每种类型使用不同的(根)元素名称来指定类型信息。

    由于根元素名称默认由 class 名称给出,您可能不需要通过元数据指示不同的根元素名称,但如果需要,请使用 XmlRootAttribute.ElementName:

    The name of the XML root element that is generated and recognized in an XML-document instance. The default is the name of the serialized class.

    如果您选择此机制,您的 classes 将类似于:

    [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

  2. 如果为所有子类型发出一个共同的根元素,则可以通过 xsi:type 属性指定类型信息。

    此属性是 {http://www.w3.org/2001/XMLSchema-instance}type 的缩写,是一个 w3c standard attribute that allows an element to explicitly assert its type, e.g. when it is a polymorphic subtype of the expected element type. XmlSerializer supports this attribute 并将使用它来确定要针对此类多态类型进行反序列化的对象的实际类型。

    如果您选择此机制,您的 classes 将类似于:

    [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只支持第一种,从reference source. The 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() 构建的,根元素名称将是为派生的具体 class 指定的名称,并且 xsi:type 信息将不包括在内。

Read(Message message) method cycles through default serializers constructed for the TargetTypes and uses the first for which XmlSerializer.CanDeserialize(XmlReader) returns true:

foreach (XmlSerializer serializer in targetSerializerTable.Values)
{
   if (serializer.CanDeserialize(reader))
       return serializer.Deserialize(reader);
}

此方法反过来简单地检查根元素名称和命名空间是否与要反序列化的类型兼容。这就是为什么您需要为每个具体类型使用不同的根元素名称。

实现你自己的IMessageFormatter.

如上所述,XmlMessageFormatter 仅通过不同的根元素名称支持多态性。如果这是不可接受的,您将需要实现您的 IMessageFormatter,它通过一些其他机制(例如上述 xsi:type 属性)对类型进行编码。

例如,以下 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
}

然后发送和接收Class1and/orClass16类型的消息,定义如下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 
    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 基本类型的值,例如 dateTimeduration 等:

    An XSD (XML Schema Document) data type.

    因此设置此值对您的应用程序没有帮助。

  • 关于XmlSerializer和多态性的一些相关问题包括:

    • Using XmlSerializer to serialize derived classes

  • 当使用带有属性覆盖的 XmlSerializer 序列化到 XML 时,您必须静态缓存并重用序列化程序以避免严重的内存泄漏。参见 Memory Leak using StreamReader and XmlSerializer 了解原因。

  • 如果您确实实现了自己的 IMessageFormatter,您可以假设使用不同的序列化程序(例如 Json.NET.

    实现您自己的消息传递格式