views:

299

answers:

2

XmlRoot does not seem to work with classes that are contained within a collection. Here are the classes I have defined:

[XmlRoot("cars")]
public class CarCollection : Collection<Car>
{
}

[XmlRoot("car")]
public class Car
{
  [XmlAttribute("make")]
  public String Make { get; set; }

  [XmlAttribute("model")]
  public String Model { get; set; }
}

Here is the code I am using to serialize these objects:

  CarCollection cars = new CarCollection();
  cars.Add(new Car { Make = "Ford", Model = "Mustang" });
  cars.Add(new Car { Make = "Honda", Model = "Accord" });
  cars.Add(new Car { Make = "Toyota", Model = "Tundra" });

  using (MemoryStream memoryStream = new MemoryStream())
  {
    XmlSerializer carSerializer = new XmlSerializer(typeof(CarCollection));
    carSerializer.Serialize(memoryStream, cars);
    memoryStream.Position = 0;

    String xml = null;
    using (StreamReader reader = new StreamReader(memoryStream))
    {
      xml = reader.ReadToEnd();
      reader.Close();
    }
    memoryStream.Close();
  }

The xml after serialization looks like this:

<cars xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;
  <Car make="Ford" model="Mustang" />
  <Car make="Honda" model="Accord" />
  <Car make="Toyota" model="Tundra" />
</cars>

Notice the "C" in car is not lowercase. What do I need to change to make this happen? If I serialize the Car directly it comes out as I would expectg.

UPDATE: I found another workaround. I am not sure how much I like it but it will work for my case. If I create a custom class (see below) and have CarCollection derive from it the serialization works as I expected.

  public class XmlSerializableCollection<T> : Collection<T>, IXmlSerializable
  {
    public XmlSchema GetSchema()
    {
      return null;
    }

    public void ReadXml(XmlReader reader)
    {
      bool wasEmpty = reader.IsEmptyElement;
      reader.Read();
      if (wasEmpty)
      {
        return;
      }

      XmlSerializer serializer = new XmlSerializer(typeof(T));

      while (reader.NodeType != XmlNodeType.EndElement)
      {
        T t = (T)serializer.Deserialize(reader);
        this.Add(t);
      }

      if (reader.NodeType == XmlNodeType.EndElement)
      {
        reader.ReadEndElement();
      }
    }

    public void WriteXml(XmlWriter writer)
    {
      XmlSerializer reqSerializer = new XmlSerializer(typeof(T));
      foreach (T t in this.Items)
      {
        reqSerializer.Serialize(writer, t);
      }
    }
  }
+1  A: 

Perhaps this is a cop-out but I was able to make this work using the DataContractSerializer like this:

using System;
using System.IO;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;

class Program
{
    static void Main()
    {
     CarCollection cars = new CarCollection();
     cars.Add(new Car { Make = "Ford", Model = "Mustang" });
     cars.Add(new Car { Make = "Honda", Model = "Accord" });
     cars.Add(new Car { Make = "Toyota", Model = "Tundra" });

     using (MemoryStream memoryStream = new MemoryStream())
     {
      DataContractSerializer serializer 
       = new DataContractSerializer(typeof(CarCollection));
      serializer.WriteObject(memoryStream, cars);
      memoryStream.Position = 0;

      String xml = null;
      using (StreamReader reader = new StreamReader(memoryStream))
      {
       xml = reader.ReadToEnd();
       reader.Close();
      }
      memoryStream.Close();
     }
    }
}

[CollectionDataContract(Name = "cars")]
public class CarCollection : Collection<Car> { }

[DataContract(Name = "car")]
public class Car
{
    [DataMember(Name = "make")]
    public String Make { get; set; }

    [DataMember(Name = "model")]
    public String Model { get; set; }
}

Output:

<cars xmlns="http://schemas.datacontract.org/2004/07/"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance"&gt;
  <car>
    <make>Ford</make>
    <model>Mustang</model>
  </car>
  <car>
    <make>Honda</make>
    <model>Accord</model>
  </car>
  <car>
    <make>Toyota</make>
    <model>Tundra</model>
  </car>
</cars>

Notice that the attributes on your types have changed to support the usage of DataContractSerializer. I don't know if this is the direction that you want to go with this but I have found that in almost every case I prefer to use the DataContractSerializer in place of the older XmlSerializer.

Andrew Hare
That might work. I would just need to get the make and model properties to be attributes instead of child nodes.
Shaun Bowe
My mistake - I will edit to fix...
Andrew Hare
+1  A: 

XmlRootAttribute only applies if the element is the root of the object graph being serialized, ie. the object you pass to the XmlSerializer instance.

To control serialization of collections, you would normally use the XmlElementAttribute to specify the element name to serialize the child objects with. Unfortunately, this attribute can only be applied to a field or property and not a class.

If you can expose your collection as a property of a class, you can use the attribute as follows:

[XmlRoot("cars")]
public class CarList
{
    [XmlElement("car")]
    public CarCollection Cars { get; set; }
}

With your code sample, produces the following result:

<cars xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;
  <car make="Ford" model="Mustang" />
  <car make="Honda" model="Accord" />
  <car make="Toyota" model="Tundra" />
</cars>

It's a bit of a work-around, but it's the closest you can get without lots of custom code.

Programming Hero