首页 > 解决方案 > “System.Messaging”系统如何识别它发送和接收的对象的类型?

问题描述

编辑:问题的根本原因

我正在开发一个应用程序,该应用程序System.Messaging通过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]类似配置:

第一类:

[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时,类型正确。

发送后,当我查看Computer Management, Services and Applications, Message Queuing, ..., 时Properties,我看到了消息,但我无法验证类型是否仍然正确。

接收方:

我有一个_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)。

此应用程序以前运行良好,但现在似乎有些失败。我遇到的最大问题是我不知道如何检查发送消息和获取接收消息类型之间的步骤。

有没有人知道Computer Management, Services and Applications, Message Queuing, ...,我如何检查发送方的对象类型是否正常?
有人知道_xmlFormatter.Read()andGetType()吗?(已经在 之后Read(),监视窗口提到了general错误的类型)

提前致谢

更多调查后编辑

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

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

有办法区分吗?

供您参考,我已经尝试了以下方法,但没有奏效:

第一类:

[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"

有人有什么想法吗?

标签: c#xmlpolymorphismmessagingmsmq

解决方案


TL/DR

XmlMessageFormatter通过检查 XML根元素名称和命名空间 URI 并在提供的 中找到兼容的类型来识别接收到的对象类型TargetTypes

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

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

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

解释

XmlMessageFormatter用于发送和接收格式化为 XML 的多态类型层次结构的实例。在内部,此序列化程序用于XmlSerializer对 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 标准属性,它允许元素显式声明其类型,例如,当它是预期元素类型的多态子类型时。 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仅通过不同的根元素名称支持多态性。如果这是不可接受的,您将需要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>

笔记


推荐阅读