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

问题描述

编辑:问题的根本原因

我正在开发一个应用程序,它通过 System.Messaging 使用 XmlMessageFormatter 和 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] 的配置:

Class1:

[XmlRoot(ElementName = "HM")]
public class Class1{
  [XmlElement(ElementName = "ID")]
  public long Id1;
}

Class16:

[XmlRoot(ElementName = "HM")]
public class Class16{
  [XmlElement(ElementName = "ID")]
  public long Id16;
}

如您所见,两个类的 XML 正文确实相同。

这甚至可能吗?

编辑:原始问题

我有一个基本类(简单类),从中继承了几个子类(大约 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() 之后,观察窗口提到 general 的类型是错误的)

提前致谢

更多调查后编辑

删除了我自己的答案,因为问题没有完全解决

与此同时,我发现 [XmlRoot] 条目导致了上述问题:我一直在为不同的类使用相同的 [XmlRoot] 条目。

有办法区分吗?

为了您的信息,我已经尝试了以下但没有奏效:

Class1:

[XmlRoot(ElementName = "HM",DataType = "subClass1",Namespace="Namespace")]
public class subClass1 : Basic_Class

Class2:

[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 root element 名称和命名空间 URI 并在提供的 TargetTypes 中找到兼容类型来识别接收到的对象类型:

指定格式化程序将从提供的消息中反序列化的可能类型集。

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

如果由于某种原因无法完成并且您需要在所有类上使用相同的根名称“HM”,您将需要实现自己的自定义IMessageFormatter 基于 XmlMessageFormatter,它通过某种其他机制(例如 xsi:type 属性)对类型进行编码。

说明

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

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

    由于根元素名称默认由类名给出,您可能不需要通过元数据指示不同的根元素名称,但如果这样做,请使用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

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

    此属性是 {http://www.w3.org/2001/XMLSchema-instance}type 的缩写,是一个 w3c standard attribute,它允许元素显式断言其类型,例如当它是预期元素类型的多态子类型时。 XmlSerializer supports this attribute 并将使用它来确定要为这种多态类型反序列化的对象的实际类型。

    如果您选择这种机制,您的类将如下所示:

    [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 Class16 : Basic_Class
    {
    }
    

    使用此机制时,为共享基类型 Basic_Class 而不是具体类型构造序列化器。

但是,在这两种机制中,XmlMessageFormatter 只支持第一种,从 reference source 可以看出。 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 仅通过不同的根元素名称支持多态性。如果这是不可接受的,您将需要实现您的 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
}

然后要发送和接收 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>

注意事项

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...