2

I have properties that have ids and values and a name. Can I represent all those with a single class using XmlElement/XmlArray C# annotations? I would like to derive the xml element name from the class attribute name;

my class would look like:

public class Property {
   public string name; //could be enum
   public int id; 
   public string value;
}

e.g:

new Property("property1name",2,"testvalue");
new Property("property2name",10,"anothervalue");

I would like to have xml that looks like:

<property1name><id>2</id><value>testvalue</value></property1name>
<property2name><id>10</id><value>anothervalue</value></property2name>

instead of the usual

<property><name>property1name</name><id>2</id><value>testvalue</value></property>
<property><name>property2name</name><id>10</id><value>anothervalue</value></property>

In other words the xmlelement gets its name from attribute name of the class Property

11
  • What would be the gain of this approach? Commented Feb 27, 2015 at 16:30
  • if there are 100 properties that just differ by name. I wouldn't want to have 100 classes for those just to change the name of the element. I know that could make things difficult for deserialization but I wonder if my requirement could be tackled somehow Commented Feb 27, 2015 at 16:31
  • Why not put the name as an attribute if you don't want is as an element? [XmlAttribute("name")] Commented Feb 27, 2015 at 16:33
  • If these are in a list, you might try something like this: stackoverflow.com/questions/28587155/… Commented Feb 27, 2015 at 16:35
  • 1
    There's no way to do this with XmlSerializer using just attributes. You would need to implement IXmlSerializable on a custom collection. Commented Feb 27, 2015 at 16:37

2 Answers 2

4

Update

And here's a quick adaptation to handle your Property class. First, a List<T> subclass that implements IXmlSerializable:

public interface IHasElementName
{
    string ElementName { get; set; }
}

public class XmlNamedElementList<T> : List<T>, IXmlSerializable where T : IHasXmlElementName
{
    // https://msdn.microsoft.com/en-us/library/System.Xml.Serialization.XmlSerializer%28v=vs.110%29.aspx
    // Any serializer created with the "XmlSerializer(typeof(T), rootAttribute)" must be cached
    // to avoid resource & memory leaks.
    class ValueSerializerCache
    {
        // By using a nested class with a static constructor, we defer generation of the XmlSerializer until it's actually required.
        static ValueSerializerCache()
        {
            var rootAttribute = new XmlRootAttribute();
            rootAttribute.ElementName = ValueTypeName;
            rootAttribute.Namespace = ValueTypeNamespace;
            serializer = new XmlSerializer(typeof(T), rootAttribute);
        }

        static readonly XmlSerializer serializer;

        internal static XmlSerializer Serializer { get { return serializer; } }
    }

    static string ValueTypeName { get { return typeof(T).DefaultXmlElementName(); } }

    static string ValueTypeNamespace { get { return typeof(T).DefaultXmlElementNamespace(); } }

    #region IXmlSerializable Members

    XmlSchema IXmlSerializable.GetSchema()
    {
        return null;
    }

    void IXmlSerializable.ReadXml(XmlReader reader)
    {
        if (reader.IsEmptyElement)
        {
            reader.Read();
            return;
        }

        var typeName = ValueTypeName;
        reader.ReadStartElement(); // Advance to the first sub element of the list element.
        while (reader.NodeType == XmlNodeType.Element)
        {
            var name = reader.Name;
            using (var subReader = reader.ReadSubtree())
            {
                var doc = XDocument.Load(subReader);
                if (doc != null && doc.Root != null)
                {
                    doc.Root.Name = doc.Root.Name.Namespace + typeName;
                    using (var docReader = doc.CreateReader())
                    {
                        var obj = ValueSerializerCache.Serializer.Deserialize(docReader);
                        if (obj != null)
                        {
                            T value = (T)obj;
                            value.ElementName = XmlConvert.DecodeName(name);
                            Add(value);
                        }
                    }
                }
            }
            // Move past the end of item element
            reader.Read();
        }
        // Move past the end of the list element
        reader.ReadEndElement();
    }

    void IXmlSerializable.WriteXml(XmlWriter writer)
    {
        foreach (var value in this)
        {
            XDocument doc = new XDocument();
            using (var subWriter = doc.CreateWriter())
            {
                // write xml into the writer
                ValueSerializerCache.Serializer.Serialize(subWriter, value);
            }

            if (doc.Root == null)
                continue;
            doc.Root.Name = doc.Root.Name.Namespace + XmlConvert.EncodeName(value.ElementName);
            // Remove redundant namespaces.
            foreach (var attr in doc.Root.Attributes().ToList())
            {
                if (!attr.IsNamespaceDeclaration || string.IsNullOrEmpty(attr.Value))
                    continue;
                var prefix = writer.LookupPrefix(attr.Value);
                if ((prefix == attr.Name.LocalName)
                    || (prefix == string.Empty && attr.Name == "xmlns"))
                    attr.Remove();
            }

            doc.Root.WriteTo(writer);
        }
    }

    #endregion
}

