views:

303

answers:

2

How can I force .NET's XmlSerializer to add an xsi:type="FooClass" to a member/node of type FooClass?

The scenario is a currently-released app, where in v.1 :

  • FooClass inherits FooBaseClass
  • FooPropertyA is on FooBaseClass
  • FooPropertyB is on FooClass
  • FooBaseClass is decorated with [XmlInclude(typeof(FooClass))]
  • BazClass has member Foo of type FooBaseClass
  • All sets of Baz.Foo are to a FooClass instance
  • All usages of Baz.Foo expect FooPropertyB (and so a FooClass instance vs FooBaseClass)

The goal: Remove FooBaseClass entirely, pushing FooBaseClass's members up into FooClass, while maintaining backward serialization compatibility

The problem: Then I lose the xsi:type="FooClass" attribute on the Baz.Foo serialization.

In other words the XmlSerializer.Serialize output of

public class BazClass
{
    public BazClass()
    {
        Foo = new FooClass { A = 5, B = "Hello" };
    }
    public FooClass Foo { get; set; }
}

public class FooClass
{
    public int FooPropertyA { get; set; }
    public string FooPropertyB { get; set; }
}

needs to be

<Baz>
    <Foo xsi:type="FooClass">
        <FooPropertyA>Hello</FooPropertyA>
        <FooPropertyB>5</FooPropertyB>
    </Foo>
</Baz>

Removing FooBasClass is easy, but then XmlSerializer no longer places xsi:type="FooClass" on Baz/Foo, and so v.1 XmlSerializer.Deserialize instantiates a FooBaseClass instance, not setting FooPropertyB, and assigns it to the Foo property of the parent Baz instance. Thus, any code which checks whether Baz.Foo is FooClass, or casts directly, fails.

The xsi:type attribute was placed automatically in the v.1 code which was

public class BazClass
{
    public BazClass()
    {
        Foo = new FooClass { A = 5, B = "Hello" };
    }
    public FooBaseClass Foo { get; set; }
}

public class FooClass : FooBaseClass
{
    public string FooPropertyB { get; set; }
}

[XmlInclude(typeof(FooClass))]    
public class FooBaseClass
{
    public int FooPropertyA { get; set; }
}

I think the short answer is that you can't - at least not without implementing I(Xml)Serializable or writing custom serialization code. However, I'm open to good suggestions. Meanwhile I have implemented a workaround hack below, and am hoping for something more elegant, or that at least allows me somehow to remove FooBaseClass entirely.

BazClass
{
    [XmlElement("Foo")]
    public FooBaseClass XmlFoo { get { return Foo; } set { Foo = (StartPicture)value; } }

    [XmlIgnore]
    public FooClass Foo { get; set; }
}    

FooClass : FooBaseClass
{
    public int FooPropertyB { get; set; }
    public string FooPropertyA { get; set; }
}

[XmlInclude("FooClass")]
FooBaseClass
{
}
A: 

I had no trouble producing the following:

<?xml version="1.0" encoding="utf-8"?>
<BazClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;
    <Foo xsi:type="FooClass">
        <FooPropertyA>Hello</FooPropertyA>
        <FooPropertyB>5</FooPropertyB>
    </Foo>
</BazClass>

from

[XmlInclude(typeof(FooClass))]
//[XmlType(TypeName = "FooBase", Namespace = "urn:namespace", IncludeInSchema = true)]
public class FooBaseClass
{
    public string FooPropertyA { get; set; }
}

//[XmlType(TypeName = "Foo", Namespace = "urn:namespace", IncludeInSchema = true)]
public class FooClass : FooBaseClass
{
    public int FooPropertyB { get; set; } 
}

public class BazClass
{
    public FooBaseClass Foo { get; set; }
}

(note the XmlType attributes are commented out. I wanted to see what happened if a namespace was specified)

Please show the code you used, and the XML it produced.

John Saunders
I've (hopefully) clarified my question above. Yes, your example would work, but it does not do what I'm wanting - i.e. remove FooBaseClass, pushing FooPropertyB up into FooClass. Your example actually mirrors my current state - not my desired state.
Tim Erickson
I just saw how my first line of the question had actually been quite misleading - my apologies.
Tim Erickson
+1  A: 

XmlSerializer can be pretty dumb and straightforward at times, which works to your advantage in this case. Just put it there manually:

public class FooClass
{
    public int FooPropertyA { get; set; }
    public string FooPropertyB { get; set; }

    [XmlAttribute("type", Namespace="http://www.w3.org/2001/XMLSchema-instance")]
    public string XsiType
    {
        get { return "Foo"; }
        set { }
    }
}
Pavel Minaev
worked like a charm! the value of a second set of eyes... :-)
Tim Erickson