scher
scher

Reputation: 1923

C#: Best way to have XML element name from generic type name

I want to create a xml for a generic class. One of the properties has the generic type. For this property I don't want to use the property name as its XML element name, but the name of the generic type.

The class looks like this:

[XmlRoot("Entity")]
public class StoreItem<TEntity>
    where TEntity : class, new()
{
    /// <summary>
    /// Gets and sets the status of the entity when storing.
    /// </summary>
    [XmlAttribute]
    public System.Data.Services.Client.EntityStates Status { get; set; }

    /// <summary>
    /// Gets and sets the entity to be stored.
    /// </summary>
    public TEntity Entity { get; set; }
}

When serializing a store item of kind StoreItem<SewageArea> the XML should contain something like:

<Entity Status="Deleted">
    <SewageArea ...>
       ...
    </SewageArea>
<Entity>

The requirement is, that the SewageArea in the above example should be serialized in the "normal" way. Another important thing is that if its possible the code should be prepared to automatically serializes new added properties at the StoreItemclass.

Upvotes: 3

Views: 3674

Answers (1)

dbc
dbc

Reputation: 117046

You'd like to do something along the lines of Rename class when serializing to XML but you cannot because attribute arguments cannot contain generic type parameters, i.e. [XmlElement(typeof(TEntity))]. And the obvious alternative of implementing IXmlSerializable is inconvenient because you lose automatic serialization of properties subsequently added to StoreItem<TEntity>.

Instead, what you can do is to make use of an [XmlAnyElement] surrogate property to do a nested serialization of your TEntity, as follows:

[XmlRoot("Entity")]
public class StoreItem<TEntity>
    where TEntity : class, new()
{
    /// <summary>
    /// Gets and sets the status of the entity when storing.
    /// </summary>
    [XmlAttribute]
    public System.Data.Services.Client.EntityStates Status { get; set; }

    /// <summary>
    /// Gets and sets the entity to be stored.
    /// </summary>
    [XmlIgnore]
    public TEntity Entity { get; set; }

    [XmlAnyElement]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public XElement XmlEntity
    {
        get
        {
            return (Entity == null ? null : XObjectExtensions.SerializeToXElement(Entity, null, true));
        }
        set
        {
            Entity = (value == null ? null : XObjectExtensions.Deserialize<TEntity>(value));
        }
    }
}

Using the extension methods:

public static class XObjectExtensions
{
    public static T Deserialize<T>(this XContainer element)
    {
        return element.Deserialize<T>(null);
    }

    public static T Deserialize<T>(this XContainer element, XmlSerializer serializer)
    {
        using (var reader = element.CreateReader())
        {
            serializer = serializer ?? new XmlSerializer(typeof(T));
            object result = serializer.Deserialize(reader);
            if (result is T)
                return (T)result;
        }
        return default(T);
    }

    public static XElement SerializeToXElement<T>(this T obj)
    {
        return obj.SerializeToXElement(null, true);
    }

    public static XElement SerializeToXElement<T>(this T obj, XmlSerializer serializer, bool omitStandardNamespaces)
    {
        var doc = new XDocument();
        using (var writer = doc.CreateWriter())
        {
            XmlSerializerNamespaces ns = null;
            if (omitStandardNamespaces)
                (ns = new XmlSerializerNamespaces()).Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
            serializer = serializer ?? new XmlSerializer(obj.GetType());
            serializer.Serialize(writer, obj, ns);
        }
        var element = doc.Root;
        if (element != null)
            element.Remove();
        return element;
    }
}

Note that the [XmlAnyElement] property will be called for all unknown elements, so if your XML for some reason has unexpected elements, you may get an exception thrown from XObjectExtensions.Deserialize<TEntity>(value)) because the root element name is wrong. You may want to catch and ignore exceptions from this method if that is a possibility.

Then, for the sample TEntity class

public class SewageArea
{
    public double Area { get; set; }
}

The XML output is:

<Entity Status="State1">
  <SewageArea>
    <Area>10101</Area>
  </SewageArea>
</Entity>

Sample fiddle.

Upvotes: 11

Related Questions