Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
180 views
in Technique[技术] by (71.8m points)

c# - Better IXmlSerializable format?

I have an interface IInput that is preventing the XmlSerializer from serializing the class natively (because it doesnt like interfaces). I found a hack/workaround that attempts to just create the underlying implementation when deserializing and then casts that back to the interface. The deserializer knows the underlying implementation because its encoded as a attribute AssemblyQualifiedName

In order to take advantage of this technique I have to implement IXmlSerializable, but only 1 property really needs help (IInput Input), I wish for all the other ones to act as if they were normal. Here is my class, it works as expected but seems like a very messy way of getting types of which the normal XMLserializer can serialize to conform to an IXmlSerialiable interface.

Is there some sort of "Serialize all properties natively except x"? If not what is one way I can make this more readable and/or less copy and pasted

public class JobInput : IJobInput, IXmlSerializable
    {
        public int AgencyId { get; set; }
        public Guid ExternalId { get; set; }
        public string Requester { get; set; }
        public IInput Input { get; set; }

        public XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(XmlReader reader)
        {
            reader.MoveToContent();
            reader.ReadStartElement();

            if (!reader.IsEmptyElement) 
            {
                reader.ReadStartElement("AgencyId");
                var xmlSerializer = new XmlSerializer(AgencyId.GetType());
                AgencyId = ((int)xmlSerializer.Deserialize(reader));
                reader.ReadEndElement();

                reader.ReadStartElement("ExternalId");
                xmlSerializer = new XmlSerializer(ExternalId.GetType());
                ExternalId = ((Guid)xmlSerializer.Deserialize(reader));
                reader.ReadEndElement();

                reader.ReadStartElement("Requester");
                xmlSerializer = new XmlSerializer(typeof(string));
                Requester = ((string)xmlSerializer.Deserialize(reader));
                reader.ReadEndElement();

                var type = Type.GetType(reader.GetAttribute("AssemblyQualifiedName"), true);
                reader.ReadStartElement("IInput");
                xmlSerializer = new XmlSerializer(type);
                Input = ((IInput)xmlSerializer.Deserialize(reader));
                reader.ReadEndElement();
                reader.ReadEndElement();
            }
        }

        public void WriteXml(XmlWriter writer)
        {
            writer.WriteStartElement("AgencyId");
            var xmlSerializer = new XmlSerializer(AgencyId.GetType());
            xmlSerializer.Serialize(writer, AgencyId);
            writer.WriteEndElement();

            writer.WriteStartElement("ExternalId");
            xmlSerializer = new XmlSerializer(ExternalId.GetType());
            xmlSerializer.Serialize(writer, ExternalId);
            writer.WriteEndElement();

            writer.WriteStartElement("Requester");
            xmlSerializer = new XmlSerializer(Requester.GetType());
            xmlSerializer.Serialize(writer, Requester);
            writer.WriteEndElement();


            writer.WriteStartElement("IInput");
            writer.WriteAttributeString("AssemblyQualifiedName", Input.GetType().AssemblyQualifiedName);
            xmlSerializer = new XmlSerializer(Input.GetType());
            xmlSerializer.Serialize(writer, Input);
            writer.WriteEndElement();
        }
    }

Is it possible to have a generic function that can just detect the type for all the concrete types and serialize/deserialize appropriately. I would like something like

