I have an issue when I try to deserialize my XML File to an object using the XmlSerializer
class.
My XML file looks like this:
<fx:FIBEX xmlns:fx="http://www.asam.net/xml/fbx" xmlns:ho="http://www.asam.net/xml" xmlns:ethernet="http://www.asam.net/xml/fbx/ethernet" xmlns:it="http://www.asam.net/xml/fbx/it" xmlns:service="http://www.asam.net/xml/fbx/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="" VERSION="4.1.0">
<fx:ELEMENTS>
<fx:CLUSTERS>
<fx:CLUSTER xsi:type="ethernet:CLUSTER-TYPE" ID="ID_CLUSTER_MAIN_1">
<ho:SHORT-NAME>SomeIpDatabase</ho:SHORT-NAME>
<fx:SPEED>1000000000</fx:SPEED>
<fx:IS-HIGH-LOW-BIT-ORDER>false</fx:IS-HIGH-LOW-BIT-ORDER>
<fx:BIT-COUNTING-POLICY>SAWTOOTH</fx:BIT-COUNTING-POLICY>
<fx:PROTOCOL>ETHERNET</fx:PROTOCOL>
<fx:PHYSICAL>OABR</fx:PHYSICAL>
<fx:CHANNEL-REFS>
<fx:CHANNEL-REF ID-REF="ID_CHANNEL_SOME_IP_1" />
</fx:CHANNEL-REFS>
<fx:MAX-FRAME-LENGTH>1500</fx:MAX-FRAME-LENGTH>
<ethernet:MAC-MULTICAST-GROUPS>
<ethernet:MAC-MULTICAST-GROUP ID="ID_CLUSTER_MAIN_1_ID_MACMULTICASTGROUP_SD_1">
<ho:SHORT-NAME>SD</ho:SHORT-NAME>
<ethernet:MAC-MULTICAST-ADDRESS>01:00:5E:40:FF:FB</ethernet:MAC-MULTICAST-ADDRESS>
</ethernet:MAC-MULTICAST-GROUP>
<ethernet:MAC-MULTICAST-GROUP ID="ID_CLUSTER_MAIN_1_ID_MACMULTICASTGROUP_BROADCAST_1">
<ho:SHORT-NAME>BROADCAST</ho:SHORT-NAME>
<ethernet:MAC-MULTICAST-ADDRESS>FF:FF:FF:FF:FF:FF</ethernet:MAC-MULTICAST-ADDRESS>
</ethernet:MAC-MULTICAST-GROUP>
</ethernet:MAC-MULTICAST-GROUPS>
</fx:CLUSTER>
<!--Additional CLUSTER elements omitted-->
</fx:CLUSTERS>
</fx:ELEMENTS>
<!--PROJECT elements omitted-->
</fx:FIBEX>
When I try to deserialize the XML file now i receive the following error:
System.InvalidOperationException: Error in XML-Dokument (11,5). ---> System.InvalidOperationException: The specified type was not recognized: Name='CLUSTER-TYPE', Namespace='http://www.asam.net/xml/fbx/ethernet', at <CLUSTER xmlns='http://www.asam.net/xml/fbx'>.
My deserializer class looks like this:
public static T DeserializeXMLFileToObject<T>(string XmlFilename)
{
T returnObject = default(T);
if (string.IsNullOrEmpty(XmlFilename)) return default(T);
try
{
StreamReader xmlStream = new StreamReader(XmlFilename);
XmlSerializer serializer = new XmlSerializer(typeof(T));
returnObject = (T)serializer.Deserialize(xmlStream);
}
catch (Exception ex)
{
Console.Write("{1} Es ist ein Fehler aufgetreten {0}", ex, DateTime.Now);
}
return returnObject;
}
The class that should contain the deserialized elements and attributes of the XML file looks like this:
[XmlRoot("FIBEX", Namespace = fxNameSpace)]
public class Fibextoobject
{
[XmlElement("PROJECT", Namespace = fxNameSpace)]
public Project project { get; set; }
[XmlElement("ELEMENTS", Namespace = fxNameSpace)]
public Elements elements { get; set; }
public class Project
{
[XmlAttribute("OID", Namespace = hoNameSpace)]
public string OID { get; set; }
[XmlAttribute("ID")]
public string ID { get; set; }
[XmlElement("SHORT-NAME", Namespace = hoNameSpace)]
public string shortname { get; set; }
[XmlElement("LONG-NAME", Namespace = hoNameSpace)]
public string longname { get; set; }
}
public class Elements
{
[XmlArray("CLUSTERS", Namespace = fxNameSpace)]
[XmlArrayItem("CLUSTER", Namespace = fxNameSpace)]
public List<Cluster> cluster { get; set; }
}
public class Cluster
{
[XmlAttribute("ID")]
public string ID { get; set; }
[XmlElement("SHORT-NAME", Namespace = hoNameSpace)]
public string shortname { get; set; }
[XmlElement("SPEED", Namespace = fxNameSpace)]
public string speed { get; set; }
}
}
How can i deserialize the XML file successfully using the xsi:type attribute with the colon and hyphen inside the value: xsi:type="ethernet:CLUSTER-TYPE"
?
Your problem is as follows. The xsi:type
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. XmlSerializer
supports this attribute and will use it to determine the actual type of object to deserialize for such a polymorphic type. However, there are several caveats and restrictions in using this attribute:
If xsi:type
is present in the XML, then that element must be bound to a polymorphic type hierarchy. XmlSerializer
will never just ignore the attribute. Thus you will need to introduce a derived subtype of Cluster
to deserialize this XML, e.g. as follows:
public class ClusterType : Cluster
{
}
XmlSerializer
requires that it be informed in advance of all possible subtypes using [XmlInclude(typeof(TDerivedType))]
. Normally one would place this attribute on the base type:
[XmlInclude(typeof(ClusterType))]
// Add XmlInclude for all additional subtypes here.
public class Cluster
{
// Remainder unchanged
Your xsi:type
value contains characters that cannot be included in a c# identifier:
xsi:type="ethernet:CLUSTER-TYPE"
In this case, XmlSerializer
interprets the the value as a qualified name where the portion before the :
is an XML namespace prefix of a valid namespace in scope, and the portion afterwards corresponds to the XmlTypeAttribute.TypeName
of the polymorphic type. You can inform the serializer of the expected namespace and type by applying the [XmlType]
attribute to the derived type as follows:
[XmlType("CLUSTER-TYPE", Namespace = ethernetNameSpace)]
public class ClusterType : Cluster
{
}
Where ethernetNameSpace
is defined as:
public const string ethernetNameSpace = "http://www.asam.net/xml/fbx/ethernet";
For some reason XmlSerializer
requires the XmlTypeAttribute.Namespace
to be initialized to something on the base type if it is set to anything at all on included derived types. The value of the namespace doesn't seem to matter when deserializing an instance of the derived type (though obviously it would when deserializing the base type), it just needs to be set to something. Even the empty string will work, e.g.:
// The XmlTypeAttribute.Namespace below must be initialized to something if it is also initialized on the derived type.
// However, the actual value does not need to be the same as the child's namespace, so modify to be something more appropriate
// based on additional XML samples.
[XmlType("CLUSTER", Namespace = "")]
[XmlInclude(typeof(ClusterType))]
// Add XmlInclude for all additional subtypes here.
public class Cluster
{
// Remainder unchanged
If the base type XML namespace is not set, XmlSerializer
will throw an exception with a misleading message:
System.InvalidOperationException: There was an error generating the XML document. ---> System.InvalidOperationException: The type Fibextoobject+ClusterType was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.
Since ClusterType
is in fact included via [XmlInclude]
the message is not helpful. It took a bit of experimentation to determine the actual problem.
A working version of your Fibextoobject
type which puts all of the above items into action is as follows:
[XmlRoot("FIBEX", Namespace = fxNameSpace)]
public partial class Fibextoobject
{
[XmlElement("PROJECT", Namespace = fxNameSpace)]
public Project project { get; set; }
[XmlElement("ELEMENTS", Namespace = fxNameSpace)]
public Elements elements { get; set; }
public class Project
{
[XmlAttribute("OID", Namespace = hoNameSpace)]
public string OID { get; set; }
[XmlAttribute("ID")]
public string ID { get; set; }
[XmlElement("SHORT-NAME", Namespace = hoNameSpace)]
public string shortname { get; set; }
[XmlElement("LONG-NAME", Namespace = hoNameSpace)]
public string longname { get; set; }
}
public class Elements
{
[XmlArray("CLUSTERS", Namespace = fxNameSpace)]
[XmlArrayItem("CLUSTER", Namespace = fxNameSpace)]
public List<Cluster> cluster { get; set; }
}
[XmlType("CLUSTER-TYPE", Namespace = ethernetNameSpace)]
public class ClusterType : Cluster
{
}
// The XmlTypeAttribute.Namespace below must be initialized to something if it is also initialized on the derived type.
// However, the actual value does not need to be the same as the child's namespace, so modify to be something more appropriate
// based on additional XML samples.
[XmlType("CLUSTER", Namespace = "")]
[XmlInclude(typeof(ClusterType))]
// Add XmlInclude for all additional subtypes here.
public class Cluster
{
[XmlAttribute("ID")]
public string ID { get; set; }
[XmlElement("SHORT-NAME", Namespace = hoNameSpace)]
public string shortname { get; set; }
[XmlElement("SPEED", Namespace = fxNameSpace)]
public string speed { get; set; }
}
}
public partial class Fibextoobject
{
public const string fxNameSpace = "http://www.asam.net/xml/fbx";
public const string hoNameSpace = "http://www.asam.net/xml";
public const string ethernetNameSpace = "http://www.asam.net/xml/fbx/ethernet";
public const string itNameSpace = "http://www.asam.net/xml/fbx/it";
public const string serviceNameSpace = "http://www.asam.net/xml/fbx/services";
}
Sample working Roslyn .Net fiddle.