Tuesday, October 1, 2013

Issue with XSD element in ASP.Net when there is Inheritance

In one of my project I was consuming an web service which had some Inheritance where the parent Class is EmployeeProfile and the child Class is FieldEmployeeProfile. When I was calling a method returning Child Class object Array, I was not getting the Child Class specific Properties (not those inherited from Parent) set. Instead, I found a property in Parent Class called "Any' which is an Array of XMLElement and having the missing Child Object Property equivalent Name-Values in the XML element array.

I will try to explain the problem using a simpler example I created and the resolution.

Server Side Classes
    public class Shape
    {
        //[System.Xml.Serialization.XmlTypeAttribute("Name")]
        [System.Xml.Serialization.XmlElement(Order = 0)]
        public string Name { get; set; }

        [System.Xml.Serialization.XmlElement(Order = 1)]
        public virtual int Area { get; set; }

        [System.Xml.Serialization.XmlAnyElementAttribute(Order = 2)]
        public System.Xml.XmlElement[] AnyElement
        {
            get;
            set;
        }
    }

    public class Rectangle : Shape
    {
        [System.Xml.Serialization.XmlElement(Order = 0)]
        public int Side1 { get; set; }

        [System.Xml.Serialization.XmlElement(Order = 1)]
        public int Side2 { get; set; }

        [System.Xml.Serialization.XmlElement(Order = 2)]
        public override int Area
        {
            get
            {
                return Side1 * Side2;
            }
        }
        [System.Xml.Serialization.XmlAnyElement(Order = 3)]
        public System.Xml.XmlElement[] AnyChildElement
        {
            get;
            set;
        }
    }

Note the AnyElement in Parent Class and special attention to Order value. I will explain it later.

The WSDL generated for these classes as as below


<s:complexType name="Shape">
  <s:sequence>
    <s:element minOccurs="0" maxOccurs="1" name="Name" type="s:string" />
    <s:element minOccurs="1" maxOccurs="1" name="Area" type="s:int" />
    <s:any minOccurs="0" maxOccurs="unbounded" />
  </s:sequence>
</s:complexType>

<s:complexType name="Rectangle">
  <s:complexContent mixed="false">
    <s:extension base="tns:Shape">
      <s:sequence>
        <s:element minOccurs="1" maxOccurs="1" name="Side1" type="s:int" />
        <s:element minOccurs="1" maxOccurs="1" name="Side2" type="s:int" />
        <s:any minOccurs="0" maxOccurs="unbounded" />
      </s:sequence>
    </s:extension>
  </s:complexContent>
</s:complexType> 

When my web method returns Rectangle I get it as below with Side1 and Side2 property values as 0 and the base Any Property is having those missing property related XMLElements.



To fix this the easiest way is to remove the "Any" property related code from the Base Class shape in "Reference.Cs" file as below (you need to click on "Show All Files" in Solution explorer top to show Reference.cs file in solution explorer).

The alternate approach is to update the reference.cs file for the code generated for child class Rectangle as below:
[System.Xml.Serialization.XmlElementAttribute(Order = 0)]
        public int Side1
        {
            get
            {
                if (this.side1Field == 0)
                {
                    foreach (System.Xml.XmlElement xe in base.Any)
                    {
                        if (xe.Name == "Side1")
                        {
                            int.TryParse(xe.InnerText, out this.side1Field);
                        }
                    }
                }
                return this.side1Field;
            }
            set
            {
                this.side1Field = value;
            }
        }

        ///
        [System.Xml.Serialization.XmlElementAttribute(Order = 1)]
        public int Side2
        {
            get
            {
                if (this.side2Field == 0)
                {
                    foreach (System.Xml.XmlElement xe in base.Any)
                    {
                        if (xe.Name == "Side2")
                        {
                            int.TryParse(xe.InnerText, out this.side2Field);
                        }
                    }
                }
                return this.side2Field;
            }
            set
            {
                this.side2Field = value;
            }

        }

Note the tag is for expending the request & response payload for an web service in future without changing the WSDL. So you may require to actually utilize the Any property. So you shouldn't delete the property from reference.cs file if your code needs to handle any additional node without breaking it's logic.

I previously mentioned to pay special attention to Order value in the server side code for the classes. The reason is that all the properties following the Any element, will not be populated if you are not taking special care. It will have same null/0 value as the Child class properties.