xsi:type attribute messing up C# XML deserializati

2020-02-02 01:07发布

I used XSD.exe to automatically generate C# objects based on the XML schemas (.xsd files). I'm deserializing OpenCover output, but one of the partial classes didn't get generated correctly.

Here's the line that's causing the exception:

<MethodPoint xsi:type="SequencePoint" vc="0" uspid="1" ordinal="0" offset="0" sl="19" sc="9" el="19" ec="10" bec="0" bev="0" fileid="1" />

Here's a shortened version of the MethodPoint class:

[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.33440")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)]
public partial class CoverageSessionModulesModuleClassesClassMethodsMethodMethodPoint {
    private string vcField;
    private string uspidField;
    private string ordinalField;
    private string offsetField;
    private string slField;
    private string scField;
    private string elField;
    private string ecField;
    private string becField;
    private string bevField;
    private string fileidField;
}

Now I've been going through a lot of .xml files, but the OpenCover output files are the only ones that contain a colon inside an attribute. The MethodPoint object is also the only object that contains a colon in an attribute. As you can see, the class does not contain the xsi:type attribute, and I know that simply adding it won't work because of the colon. How do you deal with the xsi prefix?

Here is the raw .xsd generated from one of the OpenCover XML files

<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="CoverageSession" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xs:element name="Summary">
    <xs:complexType>
      <xs:attribute name="numSequencePoints" type="xs:string" />
      <xs:attribute name="visitedSequencePoints" type="xs:string" />
      <xs:attribute name="numBranchPoints" type="xs:string" />
      <xs:attribute name="visitedBranchPoints" type="xs:string" />
      <xs:attribute name="sequenceCoverage" type="xs:string" />
      <xs:attribute name="branchCoverage" type="xs:string" />
      <xs:attribute name="maxCyclomaticComplexity" type="xs:string" />
      <xs:attribute name="minCyclomaticComplexity" type="xs:string" />
      <xs:attribute name="visitedClasses" type="xs:string" />
      <xs:attribute name="numClasses" type="xs:string" />
      <xs:attribute name="visitedMethods" type="xs:string" />
      <xs:attribute name="numMethods" type="xs:string" />
    </xs:complexType>
  </xs:element>
  <xs:element name="CoverageSession" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
    <xs:complexType>
      <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element ref="Summary" />
        <xs:element name="Modules">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="Module" minOccurs="0" maxOccurs="unbounded">
                <xs:complexType>
                  <xs:sequence>
                    <xs:element name="FullName" type="xs:string" minOccurs="0" msdata:Ordinal="1" />
                    <xs:element name="ModuleName" type="xs:string" minOccurs="0" msdata:Ordinal="2" />
                    <xs:element ref="Summary" minOccurs="0" maxOccurs="unbounded" />
                    <xs:element name="Files" minOccurs="0" maxOccurs="unbounded">
                      <xs:complexType>
                        <xs:sequence>
                          <xs:element name="File" minOccurs="0" maxOccurs="unbounded">
                            <xs:complexType>
                              <xs:attribute name="uid" type="xs:string" />
                              <xs:attribute name="fullPath" type="xs:string" />
                            </xs:complexType>
                          </xs:element>
                        </xs:sequence>
                      </xs:complexType>
                    </xs:element>
                    <xs:element name="Classes" minOccurs="0" maxOccurs="unbounded">
                      <xs:complexType>
                        <xs:sequence>
                          <xs:element name="Class" minOccurs="0" maxOccurs="unbounded">
                            <xs:complexType>
                              <xs:sequence>
                                <xs:element name="FullName" type="xs:string" minOccurs="0" />
                                <xs:element ref="Summary" minOccurs="0" maxOccurs="unbounded" />
                                <xs:element name="Methods" minOccurs="0" maxOccurs="unbounded">
                                  <xs:complexType>
                                    <xs:sequence>
                                      <xs:element name="Method" minOccurs="0" maxOccurs="unbounded">
                                        <xs:complexType>
                                          <xs:sequence>
                                            <xs:element name="MetadataToken" type="xs:string" minOccurs="0" msdata:Ordinal="1" />
                                            <xs:element name="Name" type="xs:string" minOccurs="0" msdata:Ordinal="2" />
                                            <xs:element ref="Summary" minOccurs="0" maxOccurs="unbounded" />
                                            <xs:element name="FileRef" minOccurs="0" maxOccurs="unbounded">
                                              <xs:complexType>
                                                <xs:attribute name="uid" type="xs:string" />
                                              </xs:complexType>
                                            </xs:element>
                                            <xs:element name="SequencePoints" minOccurs="0" maxOccurs="unbounded">
                                              <xs:complexType>
                                                <xs:sequence>
                                                  <xs:element name="SequencePoint" minOccurs="0" maxOccurs="unbounded">
                                                    <xs:complexType>
                                                      <xs:attribute name="vc" type="xs:string" />
                                                      <xs:attribute name="uspid" type="xs:string" />
                                                      <xs:attribute name="ordinal" type="xs:string" />
                                                      <xs:attribute name="offset" type="xs:string" />
                                                      <xs:attribute name="sl" type="xs:string" />
                                                      <xs:attribute name="sc" type="xs:string" />
                                                      <xs:attribute name="el" type="xs:string" />
                                                      <xs:attribute name="ec" type="xs:string" />
                                                      <xs:attribute name="bec" type="xs:string" />
                                                      <xs:attribute name="bev" type="xs:string" />
                                                      <xs:attribute name="fileid" type="xs:string" />
                                                    </xs:complexType>
                                                  </xs:element>
                                                </xs:sequence>
                                              </xs:complexType>
                                            </xs:element>
                                            <xs:element name="BranchPoints" minOccurs="0" maxOccurs="unbounded">
                                              <xs:complexType>
                                                <xs:sequence>
                                                  <xs:element name="BranchPoint" minOccurs="0" maxOccurs="unbounded">
                                                    <xs:complexType>
                                                      <xs:attribute name="vc" type="xs:string" />
                                                      <xs:attribute name="uspid" type="xs:string" />
                                                      <xs:attribute name="ordinal" type="xs:string" />
                                                      <xs:attribute name="offset" type="xs:string" />
                                                      <xs:attribute name="sl" type="xs:string" />
                                                      <xs:attribute name="path" type="xs:string" />
                                                      <xs:attribute name="offsetend" type="xs:string" />
                                                      <xs:attribute name="fileid" type="xs:string" />
                                                      <xs:attribute name="offsetchain" type="xs:string" />
                                                    </xs:complexType>
                                                  </xs:element>
                                                </xs:sequence>
                                              </xs:complexType>
                                            </xs:element>
                                            <xs:element name="MethodPoint" minOccurs="0" maxOccurs="unbounded">
                                              <xs:complexType>
                                                <xs:attribute name="vc" type="xs:string" />
                                                <xs:attribute name="uspid" type="xs:string" />
                                                <xs:attribute name="ordinal" type="xs:string" />
                                                <xs:attribute name="offset" type="xs:string" />
                                                <xs:attribute name="sl" type="xs:string" />
                                                <xs:attribute name="sc" type="xs:string" />
                                                <xs:attribute name="el" type="xs:string" />
                                                <xs:attribute name="ec" type="xs:string" />
                                                <xs:attribute name="bec" type="xs:string" />
                                                <xs:attribute name="bev" type="xs:string" />
                                                <xs:attribute name="fileid" type="xs:string" />
                                              </xs:complexType>
                                            </xs:element>
                                          </xs:sequence>
                                          <xs:attribute name="visited" type="xs:string" />
                                          <xs:attribute name="cyclomaticComplexity" type="xs:string" />
                                          <xs:attribute name="sequenceCoverage" type="xs:string" />
                                          <xs:attribute name="branchCoverage" type="xs:string" />
                                          <xs:attribute name="isConstructor" type="xs:string" />
                                          <xs:attribute name="isStatic" type="xs:string" />
                                          <xs:attribute name="isGetter" type="xs:string" />
                                          <xs:attribute name="isSetter" type="xs:string" />
                                          <xs:attribute name="skippedDueTo" type="xs:string" />
                                        </xs:complexType>
                                      </xs:element>
                                    </xs:sequence>
                                  </xs:complexType>
                                </xs:element>
                              </xs:sequence>
                            </xs:complexType>
                          </xs:element>
                        </xs:sequence>
                      </xs:complexType>
                    </xs:element>
                  </xs:sequence>
                  <xs:attribute name="skippedDueTo" type="xs:string" />
                  <xs:attribute name="hash" type="xs:string" />
                </xs:complexType>
              </xs:element>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:choice>
    </xs:complexType>
  </xs:element>
</xs:schema>

1条回答
SAY GOODBYE
2楼-- · 2020-02-02 01:37

The short answer is that you need to manually add [XmlInclude(typeof(SequencePoint))] to your MethodPoint class:

[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.33440")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)]
[XmlInclude(typeof(SequencePoint))]
public partial class CoverageSessionModulesModuleClassesClassMethodsMethodMethodPoint {
    private string vcField;
    private string uspidField;
    private string ordinalField;
    private string offsetField;
    private string slField;
    private string scField;
    private string elField;
    private string ecField;
    private string becField;
    private string bevField;
    private string fileidField;
}

You also need to make SequencePoint inherit from MethodPoint if it does not already do so.

You need to do this because, when you use xsd.exe to generate an XSD from an XML sample, and then c# classes in turn, it apparently doesn't add polymorphic subtype attributes to base types automatically when the attribute xsi:type="SomePolymoirphicSubType" appears in the XML, even though it seems it should.

The explanation 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, it requires to be informed in advance of all possible types using XmlIncludeAttribute. Thus, if I create the following type hierarchy:

[XmlInclude(typeof(SequencePoint))]
public class MethodPoint
{
}

public class SequencePoint : MethodPoint
{
}

And serialize it as follows:

var test = new SequencePoint();

var serializer = new XmlSerializer(typeof(MethodPoint));
var sb = new StringBuilder();
using (var stream = new StringWriter(sb))
    serializer.Serialize(stream, test);

Console.WriteLine(sb);

I get the following XML:

<MethodPoint 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
 xsi:type="SequencePoint" />

Then if I deserialize it using var serializer = new XmlSerializer(typeof(MethodPoint)), I get back a SequencePoint, not its base class. And if I use xsd.exe to generate a schema for these classes, I get:

<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="MethodPoint" nillable="true" type="MethodPoint" />
  <xs:complexType name="MethodPoint" />
  <xs:complexType name="SequencePoint">
    <xs:complexContent mixed="false">
      <xs:extension base="MethodPoint" />
    </xs:complexContent>
  </xs:complexType>
  <xs:element name="SequencePoint" nillable="true" type="SequencePoint" />
</xs:schema>

Notice the xs:extension? That's how the XSD indicates a polymorphic subtype. And then if I run xsd.exe backwards to regenerate my classes, I get:

[System.Xml.Serialization.XmlIncludeAttribute(typeof(SequencePoint))]
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)]
public partial class MethodPoint {
}

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)]
public partial class SequencePoint : MethodPoint {
}

As you can see, the XmlIncludeAttribute is there and the resulting classes are equivalent to the originals. Everything is working perfectly so far.

But, it seems that when inferring an XSD from a sample XML file, xsd.exe doesn't pick up on the presence of the xsi:type attribute. For instance, if I create an XSD from the trivial XML above, the result is:

<xs:schema id="MethodPoint" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xs:element name="MethodPoint" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
    <xs:complexType>
      <xs:choice minOccurs="0" maxOccurs="unbounded" />
    </xs:complexType>
  </xs:element>
</xs:schema>

The polymorphic subtype is completely missing. Classes generated from this XSD will not be able to deserialize that XML.

So it seems that generating c# classes from an XML sample with xsd.exe just isn't as reliable as generating them from a proper XSD. Specifically, in cases where xsi:type appears in the XML file, you will need to manually fix either the generated classes or the generated XSD to implement the required hierarchy. This may be either a limitation or a bug in the tool.

(The limitation/bug will also appear in Paste XML as Classes which uses xsd.exe internally.)

查看更多
登录 后发表回答