.NET Framework Developer Center > .NET Development Forums > ASMX Web Services and XML Serialization > XmlSerializer doesn't serialize attribute on List<T> subclass
Ask a questionAsk a question
 

AnswerXmlSerializer doesn't serialize attribute on List<T> subclass

  • Wednesday, July 19, 2006 6:33 PMRenaud Bédard Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Hi, I'm having a little problem with the XmlSerializer...

    I have a class which inherits from List<T>, like so :

    public class SampledList<T> : List<T>
    {
    private SamplingTypeEnum samplingType = SamplingTypeEnum.RandomNoRepeat;

    [XmlAttribute()]
    public SamplingTypeEnum SamplingType {
    get { return samplingType; }
    set { samplingType = value; }
    }

    public enum SamplingTypeEnum { Sequential, RandomNoRepeat, RandomWithOccurence };
    }

    When I serialize an instance of this class without specifying any attribute on the member using XmlSerializer, the "SamplingType" property is never serialized. All of the list members are serialized correctly.

    Am I missing something?

    Edit : I just saw that this problem was already mentionned in this post :
    http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=259037&SiteID=1
    But this one is unanswered... I hope there's a simple way of doing this.

Answers

  • Thursday, July 20, 2006 1:04 AMElena KharitidiModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    You are correct: XmlSerializer does not serialize any members if a collection. Only collection items get serialized. This is by design, basically a decision was made to handle collections as arrays not as classes with multiple properties, so collections should look like arrays on the wire, therefore they do not have any members other then collection items, and can be “flattened” by adding the [XmlElement] to the member of the ICollection type.

     

    Your solution implementing IXmlSerializable is valid one, as long as the schema you return from [XmlSchemaProvider] method matches the instance you produce, otherwise you will not be able to interop.  You could also use List<T> as a member of your class not the base type.

     

    Thanks,

    Elena Kharitidi