public static class XmlSerializationHelper
{
    static Attribute GetCustomAttribute(MemberInfo element, Type attributeType)
    {
        return Attribute.GetCustomAttribute(element, attributeType);
    }

    static T GetCustomAttribute<T>(MemberInfo element) where T : Attribute
    {
        return (T)GetCustomAttribute(element, typeof(T));
    }

    public static string DefaultXmlElementName(this Type type)
    {
        var xmlType = GetCustomAttribute<XmlTypeAttribute>(type);
        if (xmlType != null && !string.IsNullOrEmpty(xmlType.TypeName))
            return xmlType.TypeName;
        var xmlRoot = GetCustomAttribute<XmlRootAttribute>(type);
        if (xmlRoot != null && !string.IsNullOrEmpty(xmlRoot.ElementName))
            return xmlRoot.ElementName;
        return type.Name;
    }

    public static string DefaultXmlElementNamespace(this Type type)
    {
        var xmlType = GetCustomAttribute<XmlTypeAttribute>(type);
        if (xmlType != null && !string.IsNullOrEmpty(xmlType.Namespace))
            return xmlType.Namespace;
        var xmlRoot = GetCustomAttribute<XmlRootAttribute>(type);
        if (xmlRoot != null && !string.IsNullOrEmpty(xmlRoot.Namespace))
            return xmlRoot.Namespace;
        return string.Empty;
    }

    public static string GetXml<T>(this T obj)
    {
        return GetXml(obj, false);
    }

    public static string GetXml<T>(this T obj, bool omitNamespace)
    {
        return GetXml(obj, new XmlSerializer(obj.GetType()), omitNamespace);
    }

    public static string GetXml<T>(this T obj, XmlSerializer serializer)
    {
        return GetXml(obj, serializer, false);
    }

    public static string GetXml<T>(T obj, XmlSerializer serializer, bool omitStandardNamespaces)
    {
        XmlSerializerNamespaces ns = null;
        if (omitStandardNamespaces)
        {
            ns = new XmlSerializerNamespaces();
            ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
        }
        return GetXml(obj, serializer, ns);
    }

    public static string GetXml<T>(T obj, XmlSerializerNamespaces ns)
    {
        return GetXml(obj, new XmlSerializer(obj.GetType()), ns);
    }

    public static string GetXml<T>(T obj, XmlSerializer serializer, XmlSerializerNamespaces ns)
    {
        using (var textWriter = new StringWriter())
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;        // For cosmetic purposes.
            settings.IndentChars = "    "; // For cosmetic purposes.
            using (var xmlWriter = XmlWriter.Create(textWriter, settings))
            {
                if (ns != null)
                    serializer.Serialize(xmlWriter, obj, ns);
                else
                    serializer.Serialize(xmlWriter, obj);
            }
            return textWriter.ToString();
        }
    }
}

And use it like:

public class Property : IHasElementName
{
    public Property()
    {
    }

    public Property(string name, int id, string value)
    {
        this.name = name;
        this.id = id;
        this.value = value;
    }

    [XmlIgnore]
    public string name; //could be enum

    public int id;
    public string value;

    #region IHasElementName Members

    [XmlIgnore]
    string IHasElementName.ElementName { get { return name; }  set { name = value; } }

    #endregion
}

public class RootObject
{
    public RootObject()
    {
        this.Properties = new XmlNamedElementList<Property>();
    }

    public XmlNamedElementList<Property> Properties { get; set; }
}

public static class TestClass
{
    public static void Test()
    {
        var root = new RootObject
        {
            // Characters " <> first" in the first element name are for testing purposes.

            Properties = new XmlNamedElementList<Property> { new Property { id = 1, value = "1", name = "first" }, new Property("property1name", 2, "testvalue"), new Property("property2name", 10, "anothervalue") }
        };

        var xml = root.GetXml();
        Debug.WriteLine(xml);
    }
}

Which produces XML as follows:

<?xml version="1.0" encoding="utf-16"?>
<RootObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <Properties>
        <_x0020__x003C__x003E__x0020_first>
            <id>1</id>
            <value>1</value>
        </_x0020__x003C__x003E__x0020_first>
        <property1name>
            <id>2</id>
            <value>testvalue</value>
        </property1name>
        <property2name>
            <id>10</id>
            <value>anothervalue</value>
        </property2name>
    </Properties>
</RootObject>

Original Answer

As requested, here's an implementation of IXmlSerializable on a List<KeyValuePair<string, T>> in which the Key string becomes the element name in the collection.

What you would probably want to do is to adapt this to serialize a List<IHasElementName> where:

public interface IHasElementName
{
    string ElementName { get; set; }
}

public class Property : IHasElementName
{
    [XmlIgnore]
    public string name; //could be enum

    public int id;
    public string value;

    #region IHasElementName Members

    [XmlIgnore]
    string IHasElementName.ElementName
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }

    #endregion
}

If the name is actually an Enum, you could return the enum string representation from HasElementName.ElementName.

The list looks like:

public class XmlKeyValueList<T> : List<KeyValuePair<string, T>>, IXmlSerializable
{
    // TODO: validate that the "Key" string using XmlConvert.VerifyName.

