Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.2k views
in Technique[技术] by (71.8m points)

.net - How can I force the use of an xsi:type attribute?

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
{
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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 { }
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...