Jason Roell
Jason Roell

Reputation: 6809

XML object binding of types derived by restriction

I am trying to take a XML Schema and convert the relationships to C# classes. The situation I am running into is as follows:

I have a complex type in the schema which has 4 elements all of a certain type:

<xs:complexType name="ReportingBaseType" abstract="true">
    <xs:sequence>
        <xs:element name="Category" type="ent:ReportingCategoryConceptType" minOccurs="0" maxOccurs="0"/>
        <xs:element name="Frequency" type="ent:ReportingFrequencyType"/>
        <xs:element name="AdjustmentFrequency" type="ent:ReportingAdjustmentFrequencyType"/>
        <xs:element name="FirstReportDueDate" type="ent:CXMLDateType" minOccurs="0" maxOccurs="0"/>
    </xs:sequence>
</xs:complexType>

Next, I have multiple complex types that can restrict or extend this type:

<xs:complexType name="ReportingClassA">
   <xs:restriction base="ReportingBaseType">
    <xs:sequence>
        <xs:element name="Category" type="ent:ReportingClassAType" minOccurs="0" maxOccurs="0"/>
        <xs:element name="Frequency" type="ent:FequencyForThisClassType"/>
    </xs:sequence>
   </restriction>
</xs:complexType>

<xs:complexType name="ReportingClassB">
   <xs:restriction base="ReportingBaseType">
    <xs:sequence>
        <xs:element name="Category" type="ent:NewTypeForThisClass" minOccurs="0" maxOccurs="0"/>
        <xs:element name="Frequency" type="ent:AnotherNewType"/>
        <xs:element name="AdjustmentFrequency" type="ent:ThisIsADifferntTypeThenParent"/>
        <xs:element name="FirstReportDueDate" type="ent:AndAnotherNewType" minOccurs="0" maxOccurs="
    </xs:sequence>
  </restriction>
</xs:complexType>

How can I (in C#) derive from the parent class (ReportingBaseType) but have different types for the properties like they are defined in the Schema?

I have thought of using the new keyword when defining my properties on the derived classes to "hide" the inherited members but I have heard that when serializing this object that the XML serializer may not handle this correctly.

Does anyone have any advice how to correctly relate my schema to C# classes and still be able to serialize my c# objects to correct XML object that will pass the XML Schema Validation?

EDIT: I have tried using Xsd2Code and the built in xsd.exe tool to handle the correct C# classes but they do not generate the detail that I need. Especially in the circumstances that I have described above.

Upvotes: 2

Views: 641

Answers (1)

softwariness
softwariness

Reputation: 4052

.NET XML Serialization can get messy when you want to do something a bit more complicated.

One approach you can take in these kinds of scenarios is to do the XML serialization and deserialization with a proxy class, rather than on the type that you want to deal with directly in your application.

In your case, as your are doing restriction of types, the base class may already be an adequate proxy class for [de]serialization, as the XML data you can fit in the restricted types should fit into the base type too. The example below goes with this approach, but if that doesn't work for you, then you could define a separate proxy type that is used only for the XML [de]serialization of the base and restricted types to their corresponding C# classes.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace XmlTest
{
    public class BaseMemberType
    {
        public string SomeValue
        {
            get;
            set;
        }
    }

    public abstract class BaseType
    {
        [XmlElement("Member", Type=typeof(BaseMemberType))]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public virtual BaseMemberType _MemberSerializer
        {
            get
            {
                return Member;
            }
            set
            {
                this.Member = (BaseMemberType)value;
            }
        }

        [XmlIgnore()]
        public BaseMemberType Member
        {
            get;
            set;
        }
    }

    public class DerivedMemberType
    {
        public int SomeValue
        {
            get;
            set;
        }
    }

    public class DerivedType : BaseType
    {
        [XmlIgnore()]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public override BaseMemberType _MemberSerializer
        {
            get
            {
                return new BaseMemberType()
                {
                    SomeValue = this.Member.SomeValue.ToString()
                };
            }
            set
            {
                this.Member = new DerivedMemberType()
                {
                    SomeValue = int.Parse(value.SomeValue)
                };
            }
        }

        [XmlIgnore()]
        public new DerivedMemberType Member
        {
            get;
            set;
        }
    }

    public class AnotherDerivedType : BaseType
    {
    }

    public class RootElement
    {
        public DerivedType First
        {
            get;
            set;
        }

        public AnotherDerivedType Second
        {
            get;
            set;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var serializer = new System.Xml.Serialization.XmlSerializer(typeof(RootElement));
            RootElement rootElement = null;
            string xml = @"
<RootElement>
<First>
<Member><SomeValue>1234</SomeValue></Member>
</First>
<Second>
<Member><SomeValue>Some string</SomeValue></Member>
</Second>
</RootElement>
";
            using(var reader = new System.IO.StringReader(xml))
            {
                rootElement = (RootElement)serializer.Deserialize(reader);
            }

            Console.WriteLine("First.Member.SomeValue: {0}", rootElement.First.Member.SomeValue);
            Console.WriteLine("Second.Member.SomeValue: {0}", rootElement.Second.Member.SomeValue);

            using (var writer = new System.IO.StringWriter())
            {
                serializer.Serialize(writer, rootElement);
                string serialized = writer.ToString();
                Console.WriteLine("Deserialized: ");
                Console.WriteLine(serialized);
            }
        }
    }
}

You'll see that BaseMemberType represents a base type that is restricted by DerivedMemberType so that the string member is restricted to being an int. BaseType contains BaseMemberType, and DerivedType restricts BaseType to use DerivedMemberType for the member instead. Note the use of XmlIgnore on the property that is hiding the one in the base class using the new keyword, and the use of a separate virtual property for the serialization, in order to address the problem that new-ed properties won't work with the .NET XML serialization code.

One caveat you should bear in mind when using proxies for [de]serialization, is that if you have a collection instead of a single object (List<MyType> vs MyType), allowing for a maxOccurs > 1, then you will need to have a deeper proxying than in the single object case. This is because the XML serializer will add to the collection returned by the proxy property's getter, rather than setting directly on the proxy property. A solution for that is to arrange for your proxy property to do an exchange of responsibility with the property it is proxying. This can be done by having private members for both proxy data and for the "real" application-facing data, but it is enforced by both the proxy and non-proxy properties that only one or the other is set at a given time, and that translation between them occurs when needed.

Upvotes: 2

Related Questions