Edit: root cause of the question
I'm working on an application, that uses System.Messaging and XML serialisation via XmlMessageFormatter.
I would like to send an object, let's say Class1, having an ID field:
public class Class1{
public long Id1;
}
I would also like to send another object, let's say Class16, having another ID field:
public class Class16{
public long Id16;
}
In XML, both need to look like:
<HM>Human_Message
<ID>Own_Identifier</ID>
</HM>
In order to achieve this, I'm working with following [Xml]-like configurations:
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;
}
As you see, the XML body will indeed be equal for both classes.
Is this even possible?
Edit: original question
I have a basic class (simple class), from which there are several subclasses (about 27 of them), inheriting from it.
I'm using standard C# System.Messaging system for sending objects back and forth.
Very simplified:
Sending side:
I have a MessageQueue, doing:
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, ...);
When checking Obj_To_Be_Sent, the type is correct.
Once this is sent, when I have a look at Computer Management, Services and Applications, Message Queuing, ..., Properties, I see the message, but I can't verify if the type is still correct.
Receiving side:
I have an _xmlMessageFormatter, containing (amongst others):
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();
To my surprise, objectType is wrong (it is believed to be SubClass16).
This application has worked fine before, but now something seems to fail. The biggest problem I have is that I don't know how to check the steps between sending the message and getting the type of the received message.
Does anybody have knowledge on Computer Management, Services and Applications, Message Queuing, ..., how can I check if the object type on the sending side is ok?
Does anybody have knowledge on _xmlFormatter.Read() and GetType()? (Already after the Read(), the watch-window mentions the Type of general to be wrong)
Thanks in advance
Edit after more investigation
I delete my own answer as the problem is not completely solved.
Meanwhile I've discovered that [XmlRoot] entries are causing the mentioned issues: I've been using the same [XmlRoot] entry for different classes.
Is there a way to differentiate?
For your information, I've already tried the following but it did not work:
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
while the _xmlFormatter.TargetTypes contained entries like:
Name = "subClass1" FullName="Namespace.Class1"
Name = "subClass16" FullName="Namespace.Class16"
Does anybody have any ideas?
TL/DR
XmlMessageFormatterrecognizes the type of object received by inspecting the XML root element name and namespace URI and finding a compatible type among the providedTargetTypes:Thus you need to specify distinct
XmlRootAttribute.ElementNameroot element names for each of your derived types rather than using the same name"HM"for all of them. Having done so, you should be able to send and receive them usingXmlMessageFormatterwithout loss of type information.If for whatever reason this cannot be done and you need to use the same root name "HM" on all your classes, you will need to implement your own custom
IMessageFormatterbased onXmlMessageFormatterthat encodes the type via some other mechanism such as anxsi:typeattribute.Explanation
You are using
XmlMessageFormatterto send and receive instances of a polymorphic type hierarchy formatted as XML. Internally this serializer usesXmlSerializerto serialize to and deserialize from XML. This serializer supports two different mechanisms for exchanging polymorphic types:Type information may be specified by using different (root) element names for each type.
Since the root element name is given by default by the class name, you may not need to indicate different root element names via metadata, but if you do, use
XmlRootAttribute.ElementName:If you were to opt for this mechanism, your classes would look something like:
When using this mechanism, construct an
XmlSerializerfor the concrete type to be serialized.If a common root element is emitted for all subtypes, type information may be specified via an
xsi:typeattribute.This attribute, short for
{http://www.w3.org/2001/XMLSchema-instance}type, is a 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.XmlSerializersupports this attribute and will use it to determine the actual type of object to deserialize for such a polymorphic type.If you were to opt for this mechanism, your classes would look something like:
When using this mechanism, construct a serializer for the shared base type
Basic_Classrather than the concrete type.But, of these two mechanisms, only the first is supported by
XmlMessageFormatter, as can be seen from the reference source. TheWrite(Message message, object obj)simply constructs or uses a default serializer for the concrete type of the incoming object:As the serializer is constructed using
obj.GetType(), the root element name will be the name specified for the derived, concrete class, andxsi:typeinformation will not be included.The
Read(Message message)method cycles through default serializers constructed for theTargetTypesand uses the first for whichXmlSerializer.CanDeserialize(XmlReader)returnstrue:This method in turn simply checks to see whether the root element name and namespace are the compatible with the type to be deserialized. And this is why you need to use different root element names for each concrete type.
Implementing your own
IMessageFormatter.As explained above,
XmlMessageFormatteronly supports polymorphism via different root element names. If this is not acceptable, you will need to implement yourIMessageFormatterthat encodes the type via some other mechanism such as thexsi:typeattribute described above.For instance, the following
IMessageFormattersupports serializing and deserializing via a suppliedXmlSerializer:Then to send and receive messages of type
Class1and/orClass16, define the followingXmlSerializerCache.MessagingSerializer:And set
You will need to use this formatter on both the sending and receiving sides.
The XML sent will look like (for
Class1):Notes
XmlRootAttribute.DataTypeis not used in specifying polymorphic type information. Instead it can be used to indicate that the element contains a value of some specific XML primitive type such asdateTime,duration, etc:As such setting this value is not helpful in your application.
Some related questions about
XmlSerializerand polymorphism include:Using XmlSerializer to serialize derived classes
How to deserialize XML if the return type could be an Error or Success object
When serializing to XML with an
XmlSerializerwith attribute overrides, you must statically cache and reuse the serializer to avoid a severe memory leak. See Memory Leak using StreamReader and XmlSerializer for why.If you do implement your own
IMessageFormatteryou could hypothetically implement your own messaging format using a different serializer such as Json.NET.