All Replies

  • Wednesday, July 19, 2006 9:30 PMRenaud Bédard Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Since I couldn't wait for an answer, I made a costly workaround that some people might be interested in. It works in my case, might not work in all cases, but it's at least something.

    What this does is use IXmlSerializable to do custom serialization.
    - All [XmlAttribute] properties will be serialized in the Xml and deserialized back.
    - All items of the collection will be serialized too, much like the normal behaviour
    - Since I can't access the [XmlArrayItem()] attribute of the list's declaring property, I made a field in the class that emulates its functionality.

    ___

    public class ListSubclass<T> : List<T>, IXmlSerializable where T : new()
        {
            string theAttribute;
            string arrayItemName;

            [XmlAttribute("TheAttributeName")]
            public string TheAttribute
            {
                get { return theAttribute; }
                set { theAttribute = value; }
            }

            [XmlIgnore()]
            public string ArrayItemName
            {
                set { arrayItemName = value; }
            }

            #region IXmlSerializable Members

            public XmlSchema GetSchema()
            {
                // A little too complicated for my taste
                return null;
            }

            public void ReadXml(XmlReader reader)
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(reader);
                XmlElement docElem = doc.DocumentElement;

                // Reflect the [XmlAttribute]'s
                PropertyInfo[] props = this.GetType().GetProperties();
                foreach (PropertyInfo prop in props)
                {
                    object[] attrs = prop.GetCustomAttributes(typeof(XmlAttributeAttribute), false);
                    if (attrs != null && attrs.Length == 1)
                    {
                        string name = (attrs[0] as XmlAttributeAttribute).AttributeName ?? prop.Name;

                        if (docElem.Attributes[name] != null)
                            prop.GetSetMethod().Invoke(this, new object[] { docElem.Attributes[name].Value });
                    }
                }

                // Deserialize the collection members
                XmlNodeList nodes = docElem.SelectNodes("./*");
                foreach (XmlNode node in nodes)
                {
                    // Make sure it isn't a text node or something
                    if (node is XmlElement)
                    {
                        XmlElement elem = doc.CreateElement(typeof(T).Name);
                        elem.InnerXml = node.InnerXml;
                        foreach (XmlAttribute xmlAttr in (node as XmlElement).Attributes) {
                            XmlAttribute newAttr = doc.CreateAttribute(xmlAttr.Name);
                            newAttr.Value = xmlAttr.Value;
                            elem.Attributes.Append(newAttr);
                        }

                        this.Add(Serializer.Deserialize<T>(elem));
                    }
                }
            }

            public void WriteXml(XmlWriter writer)
            {
                // Reflect the [XmlAttribute]'s
                PropertyInfo[] props = this.GetType().GetProperties();
                foreach (PropertyInfo prop in props)
                {
                    object[] attrs = prop.GetCustomAttributes(typeof(XmlAttributeAttribute), false);
                    if (attrs != null && attrs.Length == 1)
                    {
                        string name = (attrs[0] as XmlAttributeAttribute).AttributeName;
                        if (name == null) name = prop.Name;

                        object value = prop.GetGetMethod().Invoke(this, null);
                        if(value != null)
                            writer.WriteAttributeString(name, value.ToString());
                    }
                }

                // Serialize the collection members
                foreach (T item in this)
                {
                    string itemName = arrayItemName ?? typeof(T).Name;

                    XmlElement serializedItem = Serializer.Serialize<T>(item);
                    writer.WriteStartElement(itemName);
                    foreach (XmlAttribute xmlAttr in serializedItem.Attributes)
                    {
                        // We don't want to write the xsd/xsi namespace attributes
                        if(!(xmlAttr.Name.StartsWith("xmlns:xsd") || xmlAttr.Name.StartsWith("xmlns:xsi")))
                            writer.WriteAttributeString(xmlAttr.Name, xmlAttr.Value);
                    }
                    writer.WriteRaw(serializedItem.InnerXml);
                    writer.WriteEndElement();
                }
            }
            #endregion
        }

        public static class Serializer
        {
            public static T Deserialize<T>(XmlElement node) where T : new()
            {
                T customType = new T();
                XmlSerializer serializer = new XmlSerializer(typeof(T));

                XmlDocument doc = new XmlDocument();
                doc.AppendChild(doc.CreateXmlDeclaration("1.0", "utf-8", String.Empty));
                doc.AppendChild(doc.ImportNode(node, true));

                using (MemoryStream stream = new MemoryStream())
                using (StreamWriter writer = new StreamWriter(stream))
                using (StreamReader reader = new StreamReader(stream))
                {
                    doc.Save(writer);
                    stream.Position = 0;
                    customType = (T)serializer.Deserialize(reader);
                }

                return customType;
            }

            public static XmlElement Serialize<T>(T t) where T : new()
            {
                XmlSerializer serializer = new XmlSerializer(typeof(T));

                XmlElement elem;
                using (MemoryStream stream = new MemoryStream())
                using (StreamWriter writer = new StreamWriter(stream))
                using (StreamReader reader = new StreamReader(stream))
                {
                    serializer.Serialize(writer, t);

                    XmlDocument doc = new XmlDocument();
                    stream.Position = 0;
                    doc.Load(reader);
                    elem = doc.DocumentElement;
                }

                return elem;
            }
        }
  • Thursday, July 20, 2006 1:04 AMElena KharitidiModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    You are correct: XmlSerializer does not serialize any members if a collection. Only collection items get serialized. This is by design, basically a decision was made to handle collections as arrays not as classes with multiple properties, so collections should look like arrays on the wire, therefore they do not have any members other then collection items, and can be “flattened” by adding the [XmlElement] to the member of the ICollection type.

     

    Your solution implementing IXmlSerializable is valid one, as long as the schema you return from [XmlSchemaProvider] method matches the instance you produce, otherwise you will not be able to interop.  You could also use List<T> as a member of your class not the base type.

     

    Thanks,

    Elena Kharitidi

  • Thursday, July 20, 2006 1:10 AMRenaud Bédard Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Thanks a lot for the clarification...
    I would've hoped for a simpler solution obviously, but I can live with that.

    Also I realized that instead of serializing the collection items myself, I could use the XmlSerializer to serialize the List<T> that my custom class contains and add-up the Attribute nodes I want to this output.

    Edit : Actually, I can't serialize the List<T> "normally" if I want to use [XmlArrayItem]... I can't reflect the calling property's attributes, and I can't assign instance-dependant attributes on the list either.
    This is sad... Either you have to use the built-in functions, either you have to re-do everything yourself. :/
  • Tuesday, September 25, 2007 10:50 PMestump1 Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

     

    "...and can be “flattened” by adding the [XmlElement] to the member of the ICollection type."

     

    Can you please explain how to add the [XmlElement] to the member of the ICollectionType?

    I have a class that's inheriting from List<MyClass> and need somewhere to put the [XmlElement] tag so it serializes in the format I need.

     

    thanks

     

  • Saturday, February 09, 2008 9:58 PMJozef Izso Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Hi,

    I also needed to specify the name of elements under which are items from collections serialized. You must use [XmlType] attribute on class which is used in collection to change the element name without implementing your own serialization.

       
    Code Snippet

        [XmlRoot("filters")]
        public class FilterCollection : List<Filter>
        {
        }

        [XmlRoot("filter")] // used when <filter> is the root of XML document
        [XmlType("filter")] // used for FilterCollection.Items
        public class Filter
        {
            public Filter()
            {
            }

            [XmlAttribute("type")]
            public string Type { get; set; }

            [XmlAttribute("sFilter")]
            public string FilterId { get; set; }

            [XmlText]
            public string Name { get; set; }
        }




    Regards,
    Jozef