public void WriteXml(XmlWriter writer) {
    GenericSerialize("AgencyId", AgencyId, writer);
    GenericSerialize("ExternalId", ExternalId, writer);
    GenericSerialize("Requester", Requester, writer);

    writer.WriteStartElement("IInput");
    writer.WriteAttributeString("AssemblyQualifiedName", Input.GetType().AssemblyQualifiedName);
    xmlSerializer = new XmlSerializer(Input.GetType());
    xmlSerializer.Serialize(writer, Input);
    writer.WriteEndElement();
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You can use [XmlAnyElement] to add an XElement []-valued property to your class that handles serialization and deserialization of properties that cannot be automatically serialized, like so:

[XmlRoot(Namespace = JobInput.XmlNamespace)]
[XmlType(Namespace = JobInput.XmlNamespace)]
public class JobInput
{
    const string XmlNamespace = "";

    public int AgencyId { get; set; }
    public Guid ExternalId { get; set; }
    public string Requester { get; set; }

    [XmlIgnore]
    public IInput Input { get; set; }

    [XmlAnyElement]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public XElement[] XmlCustomElements
    {
        get
        {
            var list = new List<XElement>();
            if (Input != null)
                list.Add(Input.SerializePolymorphicToXElement(XName.Get("Input", XmlNamespace)));
            // Add others as needed.
            return list.ToArray();
        }
        set
        {
            if (value == null)
                return;
            this.Input = value.DeserializePolymorphicEntry<IInput>(XName.Get("Input", XmlNamespace));
            // Add others as needed.
        }
    }
}

Your standard properties will now get automatically serialized and your custom properties can be semi-automatically serialized through nested calls to XmlSerializer using the appropriate type. The following extension methods are required:

public static class XObjectExtensions
{
    public static XmlSerializerNamespaces NoStandardXmlNamespaces()
    {
        var ns = new XmlSerializerNamespaces();
        ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
        return ns;
    }

    public static object Deserialize(this XContainer element, Type type, XmlSerializer serializer)
    {
        using (var reader = element.CreateReader())
        {
            return (serializer ?? new XmlSerializer(type)).Deserialize(reader);
        }
    }

    public static XElement SerializeToXElement<T>(this T obj, XmlSerializerNamespaces ns, XmlSerializer serializer)
    {
        var doc = new XDocument();
        using (var writer = doc.CreateWriter())
            (serializer ?? new XmlSerializer(obj.GetType())).Serialize(writer, obj, ns);
        var element = doc.Root;
        if (element != null)
            element.Remove();
        return element;
    }

    const string TypeAttributeName = "AssemblyQualifiedName";

    public static T DeserializePolymorphicEntry<T>(this XElement[] arrayValue, XName name)
    {
        var element = arrayValue.Where(e => e.Name == name).FirstOrDefault();
        return element.DeserializePolymorphic<T>(name);
    }

    public static T DeserializePolymorphic<T>(this XElement value, XName name)
    {
        if (value == null)
            return default(T);
        var typeName = (string)value.Attribute(TypeAttributeName);
        if (typeName == null)
            throw new InvalidOperationException(string.Format("Missing AssemblyQualifiedName for "{0}"", value.ToString()));
        var type = Type.GetType(typeName, true); // Throw on error
        return (T)value.Deserialize(type, XmlSerializerFactory.Create(type, name));
    }

    public static XElement SerializePolymorphicToXElement<T>(this T obj, XName name)
    {
        if (obj == null)
            return null;
        var element = obj.SerializeToXElement(XObjectExtensions.NoStandardXmlNamespaces(), XmlSerializerFactory.Create(obj.GetType(), name));
        // Remove namespace attributes (they will be added back by the XmlWriter if needed)
        foreach (var attr in element.Attributes().Where(a => a.IsNamespaceDeclaration).ToList())
            attr.Remove();
        element.Add(new XAttribute("AssemblyQualifiedName", obj.GetType().AssemblyQualifiedName));
        return element;
    }
}

public static class XmlSerializerFactory
{
    static readonly object padlock;
    static readonly Dictionary<Tuple<Type, XName>, XmlSerializer> serializers;

    // An explicit static constructor enables fairly lazy initialization.
    static XmlSerializerFactory()
    {
        padlock = new object();
        serializers = new Dictionary<Tuple<Type, XName>, XmlSerializer>();
    }

    /// <summary>
    /// Return a cached XmlSerializer for the given type and root name.
    /// </summary>
    /// <param name="type"></param>
    /// <param name="name"></param>
    /// <returns>a cached XmlSerializer</returns>
    public static XmlSerializer Create(Type type, XName name)
    {
        // According to https://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer%28v=vs.110%29.aspx
        // XmlSerializers created with new XmlSerializer(Type, XmlRootAttribute) must be cached in a hash table 
        // to avoid a severe memory leak & performance hit.
        if (type == null)
            throw new ArgumentNullException();
        if (name == null)
            return new XmlSerializer(type);
        lock (padlock)
        {
            XmlSerializer serializer;
            var key = Tuple.Create(type, name);
            if (!serializers.TryGetValue(key, out serializer))
                serializers[key] = serializer = new XmlSerializer(type, new XmlRootAttribute { ElementName = name.LocalName, Namespace = name.NamespaceName });
            return serializer;
        }
    }
}

Doing it this way makes your class look simpler and reduces the possibility of bugs from mistakes in implementing IXmlSerializable - but it does require a little bit of reusable infrastructure.

Prototype fiddle.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...