    // https://msdn.microsoft.com/en-us/library/System.Xml.Serialization.XmlSerializer%28v=vs.110%29.aspx
    // Any serializer created with the "XmlSerializer(typeof(T), rootAttribute)" must be cached
    // to avoid resource & memory leaks.
    class ValueSerializerCache
    {
        // By using a nested class with a static constructor, we defer generation of the XmlSerializer until it's actually required.
        static ValueSerializerCache()
        {
            var rootAttribute = new XmlRootAttribute();
            rootAttribute.ElementName = ValueTypeName;
            rootAttribute.Namespace = ValueTypeNamespace;
            serializer = new XmlSerializer(typeof(T), rootAttribute);
        }

        static readonly XmlSerializer serializer;

        internal static XmlSerializer Serializer { get { return serializer; } }
    }

    static string ValueTypeName { get { return typeof(T).DefaultXmlElementName(); } }

    static string ValueTypeNamespace { get { return typeof(T).DefaultXmlElementNamespace(); } }

    #region IXmlSerializable Members

    XmlSchema IXmlSerializable.GetSchema()
    {
        return null;
    }

    void IXmlSerializable.ReadXml(XmlReader reader)
    {
        var typeName = ValueTypeName;

        reader.ReadStartElement(); // Advance to the first sub element of the list element.
        while (reader.NodeType == XmlNodeType.Element)
        {
            var name = reader.Name;
            using (var subReader = reader.ReadSubtree())
            {
                var doc = XDocument.Load(subReader);
                if (doc != null && doc.Root != null)
                {
                    doc.Root.Name = typeName;
                    using (var docReader = doc.CreateReader())
                    {
                        var obj = ValueSerializerCache.Serializer.Deserialize(docReader);
                        if (obj != null)
                        {
                            Add(new KeyValuePair<string, T>(name, (T)obj));
                        }
                    }
                }
            }
            // Move past the XmlNodeType.Element
            if (reader.NodeType == XmlNodeType.EndElement)
                reader.Read();
        }
    }

    void IXmlSerializable.WriteXml(XmlWriter writer)
    {
        foreach (var pair in this)
        {
            XDocument doc = new XDocument();
            using (var subWriter = doc.CreateWriter())
            {
                // write xml into the writer
                ValueSerializerCache.Serializer.Serialize(subWriter, pair.Value);
            }
            if (doc.Root == null)
                continue;
            doc.Root.Name = pair.Key;
            // Remove redundant namespaces.
            foreach (var attr in doc.Root.Attributes().ToList())
            {
                if (!attr.IsNamespaceDeclaration || string.IsNullOrEmpty(attr.Value))
                    continue;
                if (writer.LookupPrefix(attr.Value) == attr.Name.LocalName)
                    attr.Remove();
            }

            doc.Root.WriteTo(writer);
        }
    }

    #endregion
}

public static class XmlSerializationHelper
{
    public static string DefaultXmlElementName(this Type type)
    {
        var xmlType = type.GetCustomAttribute<XmlTypeAttribute>();
        if (xmlType != null && !string.IsNullOrEmpty(xmlType.TypeName))
            return xmlType.TypeName;
        var xmlRoot = type.GetCustomAttribute<XmlRootAttribute>();
        if (xmlRoot != null && !string.IsNullOrEmpty(xmlRoot.ElementName))
            return xmlRoot.ElementName;
        return type.Name;
    }

    public static string DefaultXmlElementNamespace(this Type type)
    {
        var xmlType = type.GetCustomAttribute<XmlTypeAttribute>();
        if (xmlType != null && !string.IsNullOrEmpty(xmlType.Namespace))
            return xmlType.Namespace;
        var xmlRoot = type.GetCustomAttribute<XmlRootAttribute>();
        if (xmlRoot != null && !string.IsNullOrEmpty(xmlRoot.Namespace))
            return xmlRoot.Namespace;
        return string.Empty;
    }
}
Sign up to request clarification or add additional context in comments.

16 Comments

@MichailMichailidis - Yes, because the XmlIgnore applied to the name attribute will prevent the element name from being serialized standalone.
@MichailMichailidis - woops, those are from XmlSerializationHelper in the original answer. Will edit.
@MichailMichailidis - GetXml() just some extension method I use for testing, that generates an XML string from an object. Presumably you already have something like that but I could add it if you want.
@MichailMichailidis - type.GetCustomAttribute<XmlTypeAttribute>() is a standard .Net extension: msdn.microsoft.com/en-us/library/hh194292%28v=vs.110%29.aspx . I added the GetXml() methods.
@MichailMichailidis - OK, I eliminated the use of the .Net 4.5 extension. I also fixed a couple bugs with namespaces in XmlNamedElementList so you should recopy that.
|
1

If UnitItem is changed into

  public class UnitItem
    {
        public string AAA;
        public string BBB;
    }

then the XML will be

<Items>
   <UnitItem>
         <AAA>testa</AAA>
         <BBB>testb</BBB>
   </UnitItem>
</Items>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.