views:

1821

answers:

1

Hello,

I've come up against a bit of a brick wall with Microsoft's .net XmlSerializer. I'm trying to deserialize some XML into an object, which is fine if I'm using a single object, but the problem comes when one puts an object into a List and tries to serialize/deserialize that. First up, here's a sample C# windows console program to illustrate the problem:

http://pastebin.com/m22e6e275

If the class 'Foo' is serialized as a root element, things behave fine, and as expected - the JezNamespace xmlns is applied to the root Foo element, and deserialization occurs fine. However if I create a List and serialize that, the XmlSerializer: - Creates a root element of ArrayOfFoo - Puts the Foo elements as children of that element - Sets the xmlns of EVERY child of Foo to the JezNamespace namespace!

I'm OK with the first two, but the third one seems mad... maybe a bug in XmlSerializer? Is there some way I can deal with this behaviour? I don't want to be specifying my namespace for every child of Foo, I just want to specify it for Foo. If I do that, currently, XmlSerializer doesn't deserialize the class properly - it just skips over any Foo element with the JezNamespace xmlns set. I have to set ALL the child elements to have that xmlns.

What I'd like to get to is XmlSerializer generating something like:

<ArrayOfFoo>
    <Foo xmlns="http://schemas.datacontract.org/2004/07/JezNamespace"&gt;
        <Field1>hello</Field1>
        <Field2>world</Field2>
    </Foo>
    <Foo xmlns="http://schemas.datacontract.org/2004/07/JezNamespace"&gt;
        <Field1>aaa</Field1>
        <Field2>bbb</Field2>
    </Foo>
</ArrayOfFoo>

... and then have XmlSerializer be able to deserialize that properly into a List. Any ideas how I can get it doing this?

+3  A: 

Your code has two attributes on Foo that, so far as I can tell, you've put there to try to associate a namespace:

 [XmlRootAttribute(Namespace="http://schemas.datacontract.org/2004/07/JezNamespace",
                   IsNullable=false)]
 [XmlTypeAttribute(AnonymousType=true,
                   Namespace="http://schemas.datacontract.org/2004/07/JezNamespace")]

The first one is simply ignored, because in your scenario Foo is not a root element. The second one doesn't quite do what you probably think it does - the namespace in it is a namespace of an XSD type, not namespace of an element itself.

To specify name and namespace of the element, you do instead need to use XmlArrayItemAttribute on the List<Foo> property of a parent class (oh, and you will need that parent class):

public class FooParent
{
    [XmlArrayItem(ElementName="Foo",
                  Namespace="http://schemas.datacontract.org/2004/07/JezNamespace")]
    public List<Foo> Foos { get; private set; }
}

This will produce:

<FooParent>
  <Foos>
    <Foo xmlns="http://schemas.datacontract.org/2004/07/JezNamespace"&gt;
      <Field1>hello</Field1>
      <Field2>world</Field2>
    </Foo>
    ...

Alternatively, if you do not want that Foos intermediate element at all, you can replace XmlArrayItem in code above with XmlElement. In this case, the output XML will look like this:

<FooParent>
  <Foo xmlns="http://schemas.datacontract.org/2004/07/JezNamespace"&gt;
    <Field1>hello</Field1>
    <Field2>world</Field2>
  </Foo>
Pavel Minaev
Hi Pavel, thanks for those useful comments. The only problem is, as you said, having to declare that FooParent class. This will require an annoying amount of refactoring of code as in the project I'm actually needing this for (my program was obviously just a test case), the equivalent to 'Foo' is a large set of classes that may need to be returned in a list. Is there absolutely no way I can achieve this effect without the FooParent wrapper class, instead directly serializing/deserializing List<Foo> somehow?
Jez
I'm not aware of any good one. The closest you could get is to use CodeDOM or Reflection.Emit to generate those wrapper classes for you at runtime.
Pavel Minaev
OK, thanks. Disappointing that Microsoft didn't provide this functionality in XmlSerializer.
Jez
XmlSerializer is semi-deprecated (as in, it's there and will remain, but you should consider using a more modern serialization stack if possible). In particular, on .NET 3.0+, consider looking at DataContractSerializer. That has a bunch of limitations of its own - though looking at your XML, you have no attributes, just elements, so should be fine - but it gives much better control over how serialization works.
Pavel Minaev