Search code examples
c#.netxmlxml-serializationxsd

C# serializing xsi:type and xsd


I have a schema defined as follows:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="Books" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xs:element name="Books" msdata:IsDataSet="true" msdata:Locale="en-US">
    <xs:complexType>
      <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element name="Book" type="MyBookType"></xs:element>
      </xs:choice>
    </xs:complexType>
  </xs:element>

  <xs:complexType name="MyBookType">
  ...
  </xs:complexType>

</xs:schema>

Using this schema and xsd.exe, I generate the classes that will be used during serialization. The class generated by the above schema produces the following xml when serialized:

<Books>
  <Book>
  ...
  </Book>
</Books>

This xml is used in a SOAP request and the service on the other end expects the following xml:

<Books>
  <Book xsi:type="MyBookType">
  ...
  </Book>
</Books>

How can I edit my schema so that the xsi:type attribute is included in the serialized xml?


Solution

  • Use a derived type, and an XmlInclude attribute. For example:

    public class Book
    {
        public string Title;
        public string Author;
    }
    
    public class MyBookType : Book { }
    
    [XmlInclude(typeof(MyBookType))]
    [XmlRoot("Books")]
    public class Books : List<Book> { }
    
    public void Run()
    {
        var b =  new Books();
        b.Add(new MyBookType
            {
                Title = "The Art of War",
                Author = "Sun Tzu"
            });
        b.Add(new MyBookType
            {
                Title = "Great Expectations",
                Author = "Charles Dickens"
            });
    
        var s = new XmlSerializer(typeof(Books));
        s.Serialize(Console.Out, b);
    }
    

    Running this produces this output:

    <?xml version="1.0" encoding="IBM437"?>
    <Books xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
           xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <Book xsi:type="MyBookType">
        <Title>The Art of War</Title>
        <Author>Sun Tzu</Author>
      </Book>
      <Book xsi:type="MyBookType">
        <Title>Great Expectations</Title>
        <Author>Charles Dickens</Author>
      </Book>
    </Books>
    

    Since you're using a SOAP request, I'm assuming ASMX, which means the serialization happens implicitly. You will need to apply [XmlInclude] to whatever holds the collection of books. This could be a parameter in a webmethod, for example.

    You can automatically generate the appropriate XmlInclude attribute, starting from XSD and WSDL, if you define the types in XSD, with the inheritance relationship I illustrated in C# code.

    In WSDL, the request message might take a Books type, which is a collection of Book. Separately, define a MyBookType which derives from Book but does not extend it.